From 2e907826ae209925d21280354869fe0ef077eed0 Mon Sep 17 00:00:00 2001 From: Stuart Heap Date: Thu, 26 Aug 2021 13:44:22 +0200 Subject: [PATCH 01/21] add required fields to schema/org form --- .dockerignore | 3 +- Cargo.lock | 851 ++++++++++++------ docker/amd64/Dockerfile | 3 +- .../up.sql | 15 +- .../2019-09-12-100000_create_tables/up.sql | 17 +- .../up.sql | 15 +- src/api/core/organizations.rs | 46 + src/api/identity.rs | 59 +- src/db/models/organization.rs | 42 +- src/db/schemas/mysql/schema.rs | 9 + src/db/schemas/postgresql/schema.rs | 9 + src/db/schemas/sqlite/schema.rs | 9 + 12 files changed, 789 insertions(+), 289 deletions(-) diff --git a/.dockerignore b/.dockerignore index 69f51d2a24..6bc2dbdd5f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,7 +3,6 @@ target # Data folder data -.env .env.template .gitattributes @@ -24,4 +23,4 @@ hooks tools # Web vault -web-vault \ No newline at end of file +#web-vault diff --git a/Cargo.lock b/Cargo.lock index db93ff8b71..cafd7f7bfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,6 +55,47 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-stream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" +dependencies = [ + "async-stream-impl", + "futures-core", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3410529e8288c463bedb5930f82833bc0c90e5d2fe639a56582a4d09220b281" +dependencies = [ + "autocfg", +] + [[package]] name = "atty" version = "0.2.14" @@ -93,16 +134,6 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" -[[package]] -name = "base64" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" -dependencies = [ - "byteorder", - "safemem", -] - [[package]] name = "base64" version = "0.11.0" @@ -127,6 +158,21 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" +[[package]] +name = "biscuit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dee631cea28b00e115fd355a1adedc860b155096941dc01259969eabd434a37" +dependencies = [ + "chrono", + "data-encoding", + "num", + "once_cell", + "ring", + "serde", + "serde_json", +] + [[package]] name = "bitflags" version = "1.2.1" @@ -318,7 +364,7 @@ checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" dependencies = [ "percent-encoding 2.1.0", "time 0.2.27", - "version_check 0.9.3", + "version_check", ] [[package]] @@ -329,7 +375,7 @@ checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d" dependencies = [ "percent-encoding 2.1.0", "time 0.2.27", - "version_check 0.9.3", + "version_check", ] [[package]] @@ -340,7 +386,7 @@ checksum = "3818dfca4b0cb5211a659bbcbb94225b7127407b2b135e650d717bfb78ab10d3" dependencies = [ "cookie 0.14.4", "idna 0.2.3", - "log 0.4.14", + "log", "publicsuffix 1.5.6", "serde", "serde_json", @@ -356,7 +402,7 @@ checksum = "55b4ac5559dd39f7bdc516f769cb412b151585d8886d216871a8435ed7f862cd" dependencies = [ "cookie 0.15.1", "idna 0.2.3", - "log 0.4.14", + "log", "publicsuffix 2.1.0", "serde", "serde_json", @@ -445,8 +491,9 @@ dependencies = [ [[package]] name = "devise" -version = "0.3.0" -source = "git+https://github.com/SergioBenitez/Devise.git?rev=e58b3ac9a#e58b3ac9afc3b6ff10a8aaf02a3e768a8f530089" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c7580b072f1c8476148f16e0a0d5dedddab787da98d86c5082c5e9ed8ab595" dependencies = [ "devise_codegen", "devise_core", @@ -454,22 +501,25 @@ dependencies = [ [[package]] name = "devise_codegen" -version = "0.3.0" -source = "git+https://github.com/SergioBenitez/Devise.git?rev=e58b3ac9a#e58b3ac9afc3b6ff10a8aaf02a3e768a8f530089" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123c73e7a6e51b05c75fe1a1b2f4e241399ea5740ed810b0e3e6cacd9db5e7b2" dependencies = [ "devise_core", - "quote 1.0.9", + "quote", ] [[package]] name = "devise_core" -version = "0.3.0" -source = "git+https://github.com/SergioBenitez/Devise.git?rev=e58b3ac9a#e58b3ac9afc3b6ff10a8aaf02a3e768a8f530089" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841ef46f4787d9097405cac4e70fb8644fc037b526e8c14054247c0263c400d0" dependencies = [ "bitflags", - "proc-macro2 1.0.28", - "quote 1.0.9", - "syn 1.0.74", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", ] [[package]] @@ -495,9 +545,9 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", - "syn 1.0.74", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -604,10 +654,24 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9a4820f0ccc8a7afd67c39a0f1a0f4b07ca1725164271a64939d7aeb9af065" dependencies = [ - "log 0.4.14", + "log", "syslog", ] +[[package]] +name = "figment" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790b4292c72618abbab50f787a477014fe15634f96291de45672ce46afe122df" +dependencies = [ + "atomic", + "pear", + "serde", + "toml", + "uncased", + "version_check", +] + [[package]] name = "flate2" version = "1.0.20" @@ -745,9 +809,9 @@ checksum = "c54913bae956fb8df7f4dc6fc90362aa72e69148e3f39041fbe8742d21e0ac57" dependencies = [ "autocfg", "proc-macro-hack", - "proc-macro2 1.0.28", - "quote 1.0.9", - "syn 1.0.74", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -783,6 +847,19 @@ dependencies = [ "slab", ] +[[package]] +name = "generator" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1d9279ca822891c1a4dae06d185612cf8fc6acfe5dff37781b41297811b12ee" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "winapi 0.3.9", +] + [[package]] name = "generic-array" version = "0.7.3" @@ -809,7 +886,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" dependencies = [ "typenum", - "version_check 0.9.3", + "version_check", ] [[package]] @@ -877,7 +954,7 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72a0ffab8c36d0436114310c7e10b59b3307e650ddfabf6d006028e29a70c6e6" dependencies = [ - "log 0.4.14", + "log", "pest", "pest_derive", "quick-error 2.0.1", @@ -939,12 +1016,12 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b" dependencies = [ - "log 0.4.14", + "log", "mac", "markup5ever", - "proc-macro2 1.0.28", - "quote 1.0.9", - "syn 1.0.74", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -981,25 +1058,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" -[[package]] -name = "hyper" -version = "0.10.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" -dependencies = [ - "base64 0.9.3", - "httparse", - "language-tags", - "log 0.3.9", - "mime 0.2.6", - "num_cpus", - "time 0.1.44", - "traitobject", - "typeable", - "unicase 1.4.2", - "url 1.7.2", -] - [[package]] name = "hyper" version = "0.14.11" @@ -1024,18 +1082,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-sync-rustls" -version = "0.3.0-rc.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cb014c4ea00486e2b62860b5e15229d37516d4924177218beafbf46583de3ab" -dependencies = [ - "hyper 0.10.16", - "rustls", - "webpki", - "webpki-roots", -] - [[package]] name = "hyper-tls" version = "0.5.0" @@ -1043,7 +1089,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes 1.0.1", - "hyper 0.14.11", + "hyper", "native-tls", "tokio", "tokio-native-tls", @@ -1071,6 +1117,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + [[package]] name = "indexmap" version = "1.7.0" @@ -1079,8 +1131,15 @@ checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" dependencies = [ "autocfg", "hashbrown", + "serde", ] +[[package]] +name = "inlinable_string" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3094308123a0e9fd59659ce45e22de9f53fc1d2ac6e1feb9fef988e4f76cad77" + [[package]] name = "instant" version = "0.1.10" @@ -1154,12 +1213,6 @@ dependencies = [ "winapi-build", ] -[[package]] -name = "language-tags" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" - [[package]] name = "lazy_static" version = "1.4.0" @@ -1183,7 +1236,7 @@ dependencies = [ "hostname", "httpdate", "idna 0.2.3", - "mime 0.3.16", + "mime", "native-tls", "nom 6.1.2", "once_cell", @@ -1221,20 +1274,24 @@ dependencies = [ [[package]] name = "log" -version = "0.3.9" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "log 0.4.14", + "cfg-if 1.0.0", ] [[package]] -name = "log" -version = "0.4.14" +name = "loom" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "2111607c723d7857e0d8299d5ce7a0bf4b844d3e44f8de136b13da513eaf8fc4" dependencies = [ "cfg-if 1.0.0", + "generator", + "scoped-tls", + "serde", + "serde_json", ] [[package]] @@ -1255,7 +1312,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" dependencies = [ - "log 0.4.14", + "log", "phf", "phf_codegen", "string_cache", @@ -1315,18 +1372,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9753f12909fd8d923f75ae5c3258cae1ed3c8ec052e1b38c93c21a6d157f789c" dependencies = [ "migrations_internals", - "proc-macro2 1.0.28", - "quote 1.0.9", - "syn 1.0.74", -] - -[[package]] -name = "mime" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" -dependencies = [ - "log 0.3.9", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1341,8 +1389,8 @@ version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" dependencies = [ - "mime 0.3.16", - "unicase 2.6.0", + "mime", + "unicase", ] [[package]] @@ -1367,7 +1415,7 @@ dependencies = [ "iovec", "kernel32-sys", "libc", - "log 0.4.14", + "log", "miow 0.2.2", "net2", "slab", @@ -1381,7 +1429,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" dependencies = [ "libc", - "log 0.4.14", + "log", "miow 0.3.7", "ntapi", "winapi 0.3.9", @@ -1394,7 +1442,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" dependencies = [ "lazycell", - "log 0.4.14", + "log", "mio 0.6.23", "slab", ] @@ -1420,6 +1468,26 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "multer" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "408327e2999b839cd1af003fc01b2019a6c10a1361769542203f6fedc5179680" +dependencies = [ + "bytes 1.0.1", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log", + "mime", + "spin 0.9.2", + "tokio", + "tokio-util", + "twoway 0.2.2", + "version_check", +] + [[package]] name = "multipart" version = "0.18.0" @@ -1428,14 +1496,14 @@ checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" dependencies = [ "buf_redux", "httparse", - "log 0.4.14", - "mime 0.3.16", + "log", + "mime", "mime_guess", "quick-error 1.2.3", "rand 0.8.4", "safemem", "tempfile", - "twoway", + "twoway 0.1.8", ] [[package]] @@ -1456,7 +1524,7 @@ checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" dependencies = [ "lazy_static", "libc", - "log 0.4.14", + "log", "openssl", "openssl-probe", "openssl-sys", @@ -1507,7 +1575,7 @@ dependencies = [ "bitvec", "funty", "memchr", - "version_check 0.9.3", + "version_check", ] [[package]] @@ -1519,6 +1587,20 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "num" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f" +dependencies = [ + "num-bigint 0.3.2", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.2.6" @@ -1530,15 +1612,35 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d0a3d5e207573f948a9e5376662aa743a2ea13f7c50a554d7af443a73fbfeba" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", - "syn 1.0.74", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1551,6 +1653,29 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-bigint 0.3.2", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" @@ -1610,6 +1735,24 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openid" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab30a9456b3484c408d9708b6f65b2bd834fdf22b73567775e1ca6de5524dd19" +dependencies = [ + "base64 0.13.0", + "biscuit", + "chrono", + "lazy_static", + "reqwest", + "serde", + "serde_json", + "thiserror", + "url 2.2.2", + "validator", +] + [[package]] name = "openssl" version = "0.10.35" @@ -1671,7 +1814,7 @@ dependencies = [ "byteorder", "bytes 0.4.12", "httparse", - "log 0.4.14", + "log", "mio 0.6.23", "mio-extras", "rand 0.7.3", @@ -1744,24 +1887,25 @@ checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58" [[package]] name = "pear" -version = "0.1.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5320f212db967792b67cfe12bd469d08afd6318a249bd917d5c19bc92200ab8a" +checksum = "15e44241c5e4c868e3eaa78b7c1848cadd6344ed4f54d029832d32b415a58702" dependencies = [ + "inlinable_string", "pear_codegen", + "yansi", ] [[package]] name = "pear_codegen" -version = "0.1.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfc1c836fdc3d1ef87c348b237b5b5c4dff922156fb2d968f57734f9669768ca" +checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0" dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "syn 0.15.44", - "version_check 0.9.3", - "yansi", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", ] [[package]] @@ -1814,9 +1958,9 @@ checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" dependencies = [ "pest", "pest_meta", - "proc-macro2 1.0.28", - "quote 1.0.9", - "syn 1.0.74", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1913,6 +2057,30 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -1927,20 +2095,24 @@ checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" [[package]] name = "proc-macro2" -version = "0.4.30" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" dependencies = [ - "unicode-xid 0.1.0", + "unicode-xid", ] [[package]] -name = "proc-macro2" -version = "1.0.28" +name = "proc-macro2-diagnostics" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" dependencies = [ - "unicode-xid 0.2.2", + "proc-macro2", + "quote", + "syn", + "version_check", + "yansi", ] [[package]] @@ -1983,22 +2155,13 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" -[[package]] -name = "quote" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" -dependencies = [ - "proc-macro2 0.4.30", -] - [[package]] name = "quote" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ - "proc-macro2 1.0.28", + "proc-macro2", ] [[package]] @@ -2013,7 +2176,7 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "545c5bc2b880973c9c10e4067418407a0ccaa3091781d1671d46eb35107cb26f" dependencies = [ - "log 0.4.14", + "log", "parking_lot 0.11.1", "scheduled-thread-pool", ] @@ -2161,6 +2324,26 @@ dependencies = [ "bitflags", ] +[[package]] +name = "ref-cast" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "regex" version = "1.5.4" @@ -2203,13 +2386,13 @@ dependencies = [ "futures-util", "http", "http-body", - "hyper 0.14.11", + "hyper", "hyper-tls", "ipnet", "js-sys", "lazy_static", - "log 0.4.14", - "mime 0.3.16", + "log", + "mime", "native-tls", "percent-encoding 2.1.0", "pin-project-lite", @@ -2237,7 +2420,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", + "spin 0.5.2", "untrusted", "web-sys", "winapi 0.3.9", @@ -2265,65 +2448,86 @@ dependencies = [ [[package]] name = "rocket" -version = "0.5.0-dev" -source = "git+https://github.com/SergioBenitez/Rocket?rev=263e39b5b429de1913ce7e3036575a7b4d88b6d7#263e39b5b429de1913ce7e3036575a7b4d88b6d7" +version = "0.5.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a71c18c42a0eb15bf3816831caf0dad11e7966f2a41aaf486a701979c4dd1f2" dependencies = [ + "async-stream", + "async-trait", + "atomic", "atty", "binascii", - "log 0.4.14", + "bytes 1.0.1", + "either", + "figment", + "futures", + "indexmap", + "log", "memchr", + "multer", "num_cpus", - "pear", + "parking_lot 0.11.1", + "pin-project-lite", + "rand 0.8.4", + "ref-cast", "rocket_codegen", "rocket_http", + "serde", + "serde_json", "state", + "tempfile", "time 0.2.27", - "toml", - "version_check 0.9.3", + "tokio", + "tokio-stream", + "tokio-util", + "ubyte", + "version_check", "yansi", ] [[package]] name = "rocket_codegen" -version = "0.5.0-dev" -source = "git+https://github.com/SergioBenitez/Rocket?rev=263e39b5b429de1913ce7e3036575a7b4d88b6d7#263e39b5b429de1913ce7e3036575a7b4d88b6d7" +version = "0.5.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66f5fa462f7eb958bba8710c17c5d774bbbd59809fa76fb1957af7e545aea8bb" dependencies = [ "devise", "glob", "indexmap", - "quote 1.0.9", + "proc-macro2", + "quote", "rocket_http", - "version_check 0.9.3", - "yansi", -] - -[[package]] -name = "rocket_contrib" -version = "0.5.0-dev" -source = "git+https://github.com/SergioBenitez/Rocket?rev=263e39b5b429de1913ce7e3036575a7b4d88b6d7#263e39b5b429de1913ce7e3036575a7b4d88b6d7" -dependencies = [ - "log 0.4.14", - "rocket", - "serde", - "serde_json", + "syn", + "unicode-xid", ] [[package]] name = "rocket_http" -version = "0.5.0-dev" -source = "git+https://github.com/SergioBenitez/Rocket?rev=263e39b5b429de1913ce7e3036575a7b4d88b6d7#263e39b5b429de1913ce7e3036575a7b4d88b6d7" +version = "0.5.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23c8b7d512d2fcac2316ebe590cde67573844b99e6cc9ee0f53375fa16e25ebd" dependencies = [ - "cookie 0.14.4", - "hyper 0.10.16", - "hyper-sync-rustls", + "cookie 0.15.1", + "either", + "http", + "hyper", "indexmap", + "log", + "memchr", + "mime", + "parking_lot 0.11.1", "pear", - "percent-encoding 1.0.1", - "rustls", + "percent-encoding 2.1.0", + "pin-project-lite", + "ref-cast", + "serde", "smallvec 1.6.1", + "stable-pattern", "state", "time 0.2.27", - "unicode-xid 0.2.2", + "tokio", + "tokio-rustls", + "uncased", ] [[package]] @@ -2349,17 +2553,23 @@ dependencies = [ [[package]] name = "rustls" -version = "0.17.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0d4a31f5d68413404705d6982529b0e11a9aacd4839d1d6222ee3b8cb4015e1" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "base64 0.11.0", - "log 0.4.14", + "base64 0.13.0", + "log", "ring", "sct", "webpki", ] +[[package]] +name = "rustversion" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" + [[package]] name = "ryu" version = "1.0.5" @@ -2400,6 +2610,12 @@ dependencies = [ "parking_lot 0.11.1", ] +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + [[package]] name = "scopeguard" version = "1.1.0" @@ -2488,9 +2704,9 @@ version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", - "syn 1.0.74", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2499,6 +2715,7 @@ version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" dependencies = [ + "indexmap", "itoa", "ryu", "serde", @@ -2573,6 +2790,15 @@ dependencies = [ "generic-array 0.7.3", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + [[package]] name = "simple_asn1" version = "0.4.1" @@ -2580,7 +2806,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b" dependencies = [ "chrono", - "num-bigint", + "num-bigint 0.2.6", "num-traits", ] @@ -2627,6 +2853,21 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" + +[[package]] +name = "stable-pattern" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" +dependencies = [ + "memchr", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2639,14 +2880,17 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" dependencies = [ - "version_check 0.9.3", + "version_check", ] [[package]] name = "state" -version = "0.4.2" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3015a7d0a5fd5105c91c3710d42f9ccf0abfb287d62206484dcc67f9569a6483" +checksum = "87cf4f5369e6d3044b5e365c9690f451516ac8f0954084622b49ea3fde2f6de5" +dependencies = [ + "loom", +] [[package]] name = "stdweb" @@ -2668,11 +2912,11 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2", + "quote", "serde", "serde_derive", - "syn 1.0.74", + "syn", ] [[package]] @@ -2682,13 +2926,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" dependencies = [ "base-x", - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2", + "quote", "serde", "serde_derive", "serde_json", "sha1", - "syn 1.0.74", + "syn", ] [[package]] @@ -2718,8 +2962,8 @@ checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97" dependencies = [ "phf_generator", "phf_shared", - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2", + "quote", ] [[package]] @@ -2728,26 +2972,15 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" -[[package]] -name = "syn" -version = "0.15.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "unicode-xid 0.1.0", -] - [[package]] name = "syn" version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", - "unicode-xid 0.2.2", + "proc-macro2", + "quote", + "unicode-xid", ] [[package]] @@ -2758,7 +2991,7 @@ checksum = "a0641142b4081d3d44beffa4eefd7346a228cdf91ed70186db2ca2cef762d327" dependencies = [ "error-chain", "libc", - "log 0.4.14", + "log", "time 0.1.44", ] @@ -2808,9 +3041,9 @@ version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", - "syn 1.0.74", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2844,7 +3077,7 @@ dependencies = [ "standback", "stdweb", "time-macros", - "version_check 0.9.3", + "version_check", "winapi 0.3.9", ] @@ -2865,10 +3098,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" dependencies = [ "proc-macro-hack", - "proc-macro2 1.0.28", - "quote 1.0.9", + "proc-macro2", + "quote", "standback", - "syn 1.0.74", + "syn", ] [[package]] @@ -2898,10 +3131,24 @@ dependencies = [ "memchr", "mio 0.7.13", "num_cpus", + "once_cell", "pin-project-lite", + "signal-hook-registry", + "tokio-macros", "winapi 0.3.9", ] +[[package]] +name = "tokio-macros" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio-native-tls" version = "0.3.0" @@ -2912,6 +3159,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + [[package]] name = "tokio-socks" version = "0.5.1" @@ -2924,6 +3182,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.6.7" @@ -2933,16 +3202,16 @@ dependencies = [ "bytes 1.0.1", "futures-core", "futures-sink", - "log 0.4.14", + "log", "pin-project-lite", "tokio", ] [[package]] name = "toml" -version = "0.4.10" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ "serde", ] @@ -2960,7 +3229,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d" dependencies = [ "cfg-if 1.0.0", - "log 0.4.14", + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2972,9 +3241,9 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", - "syn 1.0.74", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2986,12 +3255,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "traitobject" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" - [[package]] name = "try-lock" version = "0.2.3" @@ -3008,10 +3271,14 @@ dependencies = [ ] [[package]] -name = "typeable" -version = "0.1.2" +name = "twoway" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" +checksum = "c57ffb460d7c24cd6eda43694110189030a3d1dfe418416d9468fd1c1d290b47" +dependencies = [ + "memchr", + "unchecked-index", +] [[package]] name = "typenum" @@ -3036,6 +3303,15 @@ dependencies = [ "time 0.1.44", ] +[[package]] +name = "ubyte" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42756bb9e708855de2f8a98195643dff31a97f0485d90d8467b39dc24be9e8fe" +dependencies = [ + "serde", +] + [[package]] name = "ucd-trie" version = "0.1.3" @@ -3043,21 +3319,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] -name = "unicase" -version = "1.4.2" +name = "uncased" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" +checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" dependencies = [ - "version_check 0.1.5", + "serde", + "version_check", ] +[[package]] +name = "unchecked-index" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c" + [[package]] name = "unicase" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" dependencies = [ - "version_check 0.9.3", + "version_check", ] [[package]] @@ -3078,12 +3361,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-xid" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" - [[package]] name = "unicode-xid" version = "0.2.2" @@ -3117,6 +3394,7 @@ dependencies = [ "idna 0.2.3", "matches", "percent-encoding 2.1.0", + "serde", ] [[package]] @@ -3134,6 +3412,45 @@ dependencies = [ "getrandom 0.2.3", ] +[[package]] +name = "validator" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d6937c33ec6039d8071bcf72933146b5bbe378d645d8fa59bdadabfc2a249" +dependencies = [ + "idna 0.2.3", + "lazy_static", + "regex", + "serde", + "serde_derive", + "serde_json", + "url 2.2.2", + "validator_derive", + "validator_types", +] + +[[package]] +name = "validator_derive" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286b4497f270f59276a89ae0ad109d5f8f18c69b613e3fb22b61201aadb0c4d" +dependencies = [ + "if_chain", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn", + "validator_types", +] + +[[package]] +name = "validator_types" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9680608df133af2c1ddd5eaf1ddce91d60d61b6bc51494ef326458365a470a" + [[package]] name = "vaultwarden" version = "1.0.0" @@ -3151,6 +3468,7 @@ dependencies = [ "diesel_migrations", "dotenv", "fern", + "futures", "handlebars", "html5ever", "idna 0.2.3", @@ -3158,13 +3476,14 @@ dependencies = [ "jsonwebtoken", "lettre", "libsqlite3-sys", - "log 0.4.14", + "log", "markup5ever_rcdom", "multipart", "num-derive", "num-traits", "oath", "once_cell", + "openid", "openssl", "parity-ws", "paste", @@ -3176,7 +3495,6 @@ dependencies = [ "ring", "rmpv", "rocket", - "rocket_contrib", "serde", "serde_json", "syslog", @@ -3195,12 +3513,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" - [[package]] name = "version_check" version = "0.9.3" @@ -3224,7 +3536,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "log 0.4.14", + "log", "try-lock", ] @@ -3260,10 +3572,10 @@ checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" dependencies = [ "bumpalo", "lazy_static", - "log 0.4.14", - "proc-macro2 1.0.28", - "quote 1.0.9", - "syn 1.0.74", + "log", + "proc-macro2", + "quote", + "syn", "wasm-bindgen-shared", ] @@ -3285,7 +3597,7 @@ version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" dependencies = [ - "quote 1.0.9", + "quote", "wasm-bindgen-macro-support", ] @@ -3295,9 +3607,9 @@ version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", - "syn 1.0.74", + "proc-macro2", + "quote", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3325,7 +3637,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bbb2b77105c3b25ef0187146d80824648da0645f650c4d2080e3815d6cbbb87" dependencies = [ "base64 0.13.0", - "log 0.4.14", + "log", "nom 4.1.1", "openssl", "rand 0.8.4", @@ -3347,15 +3659,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "webpki-roots" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8eff4b7516a57307f9349c64bf34caa34b940b66fed4b2fb3136cb7386e5739" -dependencies = [ - "webpki", -] - [[package]] name = "winapi" version = "0.2.8" @@ -3430,7 +3733,7 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b1b52e6e8614d4a58b8e70cf51ec0cc21b256ad8206708bcff8139b5bbd6a59" dependencies = [ - "log 0.4.14", + "log", "mac", "markup5ever", "time 0.1.44", @@ -3458,3 +3761,13 @@ dependencies = [ "threadpool", "url 1.7.2", ] + +# [[patch.unused]] +# name = "rocket" +# version = "0.5.0-dev" +# source = "git+https://github.com/SergioBenitez/Rocket?rev=263e39b5b429de1913ce7e3036575a7b4d88b6d7#263e39b5b429de1913ce7e3036575a7b4d88b6d7" + +# [[patch.unused]] +# name = "rocket_contrib" +# version = "0.5.0-dev" +# source = "git+https://github.com/SergioBenitez/Rocket?rev=263e39b5b429de1913ce7e3036575a7b4d88b6d7#263e39b5b429de1913ce7e3036575a7b4d88b6d7" diff --git a/docker/amd64/Dockerfile b/docker/amd64/Dockerfile index 70d9c8f742..8737ad5a40 100644 --- a/docker/amd64/Dockerfile +++ b/docker/amd64/Dockerfile @@ -22,7 +22,6 @@ # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:29a4fa7bf3790fff9d908b02ac5a154913491f4bf30c95b87b06d8cf1c5516b5 # [vaultwarden/web-vault:v2.21.1] # -FROM vaultwarden/web-vault@sha256:29a4fa7bf3790fff9d908b02ac5a154913491f4bf30c95b87b06d8cf1c5516b5 as vault ########################## BUILD IMAGE ########################## FROM rust:1.53 as build @@ -101,7 +100,7 @@ EXPOSE 3012 # and the binary from the "build" stage to the current stage WORKDIR / COPY Rocket.toml . -COPY --from=vault /web-vault ./web-vault +COPY ./web-vault/build ./web-vault COPY --from=build /app/target/release/vaultwarden . COPY docker/healthcheck.sh /healthcheck.sh diff --git a/migrations/mysql/2018-02-17-205753_create_collections_and_orgs/up.sql b/migrations/mysql/2018-02-17-205753_create_collections_and_orgs/up.sql index dd90f9dc15..5cf2ec101a 100644 --- a/migrations/mysql/2018-02-17-205753_create_collections_and_orgs/up.sql +++ b/migrations/mysql/2018-02-17-205753_create_collections_and_orgs/up.sql @@ -5,9 +5,18 @@ CREATE TABLE collections ( ); CREATE TABLE organizations ( - uuid VARCHAR(40) NOT NULL PRIMARY KEY, - name TEXT NOT NULL, - billing_email TEXT NOT NULL + uuid VARCHAR(40) NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + billing_email TEXT NOT NULL, + identifier TEXT NOT NULL, + use_sso BOOLEAN NOT NULL, + callback_path TEXT NOT NULL, + signed_out_callback_path TEXT NOT NULL, + authority TEXT NOT NULL, + client_id TEXT NOT NULL, + client_secret TEXT NOT NULL, + metadata_address TEXT NOT NULL, + oidc_redirect_behavior TEXT NOT NULL ); CREATE TABLE users_collections ( diff --git a/migrations/postgresql/2019-09-12-100000_create_tables/up.sql b/migrations/postgresql/2019-09-12-100000_create_tables/up.sql index c747e9aa03..384669efff 100644 --- a/migrations/postgresql/2019-09-12-100000_create_tables/up.sql +++ b/migrations/postgresql/2019-09-12-100000_create_tables/up.sql @@ -33,9 +33,18 @@ CREATE TABLE devices ( ); CREATE TABLE organizations ( - uuid VARCHAR(40) NOT NULL PRIMARY KEY, - name TEXT NOT NULL, - billing_email TEXT NOT NULL + uuid VARCHAR(40) NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + billing_email TEXT NOT NULL, + identifier TEXT NOT NULL, + use_sso BOOLEAN NOT NULL, + callback_path TEXT NOT NULL, + signed_out_callback_path TEXT NOT NULL, + authority TEXT NOT NULL, + client_id TEXT NOT NULL, + client_secret TEXT NOT NULL, + metadata_address TEXT NOT NULL, + oidc_redirect_behavior TEXT NOT NULL ); CREATE TABLE ciphers ( @@ -118,4 +127,4 @@ CREATE TABLE twofactor ( CREATE TABLE invitations ( email VARCHAR(255) NOT NULL PRIMARY KEY -); \ No newline at end of file +); diff --git a/migrations/sqlite/2018-02-17-205753_create_collections_and_orgs/up.sql b/migrations/sqlite/2018-02-17-205753_create_collections_and_orgs/up.sql index 29601a4a5e..92391417fe 100644 --- a/migrations/sqlite/2018-02-17-205753_create_collections_and_orgs/up.sql +++ b/migrations/sqlite/2018-02-17-205753_create_collections_and_orgs/up.sql @@ -5,9 +5,18 @@ CREATE TABLE collections ( ); CREATE TABLE organizations ( - uuid TEXT NOT NULL PRIMARY KEY, - name TEXT NOT NULL, - billing_email TEXT NOT NULL + uuid TEXT NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + billing_email TEXT NOT NULL, + identifier TEXT NOT NULL, + use_sso BOOLEAN NOT NULL, + callback_path TEXT NOT NULL, + signed_out_callback_path TEXT NOT NULL, + authority TEXT NOT NULL, + client_id TEXT NOT NULL, + client_secret TEXT NOT NULL, + metadata_address TEXT NOT NULL, + oidc_redirect_behavior TEXT NOT NULL ); diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index c1d2326cc6..774f806771 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -24,6 +24,7 @@ pub fn routes() -> Vec { put_collection_users, put_organization, post_organization, + put_organization_sso, post_organization_collections, delete_organization_collection_user, post_organization_collection_delete_user, @@ -72,6 +73,20 @@ struct OrgData { struct OrganizationUpdateData { BillingEmail: String, Name: String, + Identifier: Option, +} + +#[derive(Deserialize, Debug)] +#[allow(non_snake_case)] +struct OrganizationSsoUpdateData { + UseSso: bool, + CallbackPath: String, + SignedOutCallbackPath: String, + Authority: String, + ClientId: String, + ClientSecret: String, + MetadataAddress: String, + OidcRedirectBehavior: String, } #[derive(Deserialize, Debug)] @@ -200,6 +215,37 @@ fn post_organization( org.name = data.Name; org.billing_email = data.BillingEmail; + org.identifier = match data.Identifier { + Some(identifier) => identifier, + None => String::from(""), + }; + + org.save(&conn)?; + Ok(Json(org.to_json())) +} + +#[put("/organizations//sso", data = "")] +fn put_organization_sso( + org_id: String, + _headers: OwnerHeaders, + data: JsonUpcase, + conn: DbConn, +) -> JsonResult { + let data: OrganizationSsoUpdateData = data.into_inner().data; + + let mut org = match Organization::find_by_uuid(&org_id, &conn) { + Some(organization) => organization, + None => err!("Can't find organization details"), + }; + + org.use_sso = data.UseSso; + org.callback_path = data.CallbackPath; + org.signed_out_callback_path = data.SignedOutCallbackPath; + org.authority = data.Authority; + org.client_id = data.ClientId; + org.client_secret = data.ClientSecret; + org.metadata_address = data.MetadataAddress; + org.oidc_redirect_behavior = data.OidcRedirectBehavior; org.save(&conn)?; Ok(Json(org.to_json())) diff --git a/src/api/identity.rs b/src/api/identity.rs index 1c1ab2338c..7a13cdfd88 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -1,6 +1,7 @@ use chrono::Local; use num_traits::FromPrimitive; use rocket::{ + http::{RawStr, Status}, request::{Form, FormItems, FromForm}, Route, }; @@ -19,7 +20,7 @@ use crate::{ }; pub fn routes() -> Vec { - routes![login] + routes![login, prevalidate, authorize] } #[post("/connect/token", data = "")] @@ -421,7 +422,6 @@ impl<'f> FromForm<'f> for ConnectData { key => warn!("Detected unexpected parameter during login: {}", key), } } - Ok(form) } } @@ -432,3 +432,58 @@ fn _check_is_some(value: &Option, msg: &str) -> EmptyResult { } Ok(()) } + +fn invalid_json(error_message: &str, exception: bool) -> JsonResult { + if exception { + err_code!(error_message, Status::BadRequest.code) + } + err_code!(error_message, Status::InternalServerError.code) +} + +#[get("/account/prevalidate?")] +#[allow(non_snake_case)] +fn prevalidate(domainHint: &RawStr, conn: DbConn) -> JsonResult { + let empty_result = json!({}); + + // TODO as_str shouldn't be used here + let organization = Organization::find_by_identifier(domainHint.as_str(), &conn); + match organization { + Some(organization) => { + if !organization.use_sso { + return invalid_json("SSO Not allowed for organization", false); + } + }, + None => { + return invalid_json("Organization not found by identifier", false); + }, + } + + if domainHint == "" { + return invalid_json("No Organization Identifier Provided", false); + } + + Ok(Json(empty_result)) +} + + +#[get("/connect/authorize?")] +fn authorize( + domain_hint: &RawStr, + conn: DbConn, +) { + let empty_result = json!({}); + let organization = Organization::find_by_identifier(domain_hint.as_str(), &conn); + match organization { + Some(organization) => { + println!("found org. authority: {}", organization.authority); + let redirect = Some(organization.callback_path.to_string()); + let issuer = reqwest::Url::parse(&organization.authority).unwrap(); + println!("got issuer: {}", issuer); + // return Ok(Json(empty_result)); + }, + None => { + println!("error"); + // return invalid_json("No Organization found", false); + } + } +} diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index e5141bb843..538fdfbffb 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -12,8 +12,17 @@ db_object! { pub uuid: String, pub name: String, pub billing_email: String, + pub identifier: String, pub private_key: Option, pub public_key: Option, + pub use_sso: bool, + pub callback_path: String, + pub signed_out_callback_path: String, + pub authority: String, + pub client_id: String, + pub client_secret: String, + pub metadata_address: String, + pub oidc_redirect_behavior: String, } #[derive(Identifiable, Queryable, Insertable, AsChangeset)] @@ -131,13 +140,22 @@ impl Organization { billing_email, private_key, public_key, + identifier: String::from(""), + use_sso: false, + callback_path: String::from("http://localhost/oidc-signin"), + signed_out_callback_path: String::from("http://localhost/sso/oidc-signin"), + authority: String::from(""), + client_id: String::from(""), + client_secret: String::from(""), + metadata_address: String::from(""), + oidc_redirect_behavior: String::from(""), } } pub fn to_json(&self) -> Value { json!({ "Id": self.uuid, - "Identifier": null, // not supported by us + "Identifier": self.identifier, "Name": self.name, "Seats": 10, // The value doesn't matter, we don't check server-side "MaxCollections": 10, // The value doesn't matter, we don't check server-side @@ -148,7 +166,7 @@ impl Organization { "UseGroups": false, // not supported by us "UseTotp": true, "UsePolicies": true, - "UseSso": false, // We do not support SSO + "UseSso": self.use_sso, "SelfHost": true, "UseApi": false, // not supported by us "HasPublicAndPrivateKeys": self.private_key.is_some() && self.public_key.is_some(), @@ -166,6 +184,13 @@ impl Organization { "PlanType": 5, // TeamsAnnually plan "UsersGetPremium": true, "Object": "organization", + "CallbackPath": self.callback_path, + "SignedOutCallbackPath": self.signed_out_callback_path, + "Authority": self.authority, + "ClientId": self.client_id, + "ClientSecret": self.client_secret, + "MetadataAddress": self.metadata_address, + "OidcRedirectBehavior": self.oidc_redirect_behavior, }) } } @@ -254,6 +279,15 @@ impl Organization { }} } + pub fn find_by_identifier(identifier: &str, conn: &DbConn) -> Option { + db_run! { conn: { + organizations::table + .filter(organizations::identifier.eq(identifier)) + .first::(conn) + .ok().from_db() + }} + } + pub fn get_all(conn: &DbConn) -> Vec { db_run! { conn: { organizations::table.load::(conn).expect("Error loading organizations").from_db() @@ -283,8 +317,8 @@ impl UserOrganization { "SelfHost": true, "HasPublicAndPrivateKeys": org.private_key.is_some() && org.public_key.is_some(), "ResetPasswordEnrolled": false, // not supported by us - "SsoBound": false, // We do not support SSO - "UseSso": false, // We do not support SSO + "SsoBound": true, + "UseSso": true, // TODO: Add support for Business Portal // Upstream is moving Policies and SSO management outside of the web-vault to /portal // For now they still have that code also in the web-vault, but they will remove it at some point. diff --git a/src/db/schemas/mysql/schema.rs b/src/db/schemas/mysql/schema.rs index 149d2267eb..32bbc44122 100644 --- a/src/db/schemas/mysql/schema.rs +++ b/src/db/schemas/mysql/schema.rs @@ -100,8 +100,17 @@ table! { uuid -> Text, name -> Text, billing_email -> Text, + identifier -> Text, private_key -> Nullable, public_key -> Nullable, + use_sso -> Bool, + callback_path -> Text, + signed_out_callback_path -> Text, + authority -> Text, + client_id -> Text, + client_secret -> Text, + metadata_address -> Text, + oidc_redirect_behavior -> Text, } } diff --git a/src/db/schemas/postgresql/schema.rs b/src/db/schemas/postgresql/schema.rs index 8feb2eb2b7..5da8c2eb6d 100644 --- a/src/db/schemas/postgresql/schema.rs +++ b/src/db/schemas/postgresql/schema.rs @@ -100,8 +100,17 @@ table! { uuid -> Text, name -> Text, billing_email -> Text, + identifier -> Text, private_key -> Nullable, public_key -> Nullable, + use_sso -> Bool, + callback_path -> Text, + signed_out_callback_path -> Text, + authority -> Text, + client_id -> Text, + client_secret -> Text, + metadata_address -> Text, + oidc_redirect_behavior -> Text, } } diff --git a/src/db/schemas/sqlite/schema.rs b/src/db/schemas/sqlite/schema.rs index 8feb2eb2b7..5da8c2eb6d 100644 --- a/src/db/schemas/sqlite/schema.rs +++ b/src/db/schemas/sqlite/schema.rs @@ -100,8 +100,17 @@ table! { uuid -> Text, name -> Text, billing_email -> Text, + identifier -> Text, private_key -> Nullable, public_key -> Nullable, + use_sso -> Bool, + callback_path -> Text, + signed_out_callback_path -> Text, + authority -> Text, + client_id -> Text, + client_secret -> Text, + metadata_address -> Text, + oidc_redirect_behavior -> Text, } } From 4674464aed0fc70303debce7696ad5d5e941e671 Mon Sep 17 00:00:00 2001 From: Stuart Heap Date: Tue, 31 Aug 2021 15:13:56 +0200 Subject: [PATCH 02/21] working sso login --- Cargo.toml | 2 + src/api/identity.rs | 159 +++++++++++++++++++++++++++++++--- src/db/models/organization.rs | 4 +- 3 files changed, 151 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cf9e3fac27..80bf7d8d92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -137,6 +137,8 @@ backtrace = "0.3.60" # Macro ident concatenation paste = "1.0.5" +openidconnect = "2.0.1" +urlencoding = "1.1.1" [patch.crates-io] # Use newest ring diff --git a/src/api/identity.rs b/src/api/identity.rs index 7a13cdfd88..ba34a0baee 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -3,10 +3,12 @@ use num_traits::FromPrimitive; use rocket::{ http::{RawStr, Status}, request::{Form, FormItems, FromForm}, + response::Redirect, Route, }; use rocket_contrib::json::Json; use serde_json::Value; +use std::iter::FromIterator; use crate::{ api::{ @@ -44,6 +46,13 @@ fn login(data: Form, conn: DbConn, ip: ClientIp) -> JsonResult { _password_login(data, conn, &ip) } + "authorization_code" => { + _check_is_some(&data.code, "code cannot be blank")?; + _check_is_some(&data.org_identifier, "org_identifier cannot be blank")?; + _check_is_some(&data.device_identifier, "device identifier cannot be blank")?; + + _authorization_login(data, conn) + } t => err!("Invalid type", t), } } @@ -78,6 +87,32 @@ fn _refresh_login(data: ConnectData, conn: DbConn) -> JsonResult { }))) } +fn _authorization_login(data: ConnectData, conn: DbConn) -> JsonResult { + let (access_token, refresh_token) = get_auth_code_access_token(data.code.unwrap(), data.org_identifier.unwrap(), &conn); + // let expiry = jsonwebtoken::decode_header(access_token.as_str()).unwrap(); + let time_now = std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH).unwrap().as_secs(); + + let mut device = Device::find_by_uuid(&data.device_identifier.unwrap(), &conn).map_res("device not found")?; + + // COMMON + let user = User::find_by_uuid(&device.user_uuid, &conn).unwrap(); + + Ok(Json(json!({ + "access_token": access_token, + "expires_in": 1000000, + "token_type": "Bearer", + "refresh_token": device.refresh_token, + "Key": user.akey, + "PrivateKey": user.private_key, + + "Kdf": user.client_kdf_type, + "KdfIterations": user.client_kdf_iter, + "ResetMasterPassword": false, // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing + "scope": "api offline_access", + "unofficialServer": true, + }))) +} + fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult { // Validate scope let scope = data.scope.as_ref().unwrap(); @@ -393,6 +428,10 @@ struct ConnectData { two_factor_provider: Option, two_factor_token: Option, two_factor_remember: Option, + + // Needed for authorization code + code: Option, + org_identifier: Option, } impl<'f> FromForm<'f> for ConnectData { @@ -419,6 +458,8 @@ impl<'f> FromForm<'f> for ConnectData { "twofactorprovider" => form.two_factor_provider = value.parse().ok(), "twofactortoken" => form.two_factor_token = Some(value), "twofactorremember" => form.two_factor_remember = value.parse().ok(), + "code" => form.code = Some(value), + "orgidentifier" => form.org_identifier = Some(value), key => warn!("Detected unexpected parameter during login: {}", key), } } @@ -465,25 +506,119 @@ fn prevalidate(domainHint: &RawStr, conn: DbConn) -> JsonResult { Ok(Json(empty_result)) } +use openidconnect::core::{ + CoreProviderMetadata, CoreClient, + CoreResponseType, +}; +use openidconnect::reqwest::http_client; +use openidconnect::{ + AuthenticationFlow, AuthorizationCode, ClientId, ClientSecret, + CsrfToken, IssuerUrl, Nonce, RedirectUrl, + Scope, OAuth2TokenResponse, +}; + +fn handle_error(fail: &T, msg: &'static str) { + let mut err_msg = format!("ERROR: {}", msg); + let mut cur_fail: Option<&dyn std::error::Error> = Some(fail); + while let Some(cause) = cur_fail { + err_msg += &format!("\n caused by: {}", cause); + cur_fail = cause.source(); + } + panic!("{}", err_msg); +} + +fn get_client_from_identifier (identifier: &str, conn: &DbConn) -> CoreClient { + let organization = Organization::find_by_identifier(identifier, conn); -#[get("/connect/authorize?")] -fn authorize( - domain_hint: &RawStr, - conn: DbConn, -) { - let empty_result = json!({}); - let organization = Organization::find_by_identifier(domain_hint.as_str(), &conn); match organization { Some(organization) => { println!("found org. authority: {}", organization.authority); - let redirect = Some(organization.callback_path.to_string()); + let redirect = organization.callback_path.to_string(); let issuer = reqwest::Url::parse(&organization.authority).unwrap(); println!("got issuer: {}", issuer); - // return Ok(Json(empty_result)); + let client_id = ClientId::new(organization.client_id); + let client_secret = ClientSecret::new(organization.client_secret); + let issuer_url = IssuerUrl::new(organization.authority).expect("invalid issuer URL"); + let provider_metadata = CoreProviderMetadata::discover(&issuer_url, http_client) + .unwrap_or_else(|err| { + handle_error(&err, "Failed to discover OpenID Provider"); + unreachable!(); + }); + let client = CoreClient::from_provider_metadata( + provider_metadata, + client_id, + Some(client_secret), + ) + .set_redirect_uri(RedirectUrl::new(redirect).expect("Invalid redirect URL")); + return client; }, None => { - println!("error"); - // return invalid_json("No Organization found", false); - } + panic!("unable to find org"); + }, } } + +#[get("/connect/authorize?&")] +fn authorize( + domain_hint: &RawStr, + state: &RawStr, + conn: DbConn, +) -> Redirect { + let empty_result = json!({}); + let client = get_client_from_identifier(domain_hint.as_str(), &conn); + + let (mut authorize_url, csrf_state, _nonce) = client + .authorize_url( + AuthenticationFlow::::AuthorizationCode, + CsrfToken::new_random, + Nonce::new_random, + ) + .add_scope(Scope::new("email".to_string())) + .add_scope(Scope::new("profile".to_string())) + .url(); + + // it seems impossible to set the state going in dynamically (requires static lifetime string) + // so I change it after the fact (will it work? Let's find out) + let old_pairs = authorize_url.query_pairs().clone(); + let new_pairs = old_pairs.map(|pair| { + let (key, value) = pair; + if key == "state" { + return format!("{}={}", key, state); + } + return format!("{}={}", key, value); + }); + let full_query = Vec::from_iter(new_pairs).join("&"); + authorize_url.set_query(Some(full_query.as_str())); + + // return Redirect::to(rocket::uri!(&authorize_url.to_string())); + return Redirect::to(authorize_url.to_string()); + // return Ok(Json(empty_result)); +} + +fn get_auth_code_access_token ( + code: String, + org_identifier: String, + conn: &DbConn, +) -> (String, String) { + let oidc_code = AuthorizationCode::new(code); + + println!("code: {}", oidc_code.secret()); + println!("identifier: {}", org_identifier); + + let client = get_client_from_identifier(&org_identifier, conn); + + let token_response = client + .exchange_code(oidc_code) + .request(http_client) + .unwrap_or_else(|err| { + handle_error(&err, "Failed to contact token endpoint"); + unreachable!(); + }); + + + let access_token = token_response.access_token().secret().to_string(); + let refresh_token = token_response.refresh_token().unwrap().secret().to_string(); + println!("access token: {}, refresh token: {}", access_token, refresh_token); + + (access_token, refresh_token) +} diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index 538fdfbffb..c9b4bfa893 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -142,8 +142,8 @@ impl Organization { public_key, identifier: String::from(""), use_sso: false, - callback_path: String::from("http://localhost/oidc-signin"), - signed_out_callback_path: String::from("http://localhost/sso/oidc-signin"), + callback_path: String::from("http://localhost/#/sso/"), + signed_out_callback_path: String::from("http://localhost/#/sso/"), authority: String::from(""), client_id: String::from(""), client_secret: String::from(""), From 284d2155c0778981e74112c08663c669c6157a83 Mon Sep 17 00:00:00 2001 From: Stuart Heap Date: Wed, 1 Sep 2021 15:42:06 +0200 Subject: [PATCH 03/21] policy enforcement - multiple devices --- Cargo.lock | 967 +++++++++++++++--------------------- src/api/identity.rs | 81 ++- src/db/models/org_policy.rs | 4 +- 3 files changed, 468 insertions(+), 584 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cafd7f7bfb..68da363292 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,47 +55,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "async-stream" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" -dependencies = [ - "async-stream-impl", - "futures-core", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async-trait" -version = "0.1.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "atomic" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3410529e8288c463bedb5930f82833bc0c90e5d2fe639a56582a4d09220b281" -dependencies = [ - "autocfg", -] - [[package]] name = "atty" version = "0.2.14" @@ -134,6 +93,16 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" +[[package]] +name = "base64" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" +dependencies = [ + "byteorder", + "safemem", +] + [[package]] name = "base64" version = "0.11.0" @@ -158,21 +127,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" -[[package]] -name = "biscuit" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dee631cea28b00e115fd355a1adedc860b155096941dc01259969eabd434a37" -dependencies = [ - "chrono", - "data-encoding", - "num", - "once_cell", - "ring", - "serde", - "serde_json", -] - [[package]] name = "bitflags" version = "1.2.1" @@ -364,7 +318,7 @@ checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" dependencies = [ "percent-encoding 2.1.0", "time 0.2.27", - "version_check", + "version_check 0.9.3", ] [[package]] @@ -375,7 +329,7 @@ checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d" dependencies = [ "percent-encoding 2.1.0", "time 0.2.27", - "version_check", + "version_check 0.9.3", ] [[package]] @@ -386,7 +340,7 @@ checksum = "3818dfca4b0cb5211a659bbcbb94225b7127407b2b135e650d717bfb78ab10d3" dependencies = [ "cookie 0.14.4", "idna 0.2.3", - "log", + "log 0.4.14", "publicsuffix 1.5.6", "serde", "serde_json", @@ -402,7 +356,7 @@ checksum = "55b4ac5559dd39f7bdc516f769cb412b151585d8886d216871a8435ed7f862cd" dependencies = [ "cookie 0.15.1", "idna 0.2.3", - "log", + "log 0.4.14", "publicsuffix 2.1.0", "serde", "serde_json", @@ -435,6 +389,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.2.1" @@ -491,9 +454,8 @@ dependencies = [ [[package]] name = "devise" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c7580b072f1c8476148f16e0a0d5dedddab787da98d86c5082c5e9ed8ab595" +version = "0.3.0" +source = "git+https://github.com/SergioBenitez/Devise.git?rev=e58b3ac9a#e58b3ac9afc3b6ff10a8aaf02a3e768a8f530089" dependencies = [ "devise_codegen", "devise_core", @@ -501,25 +463,22 @@ dependencies = [ [[package]] name = "devise_codegen" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123c73e7a6e51b05c75fe1a1b2f4e241399ea5740ed810b0e3e6cacd9db5e7b2" +version = "0.3.0" +source = "git+https://github.com/SergioBenitez/Devise.git?rev=e58b3ac9a#e58b3ac9afc3b6ff10a8aaf02a3e768a8f530089" dependencies = [ "devise_core", - "quote", + "quote 1.0.9", ] [[package]] name = "devise_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841ef46f4787d9097405cac4e70fb8644fc037b526e8c14054247c0263c400d0" +version = "0.3.0" +source = "git+https://github.com/SergioBenitez/Devise.git?rev=e58b3ac9a#e58b3ac9afc3b6ff10a8aaf02a3e768a8f530089" dependencies = [ "bitflags", - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn", + "proc-macro2 1.0.28", + "quote 1.0.9", + "syn 1.0.74", ] [[package]] @@ -545,9 +504,9 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.28", + "quote 1.0.9", + "syn 1.0.74", ] [[package]] @@ -654,24 +613,10 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9a4820f0ccc8a7afd67c39a0f1a0f4b07ca1725164271a64939d7aeb9af065" dependencies = [ - "log", + "log 0.4.14", "syslog", ] -[[package]] -name = "figment" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790b4292c72618abbab50f787a477014fe15634f96291de45672ce46afe122df" -dependencies = [ - "atomic", - "pear", - "serde", - "toml", - "uncased", - "version_check", -] - [[package]] name = "flate2" version = "1.0.20" @@ -809,9 +754,9 @@ checksum = "c54913bae956fb8df7f4dc6fc90362aa72e69148e3f39041fbe8742d21e0ac57" dependencies = [ "autocfg", "proc-macro-hack", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.28", + "quote 1.0.9", + "syn 1.0.74", ] [[package]] @@ -847,19 +792,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generator" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1d9279ca822891c1a4dae06d185612cf8fc6acfe5dff37781b41297811b12ee" -dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "winapi 0.3.9", -] - [[package]] name = "generic-array" version = "0.7.3" @@ -886,7 +818,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" dependencies = [ "typenum", - "version_check", + "version_check 0.9.3", ] [[package]] @@ -907,8 +839,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if 1.0.0", + "js-sys", "libc", "wasi 0.10.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -954,7 +888,7 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72a0ffab8c36d0436114310c7e10b59b3307e650ddfabf6d006028e29a70c6e6" dependencies = [ - "log", + "log 0.4.14", "pest", "pest_derive", "quick-error 2.0.1", @@ -1016,12 +950,12 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b" dependencies = [ - "log", + "log 0.4.14", "mac", "markup5ever", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.28", + "quote 1.0.9", + "syn 1.0.74", ] [[package]] @@ -1058,6 +992,25 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" +[[package]] +name = "hyper" +version = "0.10.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" +dependencies = [ + "base64 0.9.3", + "httparse", + "language-tags", + "log 0.3.9", + "mime 0.2.6", + "num_cpus", + "time 0.1.44", + "traitobject", + "typeable", + "unicase 1.4.2", + "url 1.7.2", +] + [[package]] name = "hyper" version = "0.14.11" @@ -1082,6 +1035,33 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" +dependencies = [ + "futures-util", + "hyper 0.14.11", + "log 0.4.14", + "rustls 0.19.1", + "tokio", + "tokio-rustls", + "webpki", +] + +[[package]] +name = "hyper-sync-rustls" +version = "0.3.0-rc.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cb014c4ea00486e2b62860b5e15229d37516d4924177218beafbf46583de3ab" +dependencies = [ + "hyper 0.10.16", + "rustls 0.17.0", + "webpki", + "webpki-roots 0.19.0", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -1089,7 +1069,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes 1.0.1", - "hyper", + "hyper 0.14.11", "native-tls", "tokio", "tokio-native-tls", @@ -1117,12 +1097,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "if_chain" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" - [[package]] name = "indexmap" version = "1.7.0" @@ -1131,15 +1105,8 @@ checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" dependencies = [ "autocfg", "hashbrown", - "serde", ] -[[package]] -name = "inlinable_string" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3094308123a0e9fd59659ce45e22de9f53fc1d2ac6e1feb9fef988e4f76cad77" - [[package]] name = "instant" version = "0.1.10" @@ -1164,6 +1131,15 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.7" @@ -1213,6 +1189,12 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "language-tags" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" + [[package]] name = "lazy_static" version = "1.4.0" @@ -1236,7 +1218,7 @@ dependencies = [ "hostname", "httpdate", "idna 0.2.3", - "mime", + "mime 0.3.16", "native-tls", "nom 6.1.2", "once_cell", @@ -1248,9 +1230,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.98" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" +checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" [[package]] name = "libsqlite3-sys" @@ -1274,24 +1256,20 @@ dependencies = [ [[package]] name = "log" -version = "0.4.14" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" dependencies = [ - "cfg-if 1.0.0", + "log 0.4.14", ] [[package]] -name = "loom" -version = "0.5.1" +name = "log" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2111607c723d7857e0d8299d5ce7a0bf4b844d3e44f8de136b13da513eaf8fc4" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ "cfg-if 1.0.0", - "generator", - "scoped-tls", - "serde", - "serde_json", ] [[package]] @@ -1312,7 +1290,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" dependencies = [ - "log", + "log 0.4.14", "phf", "phf_codegen", "string_cache", @@ -1372,9 +1350,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9753f12909fd8d923f75ae5c3258cae1ed3c8ec052e1b38c93c21a6d157f789c" dependencies = [ "migrations_internals", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.28", + "quote 1.0.9", + "syn 1.0.74", +] + +[[package]] +name = "mime" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" +dependencies = [ + "log 0.3.9", ] [[package]] @@ -1389,8 +1376,8 @@ version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" dependencies = [ - "mime", - "unicase", + "mime 0.3.16", + "unicase 2.6.0", ] [[package]] @@ -1415,7 +1402,7 @@ dependencies = [ "iovec", "kernel32-sys", "libc", - "log", + "log 0.4.14", "miow 0.2.2", "net2", "slab", @@ -1429,7 +1416,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" dependencies = [ "libc", - "log", + "log 0.4.14", "miow 0.3.7", "ntapi", "winapi 0.3.9", @@ -1442,7 +1429,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" dependencies = [ "lazycell", - "log", + "log 0.4.14", "mio 0.6.23", "slab", ] @@ -1468,26 +1455,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "multer" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "408327e2999b839cd1af003fc01b2019a6c10a1361769542203f6fedc5179680" -dependencies = [ - "bytes 1.0.1", - "encoding_rs", - "futures-util", - "http", - "httparse", - "log", - "mime", - "spin 0.9.2", - "tokio", - "tokio-util", - "twoway 0.2.2", - "version_check", -] - [[package]] name = "multipart" version = "0.18.0" @@ -1496,14 +1463,14 @@ checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" dependencies = [ "buf_redux", "httparse", - "log", - "mime", + "log 0.4.14", + "mime 0.3.16", "mime_guess", "quick-error 1.2.3", "rand 0.8.4", "safemem", "tempfile", - "twoway 0.1.8", + "twoway", ] [[package]] @@ -1524,7 +1491,7 @@ checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" dependencies = [ "lazy_static", "libc", - "log", + "log 0.4.14", "openssl", "openssl-probe", "openssl-sys", @@ -1575,7 +1542,7 @@ dependencies = [ "bitvec", "funty", "memchr", - "version_check", + "version_check 0.9.3", ] [[package]] @@ -1587,20 +1554,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "num" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f" -dependencies = [ - "num-bigint 0.3.2", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - [[package]] name = "num-bigint" version = "0.2.6" @@ -1612,35 +1565,15 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-bigint" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d0a3d5e207573f948a9e5376662aa743a2ea13f7c50a554d7af443a73fbfeba" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" -dependencies = [ - "num-traits", -] - [[package]] name = "num-derive" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.28", + "quote 1.0.9", + "syn 1.0.74", ] [[package]] @@ -1653,29 +1586,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" -dependencies = [ - "autocfg", - "num-bigint 0.3.2", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.14" @@ -1705,7 +1615,27 @@ dependencies = [ "hmac 0.1.1", "rustc-hex", "sha-1 0.3.4", - "sha2", + "sha2 0.5.3", +] + +[[package]] +name = "oauth2" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e47cfc4c0a1a519d9a025ebfbac3a2439d1b5cdf397d72dcb79b11d9920dab" +dependencies = [ + "base64 0.13.0", + "chrono", + "getrandom 0.2.3", + "http", + "rand 0.8.4", + "reqwest", + "serde", + "serde_json", + "serde_path_to_error", + "sha2 0.9.6", + "thiserror", + "url 2.2.2", ] [[package]] @@ -1736,21 +1666,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] -name = "openid" -version = "0.9.3" +name = "openidconnect" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab30a9456b3484c408d9708b6f65b2bd834fdf22b73567775e1ca6de5524dd19" +checksum = "a42ff51db0f23ae55dd6f234a15ed7bb468bc97938670693a3eaa42869110167" dependencies = [ - "base64 0.13.0", - "biscuit", + "base64 0.12.3", "chrono", - "lazy_static", - "reqwest", + "http", + "itertools", + "log 0.4.14", + "oauth2", + "rand 0.7.3", + "ring", "serde", + "serde-value", + "serde_derive", "serde_json", + "serde_path_to_error", "thiserror", + "untrusted", "url 2.2.2", - "validator", ] [[package]] @@ -1796,6 +1732,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "ordered-float" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3305af35278dd29f46fcdd139e0b1fbfae2153f0e5928b39b035542dd31e37b7" +dependencies = [ + "num-traits", +] + [[package]] name = "owning_ref" version = "0.3.3" @@ -1814,7 +1759,7 @@ dependencies = [ "byteorder", "bytes 0.4.12", "httparse", - "log", + "log 0.4.14", "mio 0.6.23", "mio-extras", "rand 0.7.3", @@ -1887,25 +1832,24 @@ checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58" [[package]] name = "pear" -version = "0.2.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e44241c5e4c868e3eaa78b7c1848cadd6344ed4f54d029832d32b415a58702" +checksum = "5320f212db967792b67cfe12bd469d08afd6318a249bd917d5c19bc92200ab8a" dependencies = [ - "inlinable_string", "pear_codegen", - "yansi", ] [[package]] name = "pear_codegen" -version = "0.2.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0" +checksum = "bfc1c836fdc3d1ef87c348b237b5b5c4dff922156fb2d968f57734f9669768ca" dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn", + "proc-macro2 0.4.30", + "quote 0.6.13", + "syn 0.15.44", + "version_check 0.9.3", + "yansi", ] [[package]] @@ -1958,9 +1902,9 @@ checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" dependencies = [ "pest", "pest_meta", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.28", + "quote 1.0.9", + "syn 1.0.74", ] [[package]] @@ -2057,30 +2001,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -2095,24 +2015,20 @@ checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" [[package]] name = "proc-macro2" -version = "1.0.28" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" dependencies = [ - "unicode-xid", + "unicode-xid 0.1.0", ] [[package]] -name = "proc-macro2-diagnostics" -version = "0.9.1" +name = "proc-macro2" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" +checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" dependencies = [ - "proc-macro2", - "quote", - "syn", - "version_check", - "yansi", + "unicode-xid 0.2.2", ] [[package]] @@ -2155,13 +2071,22 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +dependencies = [ + "proc-macro2 0.4.30", +] + [[package]] name = "quote" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ - "proc-macro2", + "proc-macro2 1.0.28", ] [[package]] @@ -2176,7 +2101,7 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "545c5bc2b880973c9c10e4067418407a0ccaa3091781d1671d46eb35107cb26f" dependencies = [ - "log", + "log 0.4.14", "parking_lot 0.11.1", "scheduled-thread-pool", ] @@ -2324,26 +2249,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "ref-cast" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "regex" version = "1.5.4" @@ -2386,28 +2291,32 @@ dependencies = [ "futures-util", "http", "http-body", - "hyper", + "hyper 0.14.11", + "hyper-rustls", "hyper-tls", "ipnet", "js-sys", "lazy_static", - "log", - "mime", + "log 0.4.14", + "mime 0.3.16", "native-tls", "percent-encoding 2.1.0", "pin-project-lite", + "rustls 0.19.1", "serde", "serde_json", "serde_urlencoded", "time 0.2.27", "tokio", "tokio-native-tls", + "tokio-rustls", "tokio-socks", "tokio-util", "url 2.2.2", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots 0.21.1", "winreg", ] @@ -2420,7 +2329,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin 0.5.2", + "spin", "untrusted", "web-sys", "winapi 0.3.9", @@ -2448,86 +2357,65 @@ dependencies = [ [[package]] name = "rocket" -version = "0.5.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a71c18c42a0eb15bf3816831caf0dad11e7966f2a41aaf486a701979c4dd1f2" +version = "0.5.0-dev" +source = "git+https://github.com/SergioBenitez/Rocket?rev=263e39b5b429de1913ce7e3036575a7b4d88b6d7#263e39b5b429de1913ce7e3036575a7b4d88b6d7" dependencies = [ - "async-stream", - "async-trait", - "atomic", "atty", "binascii", - "bytes 1.0.1", - "either", - "figment", - "futures", - "indexmap", - "log", + "log 0.4.14", "memchr", - "multer", "num_cpus", - "parking_lot 0.11.1", - "pin-project-lite", - "rand 0.8.4", - "ref-cast", + "pear", "rocket_codegen", "rocket_http", - "serde", - "serde_json", "state", - "tempfile", "time 0.2.27", - "tokio", - "tokio-stream", - "tokio-util", - "ubyte", - "version_check", + "toml", + "version_check 0.9.3", "yansi", ] [[package]] name = "rocket_codegen" -version = "0.5.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66f5fa462f7eb958bba8710c17c5d774bbbd59809fa76fb1957af7e545aea8bb" +version = "0.5.0-dev" +source = "git+https://github.com/SergioBenitez/Rocket?rev=263e39b5b429de1913ce7e3036575a7b4d88b6d7#263e39b5b429de1913ce7e3036575a7b4d88b6d7" dependencies = [ "devise", "glob", "indexmap", - "proc-macro2", - "quote", + "quote 1.0.9", "rocket_http", - "syn", - "unicode-xid", + "version_check 0.9.3", + "yansi", +] + +[[package]] +name = "rocket_contrib" +version = "0.5.0-dev" +source = "git+https://github.com/SergioBenitez/Rocket?rev=263e39b5b429de1913ce7e3036575a7b4d88b6d7#263e39b5b429de1913ce7e3036575a7b4d88b6d7" +dependencies = [ + "log 0.4.14", + "rocket", + "serde", + "serde_json", ] [[package]] name = "rocket_http" -version = "0.5.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c8b7d512d2fcac2316ebe590cde67573844b99e6cc9ee0f53375fa16e25ebd" +version = "0.5.0-dev" +source = "git+https://github.com/SergioBenitez/Rocket?rev=263e39b5b429de1913ce7e3036575a7b4d88b6d7#263e39b5b429de1913ce7e3036575a7b4d88b6d7" dependencies = [ - "cookie 0.15.1", - "either", - "http", - "hyper", + "cookie 0.14.4", + "hyper 0.10.16", + "hyper-sync-rustls", "indexmap", - "log", - "memchr", - "mime", - "parking_lot 0.11.1", "pear", - "percent-encoding 2.1.0", - "pin-project-lite", - "ref-cast", - "serde", + "percent-encoding 1.0.1", + "rustls 0.17.0", "smallvec 1.6.1", - "stable-pattern", "state", "time 0.2.27", - "tokio", - "tokio-rustls", - "uncased", + "unicode-xid 0.2.2", ] [[package]] @@ -2553,22 +2441,29 @@ dependencies = [ [[package]] name = "rustls" -version = "0.19.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +checksum = "c0d4a31f5d68413404705d6982529b0e11a9aacd4839d1d6222ee3b8cb4015e1" dependencies = [ - "base64 0.13.0", - "log", + "base64 0.11.0", + "log 0.4.14", "ring", "sct", "webpki", ] [[package]] -name = "rustversion" -version = "1.0.5" +name = "rustls" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64 0.13.0", + "log 0.4.14", + "ring", + "sct", + "webpki", +] [[package]] name = "ryu" @@ -2610,12 +2505,6 @@ dependencies = [ "parking_lot 0.11.1", ] -[[package]] -name = "scoped-tls" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" - [[package]] name = "scopeguard" version = "1.1.0" @@ -2679,6 +2568,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-value" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a65a7291a8a568adcae4c10a677ebcedbc6c9cec91c054dee2ce40b0e3290eb" +dependencies = [ + "ordered-float", + "serde", +] + [[package]] name = "serde_bytes" version = "0.11.5" @@ -2704,9 +2603,9 @@ version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.28", + "quote 1.0.9", + "syn 1.0.74", ] [[package]] @@ -2715,12 +2614,20 @@ version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" dependencies = [ - "indexmap", "itoa", "ryu", "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f6109f0506e20f7e0f910e51a0079acf41da8e0694e6442527c4ddf5a2b158" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.0" @@ -2766,7 +2673,7 @@ checksum = "1a0c8611594e2ab4ebbf06ec7cbbf0a99450b8570e96cbf5188b5d5f6ef18d81" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures", + "cpufeatures 0.1.5", "digest 0.9.0", "opaque-debug 0.3.0", ] @@ -2791,12 +2698,16 @@ dependencies = [ ] [[package]] -name = "signal-hook-registry" -version = "1.4.0" +name = "sha2" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "9204c41a1597a8c5af23c82d1c921cb01ec0a4c59e07a9c7306062829a3903f3" dependencies = [ - "libc", + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures 0.2.1", + "digest 0.9.0", + "opaque-debug 0.3.0", ] [[package]] @@ -2806,7 +2717,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b" dependencies = [ "chrono", - "num-bigint 0.2.6", + "num-bigint", "num-traits", ] @@ -2853,21 +2764,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "spin" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" - -[[package]] -name = "stable-pattern" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" -dependencies = [ - "memchr", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2880,17 +2776,14 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" dependencies = [ - "version_check", + "version_check 0.9.3", ] [[package]] name = "state" -version = "0.5.2" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cf4f5369e6d3044b5e365c9690f451516ac8f0954084622b49ea3fde2f6de5" -dependencies = [ - "loom", -] +checksum = "3015a7d0a5fd5105c91c3710d42f9ccf0abfb287d62206484dcc67f9569a6483" [[package]] name = "stdweb" @@ -2912,11 +2805,11 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.28", + "quote 1.0.9", "serde", "serde_derive", - "syn", + "syn 1.0.74", ] [[package]] @@ -2926,13 +2819,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" dependencies = [ "base-x", - "proc-macro2", - "quote", + "proc-macro2 1.0.28", + "quote 1.0.9", "serde", "serde_derive", "serde_json", "sha1", - "syn", + "syn 1.0.74", ] [[package]] @@ -2962,8 +2855,8 @@ checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97" dependencies = [ "phf_generator", "phf_shared", - "proc-macro2", - "quote", + "proc-macro2 1.0.28", + "quote 1.0.9", ] [[package]] @@ -2972,15 +2865,26 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +[[package]] +name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "unicode-xid 0.1.0", +] + [[package]] name = "syn" version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", + "proc-macro2 1.0.28", + "quote 1.0.9", + "unicode-xid 0.2.2", ] [[package]] @@ -2991,7 +2895,7 @@ checksum = "a0641142b4081d3d44beffa4eefd7346a228cdf91ed70186db2ca2cef762d327" dependencies = [ "error-chain", "libc", - "log", + "log 0.4.14", "time 0.1.44", ] @@ -3041,9 +2945,9 @@ version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.28", + "quote 1.0.9", + "syn 1.0.74", ] [[package]] @@ -3077,7 +2981,7 @@ dependencies = [ "standback", "stdweb", "time-macros", - "version_check", + "version_check 0.9.3", "winapi 0.3.9", ] @@ -3098,10 +3002,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" dependencies = [ "proc-macro-hack", - "proc-macro2", - "quote", + "proc-macro2 1.0.28", + "quote 1.0.9", "standback", - "syn", + "syn 1.0.74", ] [[package]] @@ -3131,24 +3035,10 @@ dependencies = [ "memchr", "mio 0.7.13", "num_cpus", - "once_cell", "pin-project-lite", - "signal-hook-registry", - "tokio-macros", "winapi 0.3.9", ] -[[package]] -name = "tokio-macros" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tokio-native-tls" version = "0.3.0" @@ -3165,7 +3055,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ - "rustls", + "rustls 0.19.1", "tokio", "webpki", ] @@ -3182,17 +3072,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-stream" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-util" version = "0.6.7" @@ -3202,16 +3081,16 @@ dependencies = [ "bytes 1.0.1", "futures-core", "futures-sink", - "log", + "log 0.4.14", "pin-project-lite", "tokio", ] [[package]] name = "toml" -version = "0.5.8" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" dependencies = [ "serde", ] @@ -3229,7 +3108,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d" dependencies = [ "cfg-if 1.0.0", - "log", + "log 0.4.14", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3241,9 +3120,9 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.28", + "quote 1.0.9", + "syn 1.0.74", ] [[package]] @@ -3255,6 +3134,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "traitobject" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" + [[package]] name = "try-lock" version = "0.2.3" @@ -3271,14 +3156,10 @@ dependencies = [ ] [[package]] -name = "twoway" -version = "0.2.2" +name = "typeable" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c57ffb460d7c24cd6eda43694110189030a3d1dfe418416d9468fd1c1d290b47" -dependencies = [ - "memchr", - "unchecked-index", -] +checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" [[package]] name = "typenum" @@ -3303,15 +3184,6 @@ dependencies = [ "time 0.1.44", ] -[[package]] -name = "ubyte" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42756bb9e708855de2f8a98195643dff31a97f0485d90d8467b39dc24be9e8fe" -dependencies = [ - "serde", -] - [[package]] name = "ucd-trie" version = "0.1.3" @@ -3319,28 +3191,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] -name = "uncased" -version = "0.9.6" +name = "unicase" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" +checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" dependencies = [ - "serde", - "version_check", + "version_check 0.1.5", ] -[[package]] -name = "unchecked-index" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c" - [[package]] name = "unicase" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" dependencies = [ - "version_check", + "version_check 0.9.3", ] [[package]] @@ -3361,6 +3226,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + [[package]] name = "unicode-xid" version = "0.2.2" @@ -3397,6 +3268,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a1f0175e03a0973cf4afd476bef05c26e228520400eb1fd473ad417b1c00ffb" + [[package]] name = "utf-8" version = "0.7.6" @@ -3412,45 +3289,6 @@ dependencies = [ "getrandom 0.2.3", ] -[[package]] -name = "validator" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d6937c33ec6039d8071bcf72933146b5bbe378d645d8fa59bdadabfc2a249" -dependencies = [ - "idna 0.2.3", - "lazy_static", - "regex", - "serde", - "serde_derive", - "serde_json", - "url 2.2.2", - "validator_derive", - "validator_types", -] - -[[package]] -name = "validator_derive" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286b4497f270f59276a89ae0ad109d5f8f18c69b613e3fb22b61201aadb0c4d" -dependencies = [ - "if_chain", - "lazy_static", - "proc-macro-error", - "proc-macro2", - "quote", - "regex", - "syn", - "validator_types", -] - -[[package]] -name = "validator_types" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad9680608df133af2c1ddd5eaf1ddce91d60d61b6bc51494ef326458365a470a" - [[package]] name = "vaultwarden" version = "1.0.0" @@ -3468,7 +3306,6 @@ dependencies = [ "diesel_migrations", "dotenv", "fern", - "futures", "handlebars", "html5ever", "idna 0.2.3", @@ -3476,14 +3313,14 @@ dependencies = [ "jsonwebtoken", "lettre", "libsqlite3-sys", - "log", + "log 0.4.14", "markup5ever_rcdom", "multipart", "num-derive", "num-traits", "oath", "once_cell", - "openid", + "openidconnect", "openssl", "parity-ws", "paste", @@ -3495,6 +3332,7 @@ dependencies = [ "ring", "rmpv", "rocket", + "rocket_contrib", "serde", "serde_json", "syslog", @@ -3502,6 +3340,7 @@ dependencies = [ "tracing", "u2f", "url 2.2.2", + "urlencoding", "uuid", "webauthn-rs", "yubico", @@ -3513,6 +3352,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + [[package]] name = "version_check" version = "0.9.3" @@ -3536,7 +3381,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "log", + "log 0.4.14", "try-lock", ] @@ -3572,10 +3417,10 @@ checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" dependencies = [ "bumpalo", "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", + "log 0.4.14", + "proc-macro2 1.0.28", + "quote 1.0.9", + "syn 1.0.74", "wasm-bindgen-shared", ] @@ -3597,7 +3442,7 @@ version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" dependencies = [ - "quote", + "quote 1.0.9", "wasm-bindgen-macro-support", ] @@ -3607,9 +3452,9 @@ version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.28", + "quote 1.0.9", + "syn 1.0.74", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3637,7 +3482,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bbb2b77105c3b25ef0187146d80824648da0645f650c4d2080e3815d6cbbb87" dependencies = [ "base64 0.13.0", - "log", + "log 0.4.14", "nom 4.1.1", "openssl", "rand 0.8.4", @@ -3659,6 +3504,24 @@ dependencies = [ "untrusted", ] +[[package]] +name = "webpki-roots" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eff4b7516a57307f9349c64bf34caa34b940b66fed4b2fb3136cb7386e5739" +dependencies = [ + "webpki", +] + +[[package]] +name = "webpki-roots" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +dependencies = [ + "webpki", +] + [[package]] name = "winapi" version = "0.2.8" @@ -3733,7 +3596,7 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b1b52e6e8614d4a58b8e70cf51ec0cc21b256ad8206708bcff8139b5bbd6a59" dependencies = [ - "log", + "log 0.4.14", "mac", "markup5ever", "time 0.1.44", @@ -3761,13 +3624,3 @@ dependencies = [ "threadpool", "url 1.7.2", ] - -# [[patch.unused]] -# name = "rocket" -# version = "0.5.0-dev" -# source = "git+https://github.com/SergioBenitez/Rocket?rev=263e39b5b429de1913ce7e3036575a7b4d88b6d7#263e39b5b429de1913ce7e3036575a7b4d88b6d7" - -# [[patch.unused]] -# name = "rocket_contrib" -# version = "0.5.0-dev" -# source = "git+https://github.com/SergioBenitez/Rocket?rev=263e39b5b429de1913ce7e3036575a7b4d88b6d7#263e39b5b429de1913ce7e3036575a7b4d88b6d7" diff --git a/src/api/identity.rs b/src/api/identity.rs index ba34a0baee..3d6f3fef65 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -51,7 +51,7 @@ fn login(data: Form, conn: DbConn, ip: ClientIp) -> JsonResult { _check_is_some(&data.org_identifier, "org_identifier cannot be blank")?; _check_is_some(&data.device_identifier, "device identifier cannot be blank")?; - _authorization_login(data, conn) + _authorization_login(data, conn, &ip) } t => err!("Invalid type", t), } @@ -87,21 +87,46 @@ fn _refresh_login(data: ConnectData, conn: DbConn) -> JsonResult { }))) } -fn _authorization_login(data: ConnectData, conn: DbConn) -> JsonResult { - let (access_token, refresh_token) = get_auth_code_access_token(data.code.unwrap(), data.org_identifier.unwrap(), &conn); - // let expiry = jsonwebtoken::decode_header(access_token.as_str()).unwrap(); - let time_now = std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH).unwrap().as_secs(); +#[derive(Debug, Serialize, Deserialize)] +struct TokenPayload { + exp: i64, + email: String, +} - let mut device = Device::find_by_uuid(&data.device_identifier.unwrap(), &conn).map_res("device not found")?; +fn _authorization_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult { + let org_identifier = data.org_identifier.as_ref().unwrap(); + let code = data.code.as_ref().unwrap(); + let (access_token, refresh_token) = get_auth_code_access_token(&code, &org_identifier, &conn); + let token = jsonwebtoken::dangerous_insecure_decode::(access_token.as_str()).unwrap().claims; + let expiry = token.exp; + let user_email = token.email; + let now = Local::now(); // COMMON - let user = User::find_by_uuid(&device.user_uuid, &conn).unwrap(); + let user = User::find_by_mail(&user_email, &conn).unwrap(); - Ok(Json(json!({ + let (mut device, new_device) = get_device(&data, &conn, &user); + + let twofactor_token = twofactor_auth(&user.uuid, &data, &mut device, ip, &conn)?; + + if CONFIG.mail_enabled() && new_device { + if let Err(e) = mail::send_new_device_logged_in(&user.email, &ip.ip.to_string(), &now, &device.name) { + error!("Error sending new device email: {:#?}", e); + + if CONFIG.require_device_email() { + err!("Could not send login notification email. Please contact your administrator.") + } + } + } + + device.refresh_token = refresh_token.clone(); + device.save(&conn)?; + + let mut result = json!({ "access_token": access_token, - "expires_in": 1000000, + "expires_in": expiry - now.naive_utc().timestamp(), "token_type": "Bearer", - "refresh_token": device.refresh_token, + "refresh_token": refresh_token, "Key": user.akey, "PrivateKey": user.private_key, @@ -110,7 +135,13 @@ fn _authorization_login(data: ConnectData, conn: DbConn) -> JsonResult { "ResetMasterPassword": false, // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing "scope": "api offline_access", "unofficialServer": true, - }))) + }); + + if let Some(token) = twofactor_token { + result["TwoFactorToken"] = Value::String(token); + } + + Ok(Json(result)) } fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult { @@ -138,6 +169,15 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> JsonResult err!("This user has been disabled", format!("IP: {}. Username: {}.", ip.ip, username)) } + // Check if org policy prevents password login + let user_orgs = UserOrganization::find_by_user_and_policy(&user.uuid, OrgPolicyType::RequireSso, &conn); + if user_orgs.len() == 1 && user_orgs[0].atype >= 2 { + // if requires SSO is active, user is in exactly one org by policy rules + // policy only applies to "non-owner/non-admin" members + + err!("Organization policy requires SSO sign in"); + } + let now = Local::now(); if user.verified_at.is_none() && CONFIG.mail_enabled() && CONFIG.signups_verify() { @@ -532,10 +572,8 @@ fn get_client_from_identifier (identifier: &str, conn: &DbConn) -> CoreClient { match organization { Some(organization) => { - println!("found org. authority: {}", organization.authority); let redirect = organization.callback_path.to_string(); let issuer = reqwest::Url::parse(&organization.authority).unwrap(); - println!("got issuer: {}", issuer); let client_id = ClientId::new(organization.client_id); let client_secret = ClientSecret::new(organization.client_secret); let issuer_url = IssuerUrl::new(organization.authority).expect("invalid issuer URL"); @@ -564,10 +602,9 @@ fn authorize( state: &RawStr, conn: DbConn, ) -> Redirect { - let empty_result = json!({}); let client = get_client_from_identifier(domain_hint.as_str(), &conn); - let (mut authorize_url, csrf_state, _nonce) = client + let (mut authorize_url, _csrf_state, _nonce) = client .authorize_url( AuthenticationFlow::::AuthorizationCode, CsrfToken::new_random, @@ -590,22 +627,17 @@ fn authorize( let full_query = Vec::from_iter(new_pairs).join("&"); authorize_url.set_query(Some(full_query.as_str())); - // return Redirect::to(rocket::uri!(&authorize_url.to_string())); return Redirect::to(authorize_url.to_string()); - // return Ok(Json(empty_result)); } fn get_auth_code_access_token ( - code: String, - org_identifier: String, + code: &str, + org_identifier: &str, conn: &DbConn, ) -> (String, String) { - let oidc_code = AuthorizationCode::new(code); - - println!("code: {}", oidc_code.secret()); - println!("identifier: {}", org_identifier); + let oidc_code = AuthorizationCode::new(String::from(code)); - let client = get_client_from_identifier(&org_identifier, conn); + let client = get_client_from_identifier(org_identifier, conn); let token_response = client .exchange_code(oidc_code) @@ -618,7 +650,6 @@ fn get_auth_code_access_token ( let access_token = token_response.access_token().secret().to_string(); let refresh_token = token_response.refresh_token().unwrap().secret().to_string(); - println!("access token: {}, refresh token: {}", access_token, refresh_token); (access_token, refresh_token) } diff --git a/src/db/models/org_policy.rs b/src/db/models/org_policy.rs index 34eaedb1fc..c669ada3ab 100644 --- a/src/db/models/org_policy.rs +++ b/src/db/models/org_policy.rs @@ -27,8 +27,8 @@ pub enum OrgPolicyType { TwoFactorAuthentication = 0, MasterPassword = 1, PasswordGenerator = 2, - // SingleOrg = 3, // Not currently supported. - // RequireSso = 4, // Not currently supported. + SingleOrg = 3, + RequireSso = 4, PersonalOwnership = 5, DisableSend = 6, SendOptions = 7, From d0d476f8effc1067748de653e431131f265acf0f Mon Sep 17 00:00:00 2001 From: Stuart Heap Date: Wed, 1 Sep 2021 16:28:01 +0200 Subject: [PATCH 04/21] cleanup --- .../2018-02-17-205753_create_collections_and_orgs/up.sql | 4 +--- .../postgresql/2019-09-12-100000_create_tables/up.sql | 4 +--- .../2018-02-17-205753_create_collections_and_orgs/up.sql | 4 +--- src/api/core/organizations.rs | 4 ---- src/api/identity.rs | 1 - src/db/models/organization.rs | 6 ------ src/db/schemas/mysql/schema.rs | 2 -- src/db/schemas/postgresql/schema.rs | 2 -- src/db/schemas/sqlite/schema.rs | 2 -- 9 files changed, 3 insertions(+), 26 deletions(-) diff --git a/migrations/mysql/2018-02-17-205753_create_collections_and_orgs/up.sql b/migrations/mysql/2018-02-17-205753_create_collections_and_orgs/up.sql index 5cf2ec101a..e1c20b31d1 100644 --- a/migrations/mysql/2018-02-17-205753_create_collections_and_orgs/up.sql +++ b/migrations/mysql/2018-02-17-205753_create_collections_and_orgs/up.sql @@ -14,9 +14,7 @@ CREATE TABLE organizations ( signed_out_callback_path TEXT NOT NULL, authority TEXT NOT NULL, client_id TEXT NOT NULL, - client_secret TEXT NOT NULL, - metadata_address TEXT NOT NULL, - oidc_redirect_behavior TEXT NOT NULL + client_secret TEXT NOT NULL ); CREATE TABLE users_collections ( diff --git a/migrations/postgresql/2019-09-12-100000_create_tables/up.sql b/migrations/postgresql/2019-09-12-100000_create_tables/up.sql index 384669efff..74f492f191 100644 --- a/migrations/postgresql/2019-09-12-100000_create_tables/up.sql +++ b/migrations/postgresql/2019-09-12-100000_create_tables/up.sql @@ -42,9 +42,7 @@ CREATE TABLE organizations ( signed_out_callback_path TEXT NOT NULL, authority TEXT NOT NULL, client_id TEXT NOT NULL, - client_secret TEXT NOT NULL, - metadata_address TEXT NOT NULL, - oidc_redirect_behavior TEXT NOT NULL + client_secret TEXT NOT NULL ); CREATE TABLE ciphers ( diff --git a/migrations/sqlite/2018-02-17-205753_create_collections_and_orgs/up.sql b/migrations/sqlite/2018-02-17-205753_create_collections_and_orgs/up.sql index 92391417fe..7a8c835f42 100644 --- a/migrations/sqlite/2018-02-17-205753_create_collections_and_orgs/up.sql +++ b/migrations/sqlite/2018-02-17-205753_create_collections_and_orgs/up.sql @@ -14,9 +14,7 @@ CREATE TABLE organizations ( signed_out_callback_path TEXT NOT NULL, authority TEXT NOT NULL, client_id TEXT NOT NULL, - client_secret TEXT NOT NULL, - metadata_address TEXT NOT NULL, - oidc_redirect_behavior TEXT NOT NULL + client_secret TEXT NOT NULL ); diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 774f806771..bc09d38ce0 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -85,8 +85,6 @@ struct OrganizationSsoUpdateData { Authority: String, ClientId: String, ClientSecret: String, - MetadataAddress: String, - OidcRedirectBehavior: String, } #[derive(Deserialize, Debug)] @@ -244,8 +242,6 @@ fn put_organization_sso( org.authority = data.Authority; org.client_id = data.ClientId; org.client_secret = data.ClientSecret; - org.metadata_address = data.MetadataAddress; - org.oidc_redirect_behavior = data.OidcRedirectBehavior; org.save(&conn)?; Ok(Json(org.to_json())) diff --git a/src/api/identity.rs b/src/api/identity.rs index 3d6f3fef65..8f0a2ea7e2 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -573,7 +573,6 @@ fn get_client_from_identifier (identifier: &str, conn: &DbConn) -> CoreClient { match organization { Some(organization) => { let redirect = organization.callback_path.to_string(); - let issuer = reqwest::Url::parse(&organization.authority).unwrap(); let client_id = ClientId::new(organization.client_id); let client_secret = ClientSecret::new(organization.client_secret); let issuer_url = IssuerUrl::new(organization.authority).expect("invalid issuer URL"); diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs index c9b4bfa893..20a19c232a 100644 --- a/src/db/models/organization.rs +++ b/src/db/models/organization.rs @@ -21,8 +21,6 @@ db_object! { pub authority: String, pub client_id: String, pub client_secret: String, - pub metadata_address: String, - pub oidc_redirect_behavior: String, } #[derive(Identifiable, Queryable, Insertable, AsChangeset)] @@ -147,8 +145,6 @@ impl Organization { authority: String::from(""), client_id: String::from(""), client_secret: String::from(""), - metadata_address: String::from(""), - oidc_redirect_behavior: String::from(""), } } @@ -189,8 +185,6 @@ impl Organization { "Authority": self.authority, "ClientId": self.client_id, "ClientSecret": self.client_secret, - "MetadataAddress": self.metadata_address, - "OidcRedirectBehavior": self.oidc_redirect_behavior, }) } } diff --git a/src/db/schemas/mysql/schema.rs b/src/db/schemas/mysql/schema.rs index 32bbc44122..f5caaa8443 100644 --- a/src/db/schemas/mysql/schema.rs +++ b/src/db/schemas/mysql/schema.rs @@ -109,8 +109,6 @@ table! { authority -> Text, client_id -> Text, client_secret -> Text, - metadata_address -> Text, - oidc_redirect_behavior -> Text, } } diff --git a/src/db/schemas/postgresql/schema.rs b/src/db/schemas/postgresql/schema.rs index 5da8c2eb6d..560399b89e 100644 --- a/src/db/schemas/postgresql/schema.rs +++ b/src/db/schemas/postgresql/schema.rs @@ -109,8 +109,6 @@ table! { authority -> Text, client_id -> Text, client_secret -> Text, - metadata_address -> Text, - oidc_redirect_behavior -> Text, } } diff --git a/src/db/schemas/sqlite/schema.rs b/src/db/schemas/sqlite/schema.rs index 5da8c2eb6d..560399b89e 100644 --- a/src/db/schemas/sqlite/schema.rs +++ b/src/db/schemas/sqlite/schema.rs @@ -109,8 +109,6 @@ table! { authority -> Text, client_id -> Text, client_secret -> Text, - metadata_address -> Text, - oidc_redirect_behavior -> Text, } } From d0d261a3468c97e7500b8b3aa956ea99ad18e9d2 Mon Sep 17 00:00:00 2001 From: Stuart Heap Date: Wed, 1 Sep 2021 16:48:51 +0200 Subject: [PATCH 05/21] safe handling of RawStrs --- src/api/identity.rs | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/api/identity.rs b/src/api/identity.rs index 8f0a2ea7e2..f7bc54df7e 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -525,25 +525,30 @@ fn invalid_json(error_message: &str, exception: bool) -> JsonResult { #[allow(non_snake_case)] fn prevalidate(domainHint: &RawStr, conn: DbConn) -> JsonResult { let empty_result = json!({}); + match domainHint.percent_decode() { + Ok(domain_hint) => { + let organization = Organization::find_by_identifier(&domain_hint.to_owned(), &conn); + match organization { + Some(organization) => { + if !organization.use_sso { + return invalid_json("SSO Not allowed for organization", false); + } + }, + None => { + return invalid_json("Organization not found by identifier", false); + }, + } - // TODO as_str shouldn't be used here - let organization = Organization::find_by_identifier(domainHint.as_str(), &conn); - match organization { - Some(organization) => { - if !organization.use_sso { - return invalid_json("SSO Not allowed for organization", false); + if domainHint == "" { + return invalid_json("No Organization Identifier Provided", false); } + + Ok(Json(empty_result)) }, - None => { - return invalid_json("Organization not found by identifier", false); + Err(_) => { + return invalid_json("Invalid domainHint received", false); }, } - - if domainHint == "" { - return invalid_json("No Organization Identifier Provided", false); - } - - Ok(Json(empty_result)) } use openidconnect::core::{ @@ -601,7 +606,9 @@ fn authorize( state: &RawStr, conn: DbConn, ) -> Redirect { - let client = get_client_from_identifier(domain_hint.as_str(), &conn); + let domain_hint_decoded = &domain_hint.percent_decode().expect("Invalid domain_hint").into_owned(); + let state_decoded = &state.percent_decode().expect("Invalid state").into_owned(); + let client = get_client_from_identifier(domain_hint_decoded, &conn); let (mut authorize_url, _csrf_state, _nonce) = client .authorize_url( @@ -619,7 +626,7 @@ fn authorize( let new_pairs = old_pairs.map(|pair| { let (key, value) = pair; if key == "state" { - return format!("{}={}", key, state); + return format!("{}={}", key, state_decoded); } return format!("{}={}", key, value); }); From 05a4a6c4a8c9cdd11bbff2266010a1fc32a226db Mon Sep 17 00:00:00 2001 From: Stuart Heap Date: Wed, 1 Sep 2021 17:12:05 +0200 Subject: [PATCH 06/21] comment updates --- src/api/identity.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/api/identity.rs b/src/api/identity.rs index f7bc54df7e..f36b362b4a 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -610,6 +610,8 @@ fn authorize( let state_decoded = &state.percent_decode().expect("Invalid state").into_owned(); let client = get_client_from_identifier(domain_hint_decoded, &conn); + // TODO store the nonce for validation on authorization token exchange - unclear where to store + // this let (mut authorize_url, _csrf_state, _nonce) = client .authorize_url( AuthenticationFlow::::AuthorizationCode, @@ -621,7 +623,7 @@ fn authorize( .url(); // it seems impossible to set the state going in dynamically (requires static lifetime string) - // so I change it after the fact (will it work? Let's find out) + // so I change it after the fact let old_pairs = authorize_url.query_pairs().clone(); let new_pairs = old_pairs.map(|pair| { let (key, value) = pair; From 7f97e7f8dd36fa0c33d9773a831a0c446dcd3fe9 Mon Sep 17 00:00:00 2001 From: Stuart Heap Date: Wed, 1 Sep 2021 17:34:56 +0200 Subject: [PATCH 07/21] add web-vault-sso.patch --- web-vault-sso.patch | 570 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 570 insertions(+) create mode 100644 web-vault-sso.patch diff --git a/web-vault-sso.patch b/web-vault-sso.patch new file mode 100644 index 0000000000..dde307ad5f --- /dev/null +++ b/web-vault-sso.patch @@ -0,0 +1,570 @@ +Submodule jslib contains modified content +diff --git a/jslib/angular/src/components/register.component.ts b/jslib/angular/src/components/register.component.ts +index 53ec3c8..7b49db1 100644 +--- a/jslib/angular/src/components/register.component.ts ++++ b/jslib/angular/src/components/register.component.ts +@@ -24,7 +24,7 @@ export class RegisterComponent { + formPromise: Promise; + masterPasswordScore: number; + referenceData: ReferenceEventRequest; +- showTerms = true; ++ showTerms = false; + acceptPolicies: boolean = false; + + protected successRoute = 'login'; +@@ -35,7 +35,7 @@ export class RegisterComponent { + protected apiService: ApiService, protected stateService: StateService, + protected platformUtilsService: PlatformUtilsService, + protected passwordGenerationService: PasswordGenerationService) { +- this.showTerms = !platformUtilsService.isSelfHost(); ++ this.showTerms = false; + } + + get masterPasswordScoreWidth() { +@@ -69,6 +69,12 @@ export class RegisterComponent { + } + + async submit() { ++ if (typeof crypto.subtle === 'undefined') { ++ this.platformUtilsService.showToast('error', "This browser requires HTTPS to use the web vault", ++ "Check the Vaultwarden wiki for details on how to enable it"); ++ return; ++ } ++ + if (!this.acceptPolicies && this.showTerms) { + this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), + this.i18nService.t('acceptPoliciesError')); +diff --git a/jslib/angular/src/components/sso.component.ts b/jslib/angular/src/components/sso.component.ts +index d4512a1..ad57f69 100644 +--- a/jslib/angular/src/components/sso.component.ts ++++ b/jslib/angular/src/components/sso.component.ts +@@ -19,6 +19,8 @@ import { Utils } from 'jslib-common/misc/utils'; + + import { AuthResult } from 'jslib-common/models/domain/authResult'; + ++import { switchMap } from 'rxjs/operators'; ++ + @Directive() + export class SsoComponent { + identifier: string; +@@ -48,13 +50,19 @@ export class SsoComponent { + + async ngOnInit() { + const queryParamsSub = this.route.queryParams.subscribe(async qParams => { +- if (qParams.code != null && qParams.state != null) { ++ // I have no idea why the qParams is empty here - I've hacked in an alternative very messily, but it works. ++ const workingParams = (new URL(window.location.href)).searchParams; ++ const workingSwap = { ++ code: workingParams.get('code'), ++ state: workingParams.get('state'), ++ }; ++ if (workingSwap.code != null && workingSwap.state != null) { + const codeVerifier = await this.storageService.get(ConstantsService.ssoCodeVerifierKey); + const state = await this.storageService.get(ConstantsService.ssoStateKey); + await this.storageService.remove(ConstantsService.ssoCodeVerifierKey); + await this.storageService.remove(ConstantsService.ssoStateKey); +- if (qParams.code != null && codeVerifier != null && state != null && this.checkState(state, qParams.state)) { +- await this.logIn(qParams.code, codeVerifier, this.getOrgIdentiferFromState(qParams.state)); ++ if (workingSwap.code != null && codeVerifier != null && state != null && this.checkState(state, workingSwap.state)) { ++ await this.logIn(workingSwap.code, codeVerifier, this.getOrgIdentiferFromState(workingSwap.state)); + } + } else if (qParams.clientId != null && qParams.redirectUri != null && qParams.state != null && + qParams.codeChallenge != null) { +@@ -122,7 +130,7 @@ export class SsoComponent { + let authorizeUrl = this.apiService.identityBaseUrl + '/connect/authorize?' + + 'client_id=' + this.clientId + '&redirect_uri=' + encodeURIComponent(this.redirectUri) + '&' + + 'response_type=code&scope=api offline_access&' + +- 'state=' + state + '&code_challenge=' + codeChallenge + '&' + ++ 'state=' + encodeURIComponent(state) + '&code_challenge=' + codeChallenge + '&' + + 'code_challenge_method=S256&response_mode=query&' + + 'domain_hint=' + encodeURIComponent(this.identifier); + +@@ -137,7 +145,7 @@ export class SsoComponent { + private async logIn(code: string, codeVerifier: string, orgIdFromState: string) { + this.loggingIn = true; + try { +- this.formPromise = this.authService.logInSso(code, codeVerifier, this.redirectUri); ++ this.formPromise = this.authService.logInSso(code, codeVerifier, this.redirectUri, orgIdFromState); + const response = await this.formPromise; + if (response.twoFactor) { + if (this.onSuccessfulLoginTwoFactorNavigate != null) { +diff --git a/jslib/common/src/abstractions/api.service.ts b/jslib/common/src/abstractions/api.service.ts +index 67131df..e75a874 100644 +--- a/jslib/common/src/abstractions/api.service.ts ++++ b/jslib/common/src/abstractions/api.service.ts +@@ -33,6 +33,7 @@ import { KeysRequest } from '../models/request/keysRequest'; + import { OrganizationCreateRequest } from '../models/request/organizationCreateRequest'; + import { OrganizationImportRequest } from '../models/request/organizationImportRequest'; + import { OrganizationKeysRequest } from '../models/request/organizationKeysRequest'; ++import { OrganizationSsoUpdateRequest } from '../models/request/organizationSsoUpdateRequest'; + import { OrganizationTaxInfoUpdateRequest } from '../models/request/organizationTaxInfoUpdateRequest'; + import { OrganizationUpdateRequest } from '../models/request/organizationUpdateRequest'; + import { OrganizationUpgradeRequest } from '../models/request/organizationUpgradeRequest'; +@@ -360,6 +361,7 @@ export abstract class ApiService { + getOrganizationTaxInfo: (id: string) => Promise; + postOrganization: (request: OrganizationCreateRequest) => Promise; + putOrganization: (id: string, request: OrganizationUpdateRequest) => Promise; ++ putOrganizationSso: (id: string, request: OrganizationSsoUpdateRequest) => Promise; + putOrganizationTaxInfo: (id: string, request: OrganizationTaxInfoUpdateRequest) => Promise; + postLeaveOrganization: (id: string) => Promise; + postOrganizationLicense: (data: FormData) => Promise; +diff --git a/jslib/common/src/abstractions/auth.service.ts b/jslib/common/src/abstractions/auth.service.ts +index ac7ef04..5b1b774 100644 +--- a/jslib/common/src/abstractions/auth.service.ts ++++ b/jslib/common/src/abstractions/auth.service.ts +@@ -15,7 +15,7 @@ export abstract class AuthService { + selectedTwoFactorProviderType: TwoFactorProviderType; + + logIn: (email: string, masterPassword: string) => Promise; +- logInSso: (code: string, codeVerifier: string, redirectUrl: string) => Promise; ++ logInSso: (code: string, codeVerifier: string, redirectUrl: string, orgIdentifier: string) => Promise; + logInApiKey: (clientId: string, clientSecret: string) => Promise; + logInTwoFactor: (twoFactorProvider: TwoFactorProviderType, twoFactorToken: string, + remember?: boolean) => Promise; +diff --git a/jslib/common/src/models/request/tokenRequest.ts b/jslib/common/src/models/request/tokenRequest.ts +index 7578012..964364f 100644 +--- a/jslib/common/src/models/request/tokenRequest.ts ++++ b/jslib/common/src/models/request/tokenRequest.ts +@@ -14,9 +14,10 @@ export class TokenRequest { + provider: TwoFactorProviderType; + remember: boolean; + device?: DeviceRequest; ++ orgIdentifier?: string; + + constructor(credentials: string[], codes: string[], clientIdClientSecret: string[], provider: TwoFactorProviderType, +- token: string, remember: boolean, device?: DeviceRequest) { ++ token: string, remember: boolean, device?: DeviceRequest, orgIdentifier?: string) { + if (credentials != null && credentials.length > 1) { + this.email = credentials[0]; + this.masterPasswordHash = credentials[1]; +@@ -28,6 +29,9 @@ export class TokenRequest { + this.clientId = clientIdClientSecret[0]; + this.clientSecret = clientIdClientSecret[1]; + } ++ if (orgIdentifier && orgIdentifier !== '') { ++ this.orgIdentifier = orgIdentifier; ++ } + this.token = token; + this.provider = provider; + this.remember = remember; +@@ -53,6 +57,7 @@ export class TokenRequest { + obj.code = this.code; + obj.code_verifier = this.codeVerifier; + obj.redirect_uri = this.redirectUri; ++ obj.org_identifier = this.orgIdentifier; + } else { + throw new Error('must provide credentials or codes'); + } +diff --git a/jslib/common/src/models/response/organizationResponse.ts b/jslib/common/src/models/response/organizationResponse.ts +index 21d8d43..896b7a6 100644 +--- a/jslib/common/src/models/response/organizationResponse.ts ++++ b/jslib/common/src/models/response/organizationResponse.ts +@@ -27,6 +27,12 @@ export class OrganizationResponse extends BaseResponse { + useApi: boolean; + useResetPassword: boolean; + hasPublicAndPrivateKeys: boolean; ++ useSso: boolean; ++ callbackPath: string; ++ signedOutCallbackPath: string; ++ authority: string; ++ clientId: string; ++ clientSecret: string; + + constructor(response: any) { + super(response); +@@ -54,5 +60,11 @@ export class OrganizationResponse extends BaseResponse { + this.useApi = this.getResponseProperty('UseApi'); + this.useResetPassword = this.getResponseProperty('UseResetPassword'); + this.hasPublicAndPrivateKeys = this.getResponseProperty('HasPublicAndPrivateKeys'); ++ this.useSso = this.getResponseProperty('UseSso'); ++ this.callbackPath = this.getResponseProperty('CallbackPath'); ++ this.signedOutCallbackPath = this.getResponseProperty('SignedOutCallbackPath'); ++ this.authority = this.getResponseProperty('Authority'); ++ this.clientId = this.getResponseProperty('ClientId'); ++ this.clientSecret = this.getResponseProperty('ClientSecret'); + } + } +diff --git a/jslib/common/src/services/api.service.ts b/jslib/common/src/services/api.service.ts +index 51c1c14..1a5b088 100644 +--- a/jslib/common/src/services/api.service.ts ++++ b/jslib/common/src/services/api.service.ts +@@ -37,6 +37,7 @@ import { KeysRequest } from '../models/request/keysRequest'; + import { OrganizationCreateRequest } from '../models/request/organizationCreateRequest'; + import { OrganizationImportRequest } from '../models/request/organizationImportRequest'; + import { OrganizationKeysRequest } from '../models/request/organizationKeysRequest'; ++import { OrganizationSsoUpdateRequest } from '../models/request/organizationSsoUpdateRequest'; + import { OrganizationTaxInfoUpdateRequest } from '../models/request/organizationTaxInfoUpdateRequest'; + import { OrganizationUpdateRequest } from '../models/request/organizationUpdateRequest'; + import { OrganizationUpgradeRequest } from '../models/request/organizationUpgradeRequest'; +@@ -1158,6 +1159,11 @@ export class ApiService implements ApiServiceAbstraction { + return new OrganizationResponse(r); + } + ++ async putOrganizationSso(id: string, request: OrganizationSsoUpdateRequest): Promise { ++ const r = await this.send('PUT', '/organizations/' + id + '/sso', request, true, false); ++ return new OrganizationResponse(r); ++ } ++ + async putOrganizationTaxInfo(id: string, request: OrganizationTaxInfoUpdateRequest): Promise { + return this.send('PUT', '/organizations/' + id + '/tax', request, true, false); + } +diff --git a/jslib/common/src/services/auth.service.ts b/jslib/common/src/services/auth.service.ts +index 6536a94..6f4899c 100644 +--- a/jslib/common/src/services/auth.service.ts ++++ b/jslib/common/src/services/auth.service.ts +@@ -130,10 +130,10 @@ export class AuthService implements AuthServiceAbstraction { + key, null, null, null); + } + +- async logInSso(code: string, codeVerifier: string, redirectUrl: string): Promise { ++ async logInSso(code: string, codeVerifier: string, redirectUrl: string, orgIdentifier: string): Promise { + this.selectedTwoFactorProviderType = null; + return await this.logInHelper(null, null, null, code, codeVerifier, redirectUrl, null, null, +- null, null, null, null); ++ null, null, null, null, orgIdentifier); + } + + async logInApiKey(clientId: string, clientSecret: string): Promise { +@@ -272,7 +272,7 @@ export class AuthService implements AuthServiceAbstraction { + + private async logInHelper(email: string, hashedPassword: string, localHashedPassword: string, code: string, + codeVerifier: string, redirectUrl: string, clientId: string, clientSecret: string, key: SymmetricCryptoKey, +- twoFactorProvider?: TwoFactorProviderType, twoFactorToken?: string, remember?: boolean): Promise { ++ twoFactorProvider?: TwoFactorProviderType, twoFactorToken?: string, remember?: boolean, orgIdentifier?: string): Promise { + const storedTwoFactorToken = await this.tokenService.getTwoFactorToken(email); + const appId = await this.appIdService.getAppId(); + const deviceRequest = new DeviceRequest(appId, this.platformUtilsService); +@@ -300,13 +300,13 @@ export class AuthService implements AuthServiceAbstraction { + let request: TokenRequest; + if (twoFactorToken != null && twoFactorProvider != null) { + request = new TokenRequest(emailPassword, codeCodeVerifier, clientIdClientSecret, twoFactorProvider, +- twoFactorToken, remember, deviceRequest); ++ twoFactorToken, remember, deviceRequest, orgIdentifier); + } else if (storedTwoFactorToken != null) { + request = new TokenRequest(emailPassword, codeCodeVerifier, clientIdClientSecret, TwoFactorProviderType.Remember, +- storedTwoFactorToken, false, deviceRequest); ++ storedTwoFactorToken, false, deviceRequest, orgIdentifier); + } else { + request = new TokenRequest(emailPassword, codeCodeVerifier, clientIdClientSecret, null, +- null, false, deviceRequest); ++ null, false, deviceRequest, orgIdentifier); + } + + const response = await this.apiService.postIdentityToken(request); +diff --git a/src/404.html b/src/404.html +index eba36375..cb8883ec 100644 +--- a/src/404.html ++++ b/src/404.html +@@ -41,10 +41,10 @@ + +

+

You can return to the web vault, check our status page +- or contact us.

++ or contact us.

+ + + + +diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts +index bee8416f..dad32467 100644 +--- a/src/app/app-routing.module.ts ++++ b/src/app/app-routing.module.ts +@@ -33,6 +33,7 @@ import { AccountComponent as OrgAccountComponent } from './organizations/setting + import { OrganizationBillingComponent } from './organizations/settings/organization-billing.component'; + import { OrganizationSubscriptionComponent } from './organizations/settings/organization-subscription.component'; + import { SettingsComponent as OrgSettingsComponent } from './organizations/settings/settings.component'; ++import { SsoComponent as OrgSsoComponent } from './organizations/settings/sso.component'; + import { + TwoFactorSetupComponent as OrgTwoFactorSetupComponent, + } from './organizations/settings/two-factor-setup.component'; +@@ -412,6 +413,7 @@ const routes: Routes = [ + children: [ + { path: '', pathMatch: 'full', redirectTo: 'account' }, + { path: 'account', component: OrgAccountComponent, data: { titleId: 'myOrganization' } }, ++ { path: 'sso', component: OrgSsoComponent, data: { titleId: 'sso' } }, + { path: 'two-factor', component: OrgTwoFactorSetupComponent, data: { titleId: 'twoStepLogin' } }, + { + path: 'billing', +diff --git a/src/app/app.component.ts b/src/app/app.component.ts +index 2922cf09..8f2be1ad 100644 +--- a/src/app/app.component.ts ++++ b/src/app/app.component.ts +@@ -146,6 +146,10 @@ export class AppComponent implements OnDestroy, OnInit { + } + break; + case 'showToast': ++ if (typeof message.text === "string" && typeof crypto.subtle === 'undefined') { ++ message.title="This browser requires HTTPS to use the web vault"; ++ message.text="Check the Vaultwarden wiki for details on how to enable it"; ++ } + this.showToast(message); + break; + case 'setFullWidth': +diff --git a/src/app/app.module.ts b/src/app/app.module.ts +index bafc22a0..938db7ee 100644 +--- a/src/app/app.module.ts ++++ b/src/app/app.module.ts +@@ -67,6 +67,7 @@ import { DownloadLicenseComponent } from './organizations/settings/download-lice + import { OrganizationBillingComponent } from './organizations/settings/organization-billing.component'; + import { OrganizationSubscriptionComponent } from './organizations/settings/organization-subscription.component'; + import { SettingsComponent as OrgSettingComponent } from './organizations/settings/settings.component'; ++import { SsoComponent as OrgSsoComponent } from './organizations/settings/sso.component'; + import { + TwoFactorSetupComponent as OrgTwoFactorSetupComponent, + } from './organizations/settings/two-factor-setup.component'; +@@ -347,6 +348,7 @@ registerLocaleData(localeZhTw, 'zh-TW'); + NavbarComponent, + OptionsComponent, + OrgAccountComponent, ++ OrgSsoComponent, + OrgAddEditComponent, + OrganizationBillingComponent, + OrganizationPlansComponent, +diff --git a/src/app/layouts/footer.component.html b/src/app/layouts/footer.component.html +index b001b9e3..c1bd2ac8 100644 +--- a/src/app/layouts/footer.component.html ++++ b/src/app/layouts/footer.component.html +@@ -1,7 +1,7 @@ + --
-+ -
-
-
- -- -+ -
-

OpenId Connect Configuration

-
- -- -
-
- - -+ [(ngModel)]="ssoConfig.signedOutCallbackPath" [disabled]="selfHosted"> -
-
- - -+ [(ngModel)]="ssoConfig.authority" [disabled]="selfHosted"> -
-
- - -+ [(ngModel)]="ssoConfig.clientId" [disabled]="selfHosted"> -
-
- - -+ [(ngModel)]="ssoConfig.clientSecret" [disabled]="selfHosted"> -
-
-
-@@ -45,7 +45,7 @@ - {{'save' | i18n}} - -
--
-+
- - {{'loading' | i18n}} -
-diff --git a/src/app/organizations/settings/sso.component.ts b/src/app/organizations/settings/sso.component.ts -index f40a54f2..5eeef132 100644 ---- a/src/app/organizations/settings/sso.component.ts -+++ b/src/app/organizations/settings/sso.component.ts -@@ -17,7 +17,7 @@ import { SyncService } from 'jslib-common/abstractions/sync.service'; - - import { OrganizationSsoUpdateRequest } from 'jslib-common/models/request/organizationSsoUpdateRequest'; - --import { OrganizationResponse } from 'jslib-common/models/response/organizationResponse'; -+import { SsoConfigResponse } from 'jslib-common/models/response/ssoConfigResponse'; +diff --git a/src/app/organizations/vault/vault.component.ts b/src/app/organizations/vault/vault.component.ts +index 715453fd..b7c2a7b2 100644 +--- a/src/app/organizations/vault/vault.component.ts ++++ b/src/app/organizations/vault/vault.component.ts +@@ -63,9 +63,7 @@ export class VaultComponent implements OnInit, OnDestroy { + private platformUtilsService: PlatformUtilsService) { } - import { ModalComponent } from '../../modal.component'; + ngOnInit() { +- this.trashCleanupWarning = this.i18nService.t( +- this.platformUtilsService.isSelfHost() ? 'trashCleanupWarningSelfHosted' : 'trashCleanupWarning' +- ); ++ this.trashCleanupWarning = this.i18nService.t('trashCleanupWarningSelfHosted'); -@@ -28,7 +28,7 @@ import { ModalComponent } from '../../modal.component'; - export class SsoComponent { - selfHosted = false; - loading = true; -- org: OrganizationResponse; -+ ssoConfig: SsoConfigResponse; - formPromise: Promise; - - private organizationId: string; -@@ -45,7 +45,7 @@ export class SsoComponent { - this.route.parent.parent.params.subscribe(async params => { - this.organizationId = params.organizationId; - try { -- this.org = await this.apiService.getOrganization(this.organizationId); -+ this.ssoConfig = await this.apiService.getSsoConfig(this.organizationId); - } catch { } - }); - this.loading = false; -@@ -54,12 +54,12 @@ export class SsoComponent { - async submit() { - try { - const request = new OrganizationSsoUpdateRequest(); -- request.useSso = this.org.useSso; -- request.callbackPath = this.org.callbackPath; -- request.signedOutCallbackPath = this.org.signedOutCallbackPath; -- request.authority = this.org.authority; -- request.clientId = this.org.clientId; -- request.clientSecret = this.org.clientSecret; -+ request.useSso = this.ssoConfig.useSso; -+ request.callbackPath = this.ssoConfig.callbackPath; -+ request.signedOutCallbackPath = this.ssoConfig.signedOutCallbackPath; -+ request.authority = this.ssoConfig.authority; -+ request.clientId = this.ssoConfig.clientId; -+ request.clientSecret = this.ssoConfig.clientSecret; - - this.formPromise = this.apiService.putOrganizationSso(this.organizationId, request).then(() => { - return this.syncService.fullSync(true); + this.route.parent.params.pipe(first()).subscribe(async params => { + this.organization = await this.userService.getOrganization(params.organizationId); +diff --git a/src/app/oss-routing.module.ts b/src/app/oss-routing.module.ts +index 84a056e4..88f631c2 100644 +--- a/src/app/oss-routing.module.ts ++++ b/src/app/oss-routing.module.ts +@@ -35,6 +35,7 @@ import { AccountComponent as OrgAccountComponent } from './organizations/setting + import { OrganizationBillingComponent } from './organizations/settings/organization-billing.component'; + import { OrganizationSubscriptionComponent } from './organizations/settings/organization-subscription.component'; + import { SettingsComponent as OrgSettingsComponent } from './organizations/settings/settings.component'; ++import { SsoComponent as OrgSsoComponent } from './organizations/settings/sso.component'; + import { + TwoFactorSetupComponent as OrgTwoFactorSetupComponent, + } from './organizations/settings/two-factor-setup.component'; +@@ -443,6 +444,8 @@ const routes: Routes = [ + children: [ + { path: '', pathMatch: 'full', redirectTo: 'account' }, + { path: 'account', component: OrgAccountComponent, data: { titleId: 'myOrganization' } }, ++ // TODO the diff from older version was misleading here ++ { path: 'sso', component: OrgSsoComponent, data: { titleId: 'sso' } }, + { path: 'two-factor', component: OrgTwoFactorSetupComponent, data: { titleId: 'twoStepLogin' } }, + { + path: 'billing', +diff --git a/src/app/oss.module.ts b/src/app/oss.module.ts +index 88790771..e3915e88 100644 +--- a/src/app/oss.module.ts ++++ b/src/app/oss.module.ts +@@ -67,6 +67,7 @@ import { DownloadLicenseComponent } from './organizations/settings/download-lice + import { OrganizationBillingComponent } from './organizations/settings/organization-billing.component'; + import { OrganizationSubscriptionComponent } from './organizations/settings/organization-subscription.component'; + import { SettingsComponent as OrgSettingComponent } from './organizations/settings/settings.component'; ++import { SsoComponent as OrgSsoComponent } from './organizations/settings/sso.component'; + import { + TwoFactorSetupComponent as OrgTwoFactorSetupComponent, + } from './organizations/settings/two-factor-setup.component'; +@@ -367,6 +368,7 @@ registerLocaleData(localeZhTw, 'zh-TW'); + NestedCheckboxComponent, + OptionsComponent, + OrgAccountComponent, ++ OrgSsoComponent, + OrgAddEditComponent, + OrganizationBillingComponent, + OrganizationPlansComponent, diff --git a/src/app/send/access.component.html b/src/app/send/access.component.html -index 84944a2b..b736bbe4 100644 +index 84944a2b..107ad359 100644 --- a/src/app/send/access.component.html +++ b/src/app/send/access.component.html +@@ -8,7 +8,7 @@ +
+
+ +- {{'viewSendHiddenEmailWarning' | i18n }} ++ {{'viewSendHiddenEmailWarning' | i18n }} + {{'learnMore' | i18n}}. + +
@@ -82,10 +82,7 @@

{{'sendAccessTaglineProductDesc' | i18n}}
@@ -515,15 +103855,15 @@ index 84944a2b..b736bbe4 100644 - {{'sendAccessTaglineOr' | i18n}} {{'sendAccessTaglineSignUp' | i18n}} - {{'sendAccessTaglineTryToday' | i18n}} -+ href="https://www.bitwarden.com/products/send/" target="_blank">Bitwarden Send. ++ href="https://www.bitwarden.com/products/send/" target="_blank">Bitwarden Send.

diff --git a/src/app/services/services.module.ts b/src/app/services/services.module.ts -index 231edc51..2fb39433 100644 +index 9064202e..5a6bc9de 100644 --- a/src/app/services/services.module.ts +++ b/src/app/services/services.module.ts -@@ -142,18 +142,25 @@ const passwordRepromptService = new PasswordRepromptService(i18nService, cryptoS +@@ -155,12 +155,23 @@ const userVerificationService = new UserVerificationService(cryptoService, i18nS containerService.attachToWindow(window); export function initFactory(): Function { @@ -543,25 +103883,18 @@ index 231edc51..2fb39433 100644 return async () => { await (storageService as HtmlStorageService).init(); -- if (process.env.ENV !== 'production' || platformUtilsService.isSelfHost()) { -- environmentService.baseUrl = window.location.origin; -- } else { -- environmentService.notificationsUrl = 'https://notifications.bitwarden.com'; -- environmentService.enterpriseUrl = 'https://portal.bitwarden.com'; -- } -- -+ environmentService.baseUrl = getBaseUrl(); - apiService.setUrls({ -- base: window.location.origin, -+ base: environmentService.baseUrl, - api: null, - identity: null, - events: null, +- const urls = process.env.URLS as Urls; +- urls.base ??= window.location.origin; +- environmentService.setUrls(urls, false); ++ environmentService.setUrls({ base: getBaseUrl() }, false); + + setTimeout(() => notificationsService.init(), 3000); + diff --git a/src/app/vault/vault.component.ts b/src/app/vault/vault.component.ts -index 41216ead..70dec887 100644 +index d91211eb..edd2a82d 100644 --- a/src/app/vault/vault.component.ts +++ b/src/app/vault/vault.component.ts -@@ -80,9 +80,7 @@ export class VaultComponent implements OnInit, OnDestroy { +@@ -81,9 +81,7 @@ export class VaultComponent implements OnInit, OnDestroy { async ngOnInit() { this.showVerifyEmail = !(await this.tokenService.getEmailVerified()); this.showBrowserOutdated = window.navigator.userAgent.indexOf('MSIE') !== -1; @@ -570,13 +103903,13 @@ index 41216ead..70dec887 100644 - ); + this.trashCleanupWarning = this.i18nService.t('trashCleanupWarningSelfHosted'); - const queryParamsSub = this.route.queryParams.subscribe(async params => { + this.route.queryParams.pipe(first()).subscribe(async params => { await this.syncService.fullSync(false); diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json -index e680001c..f16bd676 100644 +index 74d61382..f0b8cc2e 100644 --- a/src/locales/en/messages.json +++ b/src/locales/en/messages.json -@@ -3277,6 +3277,9 @@ +@@ -3423,6 +3423,9 @@ "enterpriseSingleSignOn": { "message": "Enterprise Single Sign-On" }, @@ -586,11 +103919,10 @@ index e680001c..f16bd676 100644 "ssoHandOff": { "message": "You may now close this tab and continue in the extension." }, -@@ -3998,5 +4001,20 @@ - }, +@@ -4195,6 +4198,21 @@ "resetPasswordManageUsers": { "message": "Manage Users must also be enabled with the Manage Password Reset permission" -+ }, + }, + "callbackPath": { + "message": "Callback Path" + }, @@ -605,81 +103937,75 @@ index e680001c..f16bd676 100644 + }, + "clientSecret": { + "message": "Client Secret" - } - } ++ }, + "setupProvider": { + "message": "Provider Setup" + }, diff --git a/src/scss/styles.scss b/src/scss/styles.scss -index 598fea83..0b702064 100644 +index 45a91fe1..b6a662a3 100644 --- a/src/scss/styles.scss +++ b/src/scss/styles.scss -@@ -1,5 +1,53 @@ - @import "../css/webfonts.css"; - -+/**** START Bitwarden_RS CHANGES ****/ +@@ -55,3 +55,46 @@ + @import "./plugins"; + @import "./tables"; + @import "./toasts"; ++ ++/**** START Vaultwarden CHANGES ****/ +/* This combines all selectors extending it into one */ -+%bwrs-hide { display: none !important; } ++%vw-hide { display: none !important; } + +/* This allows searching for the combined style in the browsers dev-tools (look into the head tag) */ -+#bwrs-hide, head { @extend %bwrs-hide; } ++#vw-hide, head { @extend %vw-hide; } + +/* Hide any link pointing to billing */ -+a[href$="/settings/billing"] { @extend %bwrs-hide; } ++a[href$="/settings/billing"] { @extend %vw-hide; } + +/* Hide any link pointing to subscriptions */ -+a[href$="/settings/subscription"] { @extend %bwrs-hide; } ++a[href$="/settings/subscription"] { @extend %vw-hide; } + -+/* Hide any link pointing to emergency access */ -+a[href$="/settings/emergency-access"] { @extend %bwrs-hide; } ++/* Hide any link pointing to Sponsored Families */ ++a[href$="/settings/sponsored-families"] { @extend %vw-hide; } + +/* Hide the info box that advertises Bitwarden Send */ -+app-send-info.d-block { @extend %bwrs-hide; } ++app-send-info.d-block { @extend %vw-hide; } + +/* Hide Two-Factor menu in Organization settings */ -+app-org-settings a[href$="/settings/two-factor"] { @extend %bwrs-hide; } ++app-org-settings a[href$="/settings/two-factor"] { @extend %vw-hide; } + +/* Hide organization plans */ -+app-organization-plans > form > div.form-check { @extend %bwrs-hide; } -+app-organization-plans > form > h2.mt-5 { @extend %bwrs-hide; } ++app-organization-plans > form > div.form-check { @extend %vw-hide; } ++app-organization-plans > form > h2.mt-5 { @extend %vw-hide; } + -+/* Hide the `API Key` section under `My Account` */ -+app-account > div:nth-child(9), -+app-account > p, -+app-account > button:nth-child(11), -+app-account > button:nth-child(12) { -+ @extend %bwrs-hide; -+} ++/* Hide the `This account is owned by a business` checkbox and label */ ++#ownedBusiness, label[for^=ownedBusiness] { @extend %vw-hide; } + +/* Hide the radio button and label for the `Custom` org user type */ +#userTypeCustom, label[for^=userTypeCustom] { -+ @extend %bwrs-hide; ++ @extend %vw-hide; +} + +/* Hide the warning that policy config is moving to Business Portal */ -+app-org-policies > app-callout { @extend %bwrs-hide; } ++app-org-policies > app-callout { @extend %vw-hide; } + +/* Hide Tax Info and Form in Organization settings */ -+app-org-account > div.secondary-header:nth-child(3) { @extend %bwrs-hide; } -+app-org-account > div.secondary-header:nth-child(3) + p { @extend %bwrs-hide; } -+app-org-account > div.secondary-header:nth-child(3) + p + form { @extend %bwrs-hide; } -+/**** END Bitwarden_RS CHANGES ****/ -+ - $primary: #175DDC; - $primary-accent: #1252A3; - $secondary: #ced4da; ++app-org-account > div.secondary-header:nth-child(3) { @extend %vw-hide; } ++app-org-account > div.secondary-header:nth-child(3) + p { @extend %vw-hide; } ++app-org-account > div.secondary-header:nth-child(3) + p + form { @extend %vw-hide; } ++/**** END Vaultwarden CHANGES ****/ diff --git a/src/services/webPlatformUtils.service.ts b/src/services/webPlatformUtils.service.ts -index e3aeea39..6e7ed1e0 100644 +index 13f754c0..c40612d8 100644 --- a/src/services/webPlatformUtils.service.ts +++ b/src/services/webPlatformUtils.service.ts -@@ -249,11 +249,12 @@ export class WebPlatformUtilsService implements PlatformUtilsService { +@@ -224,11 +224,11 @@ export class WebPlatformUtilsService implements PlatformUtilsService { } isDev(): boolean { -- return process.env.ENV === 'development'; +- return process.env.NODE_ENV === 'development'; + return false; } -+ // Even though Vaultwarden is self-hosted, returning true ends up enabling various license checks. isSelfHost(): boolean { -- return process.env.SELF_HOST.toString() === 'true'; +- return process.env.ENV.toString() === 'selfhosted'; + return false; } From 4a76119e92b8b93e58fa41358bbcc146f39b616a Mon Sep 17 00:00:00 2001 From: Stuart Heap Date: Thu, 10 Feb 2022 16:14:08 +0100 Subject: [PATCH 20/21] fixed web-vault for v2.25.0 --- web-vault-sso.patch | 104054 +---------------------------------------- 1 file changed, 374 insertions(+), 103680 deletions(-) diff --git a/web-vault-sso.patch b/web-vault-sso.patch index 681223c04f..6a0905f41f 100644 --- a/web-vault-sso.patch +++ b/web-vault-sso.patch @@ -1,103700 +1,247 @@ Submodule jslib contains modified content -Submodule jslib c65e7db6...009f69fc: -diff --git a/jslib/.editorconfig b/jslib/.editorconfig -index 332f3a5d..ec87dc82 100644 ---- a/jslib/.editorconfig -+++ b/jslib/.editorconfig -@@ -7,9 +7,10 @@ root = true - [*] - end_of_line = lf - insert_final_newline = true -+trim_trailing_whitespace = true - - # Set default charset - [*.{js,ts,scss,html}] - charset = utf-8 - indent_style = space --indent_size = 4 -+indent_size = 2 -diff --git a/jslib/.git-blame-ignore-revs b/jslib/.git-blame-ignore-revs -new file mode 100644 -index 00000000..b2c2a6a4 ---- /dev/null -+++ b/jslib/.git-blame-ignore-revs -@@ -0,0 +1 @@ -+193434461dbd9c48fe5dcbad95693470aec422ac -diff --git a/jslib/.gitattributes b/jslib/.gitattributes -new file mode 100644 -index 00000000..6313b56c ---- /dev/null -+++ b/jslib/.gitattributes -@@ -0,0 +1 @@ -+* text=auto eol=lf -diff --git a/jslib/.github/PULL_REQUEST_TEMPLATE.md b/jslib/.github/PULL_REQUEST_TEMPLATE.md -index 83e7e640..07679862 100644 ---- a/jslib/.github/PULL_REQUEST_TEMPLATE.md -+++ b/jslib/.github/PULL_REQUEST_TEMPLATE.md -@@ -1,4 +1,5 @@ - ## Type of change -+ - - [ ] Bug fix - - [ ] New feature development - - [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc) -@@ -6,22 +7,22 @@ - - [ ] Other - - ## Objective -- -- - -+ - - ## Code changes -+ - - - --* **file.ext:** Description of what was changed and why -+- **file.ext:** Description of what was changed and why - - ## Testing requirements -- -- - -+ - - ## 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) -diff --git a/jslib/.github/workflows/build.yml b/jslib/.github/workflows/build.yml -index e58289c8..ba1d31a0 100644 ---- a/jslib/.github/workflows/build.yml -+++ b/jslib/.github/workflows/build.yml -@@ -20,7 +20,6 @@ jobs: - - name: Print lines of code - run: cloc --include-lang TypeScript,JavaScript,HTML,Sass,CSS --vcs git - -- - build: - name: Build jslib - runs-on: ${{ matrix.os }} -@@ -33,11 +32,10 @@ jobs: - - name: Set up Node - uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea - with: -- node-version: '14' -+ node-version: "16" - -- - name: Update NPM -+ - name: Install node-gyp - run: | -- npm install -g npm@7 - npm install -g node-gyp - node-gyp install $(node -v) - -@@ -107,7 +105,7 @@ jobs: - secrets: "devops-alerts-slack-webhook-url" - - - name: Notify Slack on failure -- uses: act10ns/slack@e4e71685b9b239384b0f676a63c32367f59c2522 # v1.2.2 -+ uses: act10ns/slack@e4e71685b9b239384b0f676a63c32367f59c2522 # v1.2.2 - if: failure() - env: - SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }} -diff --git a/jslib/.husky/.gitignore b/jslib/.husky/.gitignore -new file mode 100644 -index 00000000..31354ec1 ---- /dev/null -+++ b/jslib/.husky/.gitignore -@@ -0,0 +1 @@ -+_ -diff --git a/jslib/.husky/pre-commit b/jslib/.husky/pre-commit -new file mode 100644 -index 00000000..36af2198 ---- /dev/null -+++ b/jslib/.husky/pre-commit -@@ -0,0 +1,4 @@ -+#!/bin/sh -+. "$(dirname "$0")/_/husky.sh" -+ -+npx lint-staged -diff --git a/jslib/.prettierignore b/jslib/.prettierignore -new file mode 100644 -index 00000000..2d0a1ff5 ---- /dev/null -+++ b/jslib/.prettierignore -@@ -0,0 +1,5 @@ -+# Build directories -+dist -+ -+# Github Workflows -+.github/workflows -diff --git a/jslib/.prettierrc.json b/jslib/.prettierrc.json -new file mode 100644 -index 00000000..de753c53 ---- /dev/null -+++ b/jslib/.prettierrc.json -@@ -0,0 +1,3 @@ -+{ -+ "printWidth": 100 -+} -diff --git a/jslib/.vscode/launch.json b/jslib/.vscode/launch.json -index d9b0460f..e43e2c72 100644 ---- a/jslib/.vscode/launch.json -+++ b/jslib/.vscode/launch.json -@@ -1,18 +1,16 @@ - { -- // Use IntelliSense to learn about possible attributes. -- // Hover to view descriptions of existing attributes. -- // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 -- "version": "0.2.0", -- "configurations": [ -- { -- "type": "node", -- "request": "launch", -- "name": "Jasmine Individual Test", -- "program": "${workspaceRoot}\\node_modules\\jasmine\\bin\\jasmine.js", -- "preLaunchTask": "npm run build", -- "args": [ -- "${workspaceFolder}/dist\\spec\\node\\services\\nodeCryptoFunction.service.spec.js" -- ] -- } -- ] --} -\ No newline at end of file -+ // Use IntelliSense to learn about possible attributes. -+ // Hover to view descriptions of existing attributes. -+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 -+ "version": "0.2.0", -+ "configurations": [ -+ { -+ "type": "node", -+ "request": "launch", -+ "name": "Jasmine Individual Test", -+ "program": "${workspaceRoot}\\node_modules\\jasmine\\bin\\jasmine.js", -+ "preLaunchTask": "npm run build", -+ "args": ["${workspaceFolder}/dist\\spec\\node\\services\\nodeCryptoFunction.service.spec.js"] -+ } -+ ] -+} -diff --git a/jslib/.vscode/tasks.json b/jslib/.vscode/tasks.json -index db998c99..924fe71b 100644 ---- a/jslib/.vscode/tasks.json -+++ b/jslib/.vscode/tasks.json -@@ -1,12 +1,12 @@ - { -- // See https://go.microsoft.com/fwlink/?LinkId=733558 -- // for the documentation about the tasks.json format -- "version": "2.0.0", -- "tasks": [ -- { -- "label": "npm run build", -- "type": "shell", -- "command": "npm run build" -- } -- ] --} -\ No newline at end of file -+ // See https://go.microsoft.com/fwlink/?LinkId=733558 -+ // for the documentation about the tasks.json format -+ "version": "2.0.0", -+ "tasks": [ -+ { -+ "label": "npm run build", -+ "type": "shell", -+ "command": "npm run build" -+ } -+ ] -+} -diff --git a/jslib/CONTRIBUTING.md b/jslib/CONTRIBUTING.md -index 4f7780a1..5eb19060 100644 ---- a/jslib/CONTRIBUTING.md -+++ b/jslib/CONTRIBUTING.md -@@ -6,15 +6,11 @@ Please visit our [Community Forums](https://community.bitwarden.com/) for genera - - Here is how you can get involved: - --* **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one -- --* **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code -- --* **Report a bug or submit a bugfix:** Use Github issues and pull requests -- --* **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help) -- --* **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums -+- **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one -+- **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code -+- **Report a bug or submit a bugfix:** Use Github issues and pull requests -+- **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help) -+- **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums - - ## Contributor Agreement - -@@ -22,9 +18,9 @@ Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/jslib - - ## Pull Request Guidelines - --* use `npm run lint` and fix any linting suggestions before submitting a pull request --* commit any pull requests against the `master` branch --* include a link to your Community Forums post -+- use `npm run lint` and fix any linting suggestions before submitting a pull request -+- commit any pull requests against the `master` branch -+- include a link to your Community Forums post - - # Introduction to jslib and git submodules - -@@ -33,36 +29,39 @@ jslib is a repository that contains shared code for all Bitwarden Typescript/Jav - If you haven't worked with submodules before, you should start by reading some basic guides (such as the [git scm chapter](https://git-scm.com/book/en/v2/Git-Tools-Submodules) or the [Atlassian tutorial](https://www.atlassian.com/git/tutorials/git-submodule)). - - # Setting up your Local Dev environment for jslib -+ - In order to easily develop local changes to jslib across each of the TypeScript/JavaScript clients, we recommend using symlinks for the submodule so that you only have to make the change once for it to be reflected across all your local repos. - - ## Prerequisites -+ - 1. git bash or other git command line - 2. In order for this to work well, you need to use a consistent relative directory structure. Repos should be cloned in the following way: - -- * `./`; we'll call this `/dev` ('cause why not) -- * jslib - `git clone https://github.com/bitwarden/jslib.git` (/dev/jslib) -- * web - `git clone --recurse-submodules https://github.com/bitwarden/web.git` (/dev/web) -- * desktop - `git clone --recurse-submodules https://github.com/bitwarden/desktop.git` (/dev/desktop) -- * browser - `git clone --recurse-submodules https://github.com/bitwarden/browser.git` (/dev/browser) -- * cli - `git clone --recurse-submodules https://github.com/bitwarden/cli` (/dev/cli) -+ - `./`; we'll call this `/dev` ('cause why not) -+ - jslib - `git clone https://github.com/bitwarden/jslib.git` (/dev/jslib) -+ - web - `git clone --recurse-submodules https://github.com/bitwarden/web.git` (/dev/web) -+ - desktop - `git clone --recurse-submodules https://github.com/bitwarden/desktop.git` (/dev/desktop) -+ - browser - `git clone --recurse-submodules https://github.com/bitwarden/browser.git` (/dev/browser) -+ - cli - `git clone --recurse-submodules https://github.com/bitwarden/cli` (/dev/cli) - -- You should notice web, desktop, browser and cli each reference jslib as a git submodule. If you've already cloned the repos but didn't use `--recurse-submodules` then you'll need to init the submodule with `npm run sub:init`. -+ You should notice web, desktop, browser and cli each reference jslib as a git submodule. If you've already cloned the repos but didn't use `--recurse-submodules` then you'll need to init the submodule with `npm run sub:init`. - - ## Configure Symlinks -+ - Using `git clone` will make symlinks added to your repo be seen by git as plain text file paths. We need to prevent that. In the project root run, `git config core.symlinks true`. - - For each project other than jslib, run the following: - --* For macOS/Linux: `npm run symlink:mac` --* For Windows: `npm run symlink:win` -+- For macOS/Linux: `npm run symlink:mac` -+- For Windows: `npm run symlink:win` - - Your client repos will now be pointing to your local jslib repo. You can now make changes in jslib and they will be immediately shared by the clients (just like they will be in production). - - ## Committing and pushing jslib changes - --* You work on jslib like any other repo. Check out a new branch, make some commits, and push to remote when you're ready to submit a PR. --* Do not commit your jslib changes in the client repo. Your changes to the client and your changes to jslib should stay completely separate. --* When submitting a client PR that depends on a jslib PR, please include a link to the jslib PR so that the reviewer knows there are jslib changes. -+- You work on jslib like any other repo. Check out a new branch, make some commits, and push to remote when you're ready to submit a PR. -+- Do not commit your jslib changes in the client repo. Your changes to the client and your changes to jslib should stay completely separate. -+- When submitting a client PR that depends on a jslib PR, please include a link to the jslib PR so that the reviewer knows there are jslib changes. - - ### Updating jslib on a feature branch - -@@ -70,8 +69,8 @@ If you've submitted a client PR and a jslib PR, your jslib PR will be approved a - - 1. If you've symlinked the client's jslib directory following the steps above, you'll need to delete that symlink and then run `npm run sub:init`. - 2. Update the jslib submodule: -- * if you're working on your own fork, run `git submodule update --remote --reference upstream`. -- * if you're working on a branch on the official repo, run `npm run sub:update` -+ - if you're working on your own fork, run `git submodule update --remote --reference upstream`. -+ - if you're working on a branch on the official repo, run `npm run sub:update` - 3. To check you've done this correctly, you can `cd` into your jslib directory and run `git log`. You should see your recent changes in the log. This will also show you the most recent commit hash, which should match the most recent commit hash on [Github](https://github.com/bitwarden/jslib). - 4. Add your changes: `git add jslib` - 5. Commit your changes: `git commit -m "update jslib version"` -@@ -89,7 +88,8 @@ If you've made changes to jslib without needing to make any changes to the clien - 4. Create a new PR to the client repo. Please include a link to your jslib PR so that reviewers know why you're updating jslib. - - ## Merge Conflicts --At times when you need to perform a `git merge master` into your feature or local branch, and there are conflicting version references to the *jslib* repo from your other clients, you will not be able to use the traditional merge or stage functions you would normally use for a file. -+ -+At times when you need to perform a `git merge master` into your feature or local branch, and there are conflicting version references to the _jslib_ repo from your other clients, you will not be able to use the traditional merge or stage functions you would normally use for a file. - - To resolve you must use either `git reset` or update the index directly using `git update-index`. You can use (depending on whether you have symlink'd jslib) one of the following: - -diff --git a/jslib/README.md b/jslib/README.md -index 2dca3256..ce5cb1f1 100644 ---- a/jslib/README.md -+++ b/jslib/README.md -@@ -5,13 +5,34 @@ - Common code referenced across Bitwarden JavaScript projects. - - ## Requirements --* [Node.js](https://nodejs.org) v14.17 or greater --* NPM v7 --* Git --* node-gyp -+ -+- [Node.js](https://nodejs.org) v16.13.1 or greater -+- NPM v8 -+- Git -+- node-gyp - - ### Windows - --* *Microsoft Build Tools 2015* in Visual Studio Installer --* [Windows 10 SDK 17134](https://developer.microsoft.com/en-us/windows/downloads/sdk-archive/) --either by downloading it seperately or through the Visual Studio Installer. -+- _Microsoft Build Tools 2015_ in Visual Studio Installer -+- [Windows 10 SDK 17134](https://developer.microsoft.com/en-us/windows/downloads/sdk-archive/) -+ either by downloading it seperately or through the Visual Studio Installer. -+ -+## Prettier -+ -+We recently migrated to using Prettier as code formatter. All previous branches will need to updated to avoid large merge conflicts using the following steps: -+ -+1. Check out your local Branch -+2. Run `git merge 8b2dfc6cdcb8ff5b604364c2ea6d343473aee7cd` -+3. Resolve any merge conflicts, commit. -+4. Run `npm run prettier` -+5. Commit -+6. Run `git merge -Xours 193434461dbd9c48fe5dcbad95693470aec422ac` -+7. Push -+ -+### Git blame -+ -+We also recommend that you configure git to ignore the prettier revision using: -+ -+```bash -+git config blame.ignoreRevsFile .git-blame-ignore-revs -+``` -diff --git a/jslib/SECURITY.md b/jslib/SECURITY.md -index ef94f0b4..7a055501 100644 ---- a/jslib/SECURITY.md -+++ b/jslib/SECURITY.md -@@ -7,7 +7,7 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in - - Let us know as soon as possible upon discovery of a potential security issue, and we'll make every - effort to quickly resolve the issue. - - Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a -- third-party. We may publicly disclose the issue before resolving it, if appropriate. -+ third-party. We may publicly disclose the issue before resolving it, if appropriate. - - Make a good faith effort to avoid privacy violations, destruction of data, and interruption or - degradation of our service. Only interact with accounts you own or with explicit permission of the - account holder. -diff --git a/jslib/angular/package-lock.json b/jslib/angular/package-lock.json -index 8bc77098..405539ab 100644 ---- a/jslib/angular/package-lock.json -+++ b/jslib/angular/package-lock.json -@@ -9,25 +9,25 @@ - "version": "0.0.0", - "license": "GPL-3.0", - "dependencies": { -- "@angular/animations": "^11.2.11", -- "@angular/cdk": "^11.2.10", -- "@angular/common": "^11.2.11", -- "@angular/compiler": "^11.2.11", -- "@angular/core": "^11.2.11", -- "@angular/forms": "^11.2.11", -- "@angular/platform-browser": "^11.2.11", -- "@angular/platform-browser-dynamic": "^11.2.11", -- "@angular/router": "^11.2.11", -+ "@angular/animations": "^12.2.13", -+ "@angular/cdk": "^12.2.13", -+ "@angular/common": "^12.2.13", -+ "@angular/compiler": "^12.2.13", -+ "@angular/core": "^12.2.13", -+ "@angular/forms": "^12.2.13", -+ "@angular/platform-browser": "^12.2.13", -+ "@angular/platform-browser-dynamic": "^12.2.13", -+ "@angular/router": "^12.2.13", - "@bitwarden/jslib-common": "file:../common", - "duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git", -- "rxjs": "6.6.7", -+ "rxjs": "^7.4.0", - "tldjs": "^2.3.1", - "zone.js": "0.11.4" - }, - "devDependencies": { - "@types/duo_web_sdk": "^2.7.1", - "rimraf": "^3.0.2", -- "typescript": "4.1.5" -+ "typescript": "4.3.5" - } - }, - "../common": { -@@ -41,7 +41,7 @@ - "lunr": "^2.3.9", - "node-forge": "^0.10.0", - "papaparse": "^5.3.0", -- "rxjs": "6.6.7", -+ "rxjs": "^7.4.0", - "tldjs": "^2.3.1", - "zxcvbn": "^4.4.2" - }, -@@ -53,92 +53,111 @@ - "@types/tldjs": "^2.3.0", - "@types/zxcvbn": "^4.4.1", - "rimraf": "^3.0.2", -- "typescript": "4.1.5" -+ "typescript": "4.3.5" - } - }, - "node_modules/@angular/animations": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-11.2.14.tgz", -- "integrity": "sha512-Heq/nNrCmb3jbkusu+BQszOecfFI/31Oxxj+CDQkqqYpBcswk6bOJLoEE472o+vmgxaXbgeflU9qbIiCQhpMFA==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-12.2.14.tgz", -+ "integrity": "sha512-1BR5u32auVePvXNNP96DB2008V+Ku0OGqeZQl2h4XA9xzES/Zk5WllIJZXqRmWMRBVARfXsfb0RdMty9gcaVjA==", - "dependencies": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" -+ }, -+ "engines": { -+ "node": "^12.14.1 || >=14.0.0" - }, - "peerDependencies": { -- "@angular/core": "11.2.14" -+ "@angular/core": "12.2.14" - } - }, - "node_modules/@angular/cdk": { -- "version": "11.2.13", -- "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-11.2.13.tgz", -- "integrity": "sha512-FkE4iCwoLbQxLDUOjV1I7M/6hmpyb7erAjEdWgch7nGRNxF1hqX5Bqf1lvLFKPNCbx5NRI5K7YVAdIUQUR8vug==", -+ "version": "12.2.13", -+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.2.13.tgz", -+ "integrity": "sha512-zSKRhECyFqhingIeyRInIyTvYErt4gWo+x5DQr0b7YLUbU8DZSwWnG4w76Ke2s4U8T7ry1jpJBHoX/e8YBpGMg==", - "dependencies": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" - }, - "optionalDependencies": { - "parse5": "^5.0.0" - }, - "peerDependencies": { -- "@angular/common": "^11.0.0 || ^12.0.0-0", -- "@angular/core": "^11.0.0 || ^12.0.0-0" -+ "@angular/common": "^12.0.0 || ^13.0.0-0", -+ "@angular/core": "^12.0.0 || ^13.0.0-0", -+ "rxjs": "^6.5.3 || ^7.0.0" - } - }, - "node_modules/@angular/common": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/common/-/common-11.2.14.tgz", -- "integrity": "sha512-ZSLV/3j7eCTyLf/8g4yBFLWySjiLz3vLJAGWscYoUpnJWMnug1VRu6zoF/COxCbtORgE+Wz6K0uhfS6MziBGVw==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/common/-/common-12.2.14.tgz", -+ "integrity": "sha512-ffYUYdwZETmFJw0AcWY30WsaWBhJxj/zSmFXWjgEGEGZH56zwbbNwfMZOYZ1jz4haAVxGu+TdXsOl2yMGzN7jQ==", - "dependencies": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" -+ }, -+ "engines": { -+ "node": "^12.14.1 || >=14.0.0" - }, - "peerDependencies": { -- "@angular/core": "11.2.14", -- "rxjs": "^6.5.3" -+ "@angular/core": "12.2.14", -+ "rxjs": "^6.5.3 || ^7.0.0" - } - }, - "node_modules/@angular/compiler": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-11.2.14.tgz", -- "integrity": "sha512-XBOK3HgA+/y6Cz7kOX4zcJYmgJ264XnfcbXUMU2cD7Ac+mbNhLPKohWrEiSWalfcjnpf5gRfufQrQP7lpAGu0A==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-12.2.14.tgz", -+ "integrity": "sha512-dwmZi+n66IUzRFlGWu9mjXq170ZEsaDvlNLZzaPgs6vZTa4Kt7PWvIF/Y7TMvnVv/uqNG6kOhfmOkf6rfz1Gjg==", - "dependencies": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" -+ }, -+ "engines": { -+ "node": "^12.14.1 || >=14.0.0" - } - }, - "node_modules/@angular/core": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/core/-/core-11.2.14.tgz", -- "integrity": "sha512-vpR4XqBGitk1Faph37CSpemwIYTmJ3pdIVNoHKP6jLonpWu+0azkchf0f7oD8/2ivj2F81opcIw0tcsy/D/5Vg==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/core/-/core-12.2.14.tgz", -+ "integrity": "sha512-dlVk7yqUHL2R/eCmM8LsWuxhEBfzg0y1zHt0UqCuFwlCoiw+IG4HFy4OlZEUw9NUEZJSv0aDv3sWqxLkvK5vvg==", - "dependencies": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" -+ }, -+ "engines": { -+ "node": "^12.14.1 || >=14.0.0" - }, - "peerDependencies": { -- "rxjs": "^6.5.3", -- "zone.js": "^0.10.2 || ^0.11.3" -+ "rxjs": "^6.5.3 || ^7.0.0", -+ "zone.js": "~0.11.4" - } - }, - "node_modules/@angular/forms": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-11.2.14.tgz", -- "integrity": "sha512-4LWqY6KEIk1AZQFnk+4PJSOCamlD4tumuVN06gO4D0dZo9Cx+GcvW6pM6N0CPubRvPs3sScCnu20WT11HNWC1w==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-12.2.14.tgz", -+ "integrity": "sha512-/9/gSJUBXVRVdRnzgJnALAQZYJATuGDMkFC9ms9DEMG4PMAhe9x4if1lJjN6noz5RAom3qNuVBNWaYAPUxlcBQ==", - "dependencies": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" -+ }, -+ "engines": { -+ "node": "^12.14.1 || >=14.0.0" - }, - "peerDependencies": { -- "@angular/common": "11.2.14", -- "@angular/core": "11.2.14", -- "@angular/platform-browser": "11.2.14", -- "rxjs": "^6.5.3" -+ "@angular/common": "12.2.14", -+ "@angular/core": "12.2.14", -+ "@angular/platform-browser": "12.2.14", -+ "rxjs": "^6.5.3 || ^7.0.0" - } - }, - "node_modules/@angular/platform-browser": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-11.2.14.tgz", -- "integrity": "sha512-fb7b7ss/gRoP8wLAN17W62leMgjynuyjEPU2eUoAAazsG9f2cgM+z3rK29GYncDVyYQxZUZYnjSqvL6GSXx86A==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-12.2.14.tgz", -+ "integrity": "sha512-fWcE2rnJ3ZCISa1oPfsIDV7FBZBoLFEdDuMXAiDYqDPKvF/E5U5nHrS+K4SlLAi094bMobtTOReNWl/Ienniyw==", - "dependencies": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" -+ }, -+ "engines": { -+ "node": "^12.14.1 || >=14.0.0" - }, - "peerDependencies": { -- "@angular/animations": "11.2.14", -- "@angular/common": "11.2.14", -- "@angular/core": "11.2.14" -+ "@angular/animations": "12.2.14", -+ "@angular/common": "12.2.14", -+ "@angular/core": "12.2.14" - }, - "peerDependenciesMeta": { - "@angular/animations": { -@@ -147,31 +166,37 @@ - } - }, - "node_modules/@angular/platform-browser-dynamic": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-11.2.14.tgz", -- "integrity": "sha512-TWTPdFs6iBBcp+/YMsgCRQwdHpWGq8KjeJDJ2tfatGgBD3Gqt2YaHOMST1zPW6RkrmupytTejuVqXzeaKWFxuw==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-12.2.14.tgz", -+ "integrity": "sha512-0NPF7mS91Tct8rBmOLZPmnLSuS4kbLHXo6eTgrg80OC0vlzBiQwGDVW4X3KncCoX9CpevaGJCdSMc+uPNsFOUQ==", - "dependencies": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" -+ }, -+ "engines": { -+ "node": "^12.14.1 || >=14.0.0" - }, - "peerDependencies": { -- "@angular/common": "11.2.14", -- "@angular/compiler": "11.2.14", -- "@angular/core": "11.2.14", -- "@angular/platform-browser": "11.2.14" -+ "@angular/common": "12.2.14", -+ "@angular/compiler": "12.2.14", -+ "@angular/core": "12.2.14", -+ "@angular/platform-browser": "12.2.14" - } - }, - "node_modules/@angular/router": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/router/-/router-11.2.14.tgz", -- "integrity": "sha512-3aYBmj+zrEL9yf/ntIQxHIYaWShZOBKP3U07X2mX+TPMpGlvHDnR7L6bWhQVZwewzMMz7YVR16ldg50IFuAlfA==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/router/-/router-12.2.14.tgz", -+ "integrity": "sha512-yP5grSnqBvc4vNhtYdcxDgDYIebUKs5f0xyFkUJM5030UnQ0CV45tBsSxHMkQbPZucIfOuxpRy8xy5+4GizuwQ==", - "dependencies": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" -+ }, -+ "engines": { -+ "node": "^12.14.1 || >=14.0.0" - }, - "peerDependencies": { -- "@angular/common": "11.2.14", -- "@angular/core": "11.2.14", -- "@angular/platform-browser": "11.2.14", -- "rxjs": "^6.5.3" -+ "@angular/common": "12.2.14", -+ "@angular/core": "12.2.14", -+ "@angular/platform-browser": "12.2.14", -+ "rxjs": "^6.5.3 || ^7.0.0" - } - }, - "node_modules/@bitwarden/jslib-common": { -@@ -310,20 +335,17 @@ - } - }, - "node_modules/rxjs": { -- "version": "6.6.7", -- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", -- "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", -+ "version": "7.4.0", -+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", -+ "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", - "dependencies": { -- "tslib": "^1.9.0" -- }, -- "engines": { -- "npm": ">=2.0.0" -+ "tslib": "~2.1.0" - } - }, - "node_modules/rxjs/node_modules/tslib": { -- "version": "1.14.1", -- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", -- "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" -+ "version": "2.1.0", -+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", -+ "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" - }, - "node_modules/tldjs": { - "version": "2.3.1", -@@ -343,9 +365,9 @@ - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "node_modules/typescript": { -- "version": "4.1.5", -- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz", -- "integrity": "sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==", -+ "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", -@@ -372,76 +394,76 @@ - }, - "dependencies": { - "@angular/animations": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-11.2.14.tgz", -- "integrity": "sha512-Heq/nNrCmb3jbkusu+BQszOecfFI/31Oxxj+CDQkqqYpBcswk6bOJLoEE472o+vmgxaXbgeflU9qbIiCQhpMFA==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-12.2.14.tgz", -+ "integrity": "sha512-1BR5u32auVePvXNNP96DB2008V+Ku0OGqeZQl2h4XA9xzES/Zk5WllIJZXqRmWMRBVARfXsfb0RdMty9gcaVjA==", - "requires": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" - } - }, - "@angular/cdk": { -- "version": "11.2.13", -- "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-11.2.13.tgz", -- "integrity": "sha512-FkE4iCwoLbQxLDUOjV1I7M/6hmpyb7erAjEdWgch7nGRNxF1hqX5Bqf1lvLFKPNCbx5NRI5K7YVAdIUQUR8vug==", -+ "version": "12.2.13", -+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.2.13.tgz", -+ "integrity": "sha512-zSKRhECyFqhingIeyRInIyTvYErt4gWo+x5DQr0b7YLUbU8DZSwWnG4w76Ke2s4U8T7ry1jpJBHoX/e8YBpGMg==", - "requires": { - "parse5": "^5.0.0", -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" - } - }, - "@angular/common": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/common/-/common-11.2.14.tgz", -- "integrity": "sha512-ZSLV/3j7eCTyLf/8g4yBFLWySjiLz3vLJAGWscYoUpnJWMnug1VRu6zoF/COxCbtORgE+Wz6K0uhfS6MziBGVw==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/common/-/common-12.2.14.tgz", -+ "integrity": "sha512-ffYUYdwZETmFJw0AcWY30WsaWBhJxj/zSmFXWjgEGEGZH56zwbbNwfMZOYZ1jz4haAVxGu+TdXsOl2yMGzN7jQ==", - "requires": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" - } - }, - "@angular/compiler": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-11.2.14.tgz", -- "integrity": "sha512-XBOK3HgA+/y6Cz7kOX4zcJYmgJ264XnfcbXUMU2cD7Ac+mbNhLPKohWrEiSWalfcjnpf5gRfufQrQP7lpAGu0A==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-12.2.14.tgz", -+ "integrity": "sha512-dwmZi+n66IUzRFlGWu9mjXq170ZEsaDvlNLZzaPgs6vZTa4Kt7PWvIF/Y7TMvnVv/uqNG6kOhfmOkf6rfz1Gjg==", - "requires": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" - } - }, - "@angular/core": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/core/-/core-11.2.14.tgz", -- "integrity": "sha512-vpR4XqBGitk1Faph37CSpemwIYTmJ3pdIVNoHKP6jLonpWu+0azkchf0f7oD8/2ivj2F81opcIw0tcsy/D/5Vg==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/core/-/core-12.2.14.tgz", -+ "integrity": "sha512-dlVk7yqUHL2R/eCmM8LsWuxhEBfzg0y1zHt0UqCuFwlCoiw+IG4HFy4OlZEUw9NUEZJSv0aDv3sWqxLkvK5vvg==", - "requires": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" - } - }, - "@angular/forms": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-11.2.14.tgz", -- "integrity": "sha512-4LWqY6KEIk1AZQFnk+4PJSOCamlD4tumuVN06gO4D0dZo9Cx+GcvW6pM6N0CPubRvPs3sScCnu20WT11HNWC1w==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-12.2.14.tgz", -+ "integrity": "sha512-/9/gSJUBXVRVdRnzgJnALAQZYJATuGDMkFC9ms9DEMG4PMAhe9x4if1lJjN6noz5RAom3qNuVBNWaYAPUxlcBQ==", - "requires": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" - } - }, - "@angular/platform-browser": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-11.2.14.tgz", -- "integrity": "sha512-fb7b7ss/gRoP8wLAN17W62leMgjynuyjEPU2eUoAAazsG9f2cgM+z3rK29GYncDVyYQxZUZYnjSqvL6GSXx86A==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-12.2.14.tgz", -+ "integrity": "sha512-fWcE2rnJ3ZCISa1oPfsIDV7FBZBoLFEdDuMXAiDYqDPKvF/E5U5nHrS+K4SlLAi094bMobtTOReNWl/Ienniyw==", - "requires": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" - } - }, - "@angular/platform-browser-dynamic": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-11.2.14.tgz", -- "integrity": "sha512-TWTPdFs6iBBcp+/YMsgCRQwdHpWGq8KjeJDJ2tfatGgBD3Gqt2YaHOMST1zPW6RkrmupytTejuVqXzeaKWFxuw==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-12.2.14.tgz", -+ "integrity": "sha512-0NPF7mS91Tct8rBmOLZPmnLSuS4kbLHXo6eTgrg80OC0vlzBiQwGDVW4X3KncCoX9CpevaGJCdSMc+uPNsFOUQ==", - "requires": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" - } - }, - "@angular/router": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/router/-/router-11.2.14.tgz", -- "integrity": "sha512-3aYBmj+zrEL9yf/ntIQxHIYaWShZOBKP3U07X2mX+TPMpGlvHDnR7L6bWhQVZwewzMMz7YVR16ldg50IFuAlfA==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/router/-/router-12.2.14.tgz", -+ "integrity": "sha512-yP5grSnqBvc4vNhtYdcxDgDYIebUKs5f0xyFkUJM5030UnQ0CV45tBsSxHMkQbPZucIfOuxpRy8xy5+4GizuwQ==", - "requires": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" - } - }, - "@bitwarden/jslib-common": { -@@ -461,9 +483,9 @@ - "node-forge": "^0.10.0", - "papaparse": "^5.3.0", - "rimraf": "^3.0.2", -- "rxjs": "6.6.7", -+ "rxjs": "^7.4.0", - "tldjs": "^2.3.1", -- "typescript": "4.1.5", -+ "typescript": "4.3.5", - "zxcvbn": "^4.4.2" - } - }, -@@ -580,17 +602,17 @@ - } - }, - "rxjs": { -- "version": "6.6.7", -- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", -- "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", -+ "version": "7.4.0", -+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", -+ "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", - "requires": { -- "tslib": "^1.9.0" -+ "tslib": "~2.1.0" - }, - "dependencies": { - "tslib": { -- "version": "1.14.1", -- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", -- "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" -+ "version": "2.1.0", -+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", -+ "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" - } - } - }, -@@ -608,9 +630,9 @@ - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "typescript": { -- "version": "4.1.5", -- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz", -- "integrity": "sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==", -+ "version": "4.3.5", -+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", -+ "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", - "dev": true - }, - "wrappy": { -diff --git a/jslib/angular/package.json b/jslib/angular/package.json -index 3425171c..d1d5f64c 100644 ---- a/jslib/angular/package.json -+++ b/jslib/angular/package.json -@@ -22,21 +22,21 @@ - "devDependencies": { - "@types/duo_web_sdk": "^2.7.1", - "rimraf": "^3.0.2", -- "typescript": "4.1.5" -+ "typescript": "4.3.5" - }, - "dependencies": { -- "@angular/animations": "^11.2.11", -- "@angular/cdk": "^11.2.10", -- "@angular/common": "^11.2.11", -- "@angular/compiler": "^11.2.11", -- "@angular/core": "^11.2.11", -- "@angular/forms": "^11.2.11", -- "@angular/platform-browser": "^11.2.11", -- "@angular/platform-browser-dynamic": "^11.2.11", -- "@angular/router": "^11.2.11", -+ "@angular/animations": "^12.2.13", -+ "@angular/cdk": "^12.2.13", -+ "@angular/common": "^12.2.13", -+ "@angular/compiler": "^12.2.13", -+ "@angular/core": "^12.2.13", -+ "@angular/forms": "^12.2.13", -+ "@angular/platform-browser": "^12.2.13", -+ "@angular/platform-browser-dynamic": "^12.2.13", -+ "@angular/router": "^12.2.13", - "@bitwarden/jslib-common": "file:../common", - "duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git", -- "rxjs": "6.6.7", -+ "rxjs": "^7.4.0", - "tldjs": "^2.3.1", - "zone.js": "0.11.4" - } -diff --git a/jslib/angular/src/components/add-edit-custom-fields.component.ts b/jslib/angular/src/components/add-edit-custom-fields.component.ts -index 71040ac4..4107bc27 100644 ---- a/jslib/angular/src/components/add-edit-custom-fields.component.ts -+++ b/jslib/angular/src/components/add-edit-custom-fields.component.ts -@@ -1,124 +1,120 @@ --import { -- Directive, -- Input, -- OnChanges, -- SimpleChanges, --} from '@angular/core'; -+import { Directive, Input, OnChanges, SimpleChanges } from "@angular/core"; - --import { -- CdkDragDrop, -- moveItemInArray, --} from '@angular/cdk/drag-drop'; -+import { CdkDragDrop, moveItemInArray } from "@angular/cdk/drag-drop"; - --import { EventService } from 'jslib-common/abstractions/event.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; -+import { EventService } from "jslib-common/abstractions/event.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; - --import { CipherView } from 'jslib-common/models/view/cipherView'; --import { FieldView } from 'jslib-common/models/view/fieldView'; -+import { CipherView } from "jslib-common/models/view/cipherView"; -+import { FieldView } from "jslib-common/models/view/fieldView"; - --import { CipherType } from 'jslib-common/enums/cipherType'; --import { EventType } from 'jslib-common/enums/eventType'; --import { FieldType } from 'jslib-common/enums/fieldType'; -+import { CipherType } from "jslib-common/enums/cipherType"; -+import { EventType } from "jslib-common/enums/eventType"; -+import { FieldType } from "jslib-common/enums/fieldType"; - --import { Utils } from 'jslib-common/misc/utils'; -+import { Utils } from "jslib-common/misc/utils"; - - @Directive() - export class AddEditCustomFieldsComponent implements OnChanges { -- @Input() cipher: CipherView; -- @Input() thisCipherType: CipherType; -- @Input() editMode: boolean; -- -- addFieldType: FieldType = FieldType.Text; -- addFieldTypeOptions: any[]; -- addFieldLinkedTypeOption: any; -- linkedFieldOptions: any[] = []; -- -- cipherType = CipherType; -- fieldType = FieldType; -- eventType = EventType; -- -- constructor(private i18nService: I18nService, private eventService: EventService) { -- this.addFieldTypeOptions = [ -- { name: i18nService.t('cfTypeText'), value: FieldType.Text }, -- { name: i18nService.t('cfTypeHidden'), value: FieldType.Hidden }, -- { name: i18nService.t('cfTypeBoolean'), value: FieldType.Boolean }, -- ]; -- this.addFieldLinkedTypeOption = { name: this.i18nService.t('cfTypeLinked'), value: FieldType.Linked }; -+ @Input() cipher: CipherView; -+ @Input() thisCipherType: CipherType; -+ @Input() editMode: boolean; -+ -+ addFieldType: FieldType = FieldType.Text; -+ addFieldTypeOptions: any[]; -+ addFieldLinkedTypeOption: any; -+ linkedFieldOptions: any[] = []; -+ -+ cipherType = CipherType; -+ fieldType = FieldType; -+ eventType = EventType; -+ -+ constructor(private i18nService: I18nService, private eventService: EventService) { -+ this.addFieldTypeOptions = [ -+ { name: i18nService.t("cfTypeText"), value: FieldType.Text }, -+ { name: i18nService.t("cfTypeHidden"), value: FieldType.Hidden }, -+ { name: i18nService.t("cfTypeBoolean"), value: FieldType.Boolean }, -+ ]; -+ this.addFieldLinkedTypeOption = { -+ name: this.i18nService.t("cfTypeLinked"), -+ value: FieldType.Linked, -+ }; -+ } -+ -+ ngOnChanges(changes: SimpleChanges) { -+ if (changes.thisCipherType != null) { -+ this.setLinkedFieldOptions(); -+ -+ if (!changes.thisCipherType.firstChange) { -+ this.resetCipherLinkedFields(); -+ } - } -+ } - -- ngOnChanges(changes: SimpleChanges) { -- if (changes.thisCipherType != null) { -- this.setLinkedFieldOptions(); -- -- if (!changes.thisCipherType.firstChange) { -- this.resetCipherLinkedFields(); -- } -- } -+ addField() { -+ if (this.cipher.fields == null) { -+ this.cipher.fields = []; - } - -- addField() { -- if (this.cipher.fields == null) { -- this.cipher.fields = []; -- } -- -- const f = new FieldView(); -- f.type = this.addFieldType; -- f.newField = true; -+ const f = new FieldView(); -+ f.type = this.addFieldType; -+ f.newField = true; - -- if (f.type === FieldType.Linked) { -- f.linkedId = this.linkedFieldOptions[0].value; -- } -- -- this.cipher.fields.push(f); -+ if (f.type === FieldType.Linked) { -+ f.linkedId = this.linkedFieldOptions[0].value; - } - -- removeField(field: FieldView) { -- const i = this.cipher.fields.indexOf(field); -- if (i > -1) { -- this.cipher.fields.splice(i, 1); -- } -- } -+ this.cipher.fields.push(f); -+ } - -- toggleFieldValue(field: FieldView) { -- const f = (field as any); -- f.showValue = !f.showValue; -- if (this.editMode && f.showValue) { -- this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipher.id); -- } -+ removeField(field: FieldView) { -+ const i = this.cipher.fields.indexOf(field); -+ if (i > -1) { -+ this.cipher.fields.splice(i, 1); - } -+ } - -- trackByFunction(index: number, item: any) { -- return index; -+ toggleFieldValue(field: FieldView) { -+ const f = field as any; -+ f.showValue = !f.showValue; -+ if (this.editMode && f.showValue) { -+ this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipher.id); - } -+ } - -- drop(event: CdkDragDrop) { -- moveItemInArray(this.cipher.fields, event.previousIndex, event.currentIndex); -- } -+ trackByFunction(index: number, item: any) { -+ return index; -+ } - -- private setLinkedFieldOptions() { -- if (this.cipher.linkedFieldOptions == null) { -- return; -- } -+ drop(event: CdkDragDrop) { -+ moveItemInArray(this.cipher.fields, event.previousIndex, event.currentIndex); -+ } - -- const options: any = []; -- this.cipher.linkedFieldOptions.forEach((linkedFieldOption, id) => -- options.push({ name: this.i18nService.t(linkedFieldOption.i18nKey), value: id })); -- this.linkedFieldOptions = options.sort(Utils.getSortFunction(this.i18nService, 'name')); -+ private setLinkedFieldOptions() { -+ if (this.cipher.linkedFieldOptions == null) { -+ return; - } - -- private resetCipherLinkedFields() { -- if (this.cipher.fields == null || this.cipher.fields.length === 0) { -- return; -- } -+ const options: any = []; -+ this.cipher.linkedFieldOptions.forEach((linkedFieldOption, id) => -+ options.push({ name: this.i18nService.t(linkedFieldOption.i18nKey), value: id }) -+ ); -+ this.linkedFieldOptions = options.sort(Utils.getSortFunction(this.i18nService, "name")); -+ } - -- // Delete any Linked custom fields if the item type does not support them -- if (this.cipher.linkedFieldOptions == null) { -- this.cipher.fields = this.cipher.fields.filter(f => f.type !== FieldType.Linked); -- return; -- } -+ private resetCipherLinkedFields() { -+ if (this.cipher.fields == null || this.cipher.fields.length === 0) { -+ return; -+ } - -- this.cipher.fields -- .filter(f => f.type === FieldType.Linked) -- .forEach(f => f.linkedId = this.linkedFieldOptions[0].value); -+ // Delete any Linked custom fields if the item type does not support them -+ if (this.cipher.linkedFieldOptions == null) { -+ this.cipher.fields = this.cipher.fields.filter((f) => f.type !== FieldType.Linked); -+ return; - } -+ -+ this.cipher.fields -+ .filter((f) => f.type === FieldType.Linked) -+ .forEach((f) => (f.linkedId = this.linkedFieldOptions[0].value)); -+ } - } -diff --git a/jslib/angular/src/components/add-edit.component.ts b/jslib/angular/src/components/add-edit.component.ts -index 0bd4001b..4c19588c 100644 ---- a/jslib/angular/src/components/add-edit.component.ts -+++ b/jslib/angular/src/components/add-edit.component.ts -@@ -1,506 +1,566 @@ --import { -- Directive, -- EventEmitter, -- Input, -- OnInit, -- Output, --} from '@angular/core'; -- --import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType'; --import { CipherType } from 'jslib-common/enums/cipherType'; --import { EventType } from 'jslib-common/enums/eventType'; --import { OrganizationUserStatusType } from 'jslib-common/enums/organizationUserStatusType'; --import { PolicyType } from 'jslib-common/enums/policyType'; --import { SecureNoteType } from 'jslib-common/enums/secureNoteType'; --import { UriMatchType } from 'jslib-common/enums/uriMatchType'; -- --import { AuditService } from 'jslib-common/abstractions/audit.service'; --import { CipherService } from 'jslib-common/abstractions/cipher.service'; --import { CollectionService } from 'jslib-common/abstractions/collection.service'; --import { EventService } from 'jslib-common/abstractions/event.service'; --import { FolderService } from 'jslib-common/abstractions/folder.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { LogService } from 'jslib-common/abstractions/log.service'; --import { MessagingService } from 'jslib-common/abstractions/messaging.service'; --import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { PolicyService } from 'jslib-common/abstractions/policy.service'; --import { StateService } from 'jslib-common/abstractions/state.service'; --import { UserService } from 'jslib-common/abstractions/user.service'; -- --import { Cipher } from 'jslib-common/models/domain/cipher'; -- --import { CardView } from 'jslib-common/models/view/cardView'; --import { CipherView } from 'jslib-common/models/view/cipherView'; --import { CollectionView } from 'jslib-common/models/view/collectionView'; --import { FolderView } from 'jslib-common/models/view/folderView'; --import { IdentityView } from 'jslib-common/models/view/identityView'; --import { LoginUriView } from 'jslib-common/models/view/loginUriView'; --import { LoginView } from 'jslib-common/models/view/loginView'; --import { SecureNoteView } from 'jslib-common/models/view/secureNoteView'; -- --import { Utils } from 'jslib-common/misc/utils'; -+import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; -+ -+import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType"; -+import { CipherType } from "jslib-common/enums/cipherType"; -+import { EventType } from "jslib-common/enums/eventType"; -+import { OrganizationUserStatusType } from "jslib-common/enums/organizationUserStatusType"; -+import { PolicyType } from "jslib-common/enums/policyType"; -+import { SecureNoteType } from "jslib-common/enums/secureNoteType"; -+import { UriMatchType } from "jslib-common/enums/uriMatchType"; -+ -+import { AuditService } from "jslib-common/abstractions/audit.service"; -+import { CipherService } from "jslib-common/abstractions/cipher.service"; -+import { CollectionService } from "jslib-common/abstractions/collection.service"; -+import { EventService } from "jslib-common/abstractions/event.service"; -+import { FolderService } from "jslib-common/abstractions/folder.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { MessagingService } from "jslib-common/abstractions/messaging.service"; -+import { OrganizationService } from "jslib-common/abstractions/organization.service"; -+import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { PolicyService } from "jslib-common/abstractions/policy.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; -+ -+import { Cipher } from "jslib-common/models/domain/cipher"; -+ -+import { CardView } from "jslib-common/models/view/cardView"; -+import { CipherView } from "jslib-common/models/view/cipherView"; -+import { CollectionView } from "jslib-common/models/view/collectionView"; -+import { FolderView } from "jslib-common/models/view/folderView"; -+import { IdentityView } from "jslib-common/models/view/identityView"; -+import { LoginUriView } from "jslib-common/models/view/loginUriView"; -+import { LoginView } from "jslib-common/models/view/loginView"; -+import { SecureNoteView } from "jslib-common/models/view/secureNoteView"; -+ -+import { Utils } from "jslib-common/misc/utils"; - - @Directive() - export class AddEditComponent implements OnInit { -- @Input() cloneMode: boolean = false; -- @Input() folderId: string = null; -- @Input() cipherId: string; -- @Input() type: CipherType; -- @Input() collectionIds: string[]; -- @Input() organizationId: string = null; -- @Output() onSavedCipher = new EventEmitter(); -- @Output() onDeletedCipher = new EventEmitter(); -- @Output() onRestoredCipher = new EventEmitter(); -- @Output() onCancelled = new EventEmitter(); -- @Output() onEditAttachments = new EventEmitter(); -- @Output() onShareCipher = new EventEmitter(); -- @Output() onEditCollections = new EventEmitter(); -- @Output() onGeneratePassword = new EventEmitter(); -- -- editMode: boolean = false; -- cipher: CipherView; -- folders: FolderView[]; -- collections: CollectionView[] = []; -- title: string; -- formPromise: Promise; -- deletePromise: Promise; -- restorePromise: Promise; -- checkPasswordPromise: Promise; -- showPassword: boolean = false; -- showCardNumber: boolean = false; -- showCardCode: boolean = false; -- cipherType = CipherType; -- typeOptions: any[]; -- cardBrandOptions: any[]; -- cardExpMonthOptions: any[]; -- identityTitleOptions: any[]; -- uriMatchOptions: any[]; -- ownershipOptions: any[] = []; -- autofillOnPageLoadOptions: any[]; -- currentDate = new Date(); -- allowPersonal = true; -- reprompt: boolean = false; -- canUseReprompt: boolean = true; -- -- protected writeableCollections: CollectionView[]; -- private previousCipherId: string; -- -- constructor(protected cipherService: CipherService, protected folderService: FolderService, -- protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, -- protected auditService: AuditService, protected stateService: StateService, -- protected userService: UserService, protected collectionService: CollectionService, -- protected messagingService: MessagingService, protected eventService: EventService, -- protected policyService: PolicyService, protected passwordRepromptService: PasswordRepromptService, -- private logService: LogService) { -- this.typeOptions = [ -- { name: i18nService.t('typeLogin'), value: CipherType.Login }, -- { name: i18nService.t('typeCard'), value: CipherType.Card }, -- { name: i18nService.t('typeIdentity'), value: CipherType.Identity }, -- { name: i18nService.t('typeSecureNote'), value: CipherType.SecureNote }, -- ]; -- this.cardBrandOptions = [ -- { name: '-- ' + i18nService.t('select') + ' --', value: null }, -- { name: 'Visa', value: 'Visa' }, -- { name: 'Mastercard', value: 'Mastercard' }, -- { name: 'American Express', value: 'Amex' }, -- { name: 'Discover', value: 'Discover' }, -- { name: 'Diners Club', value: 'Diners Club' }, -- { name: 'JCB', value: 'JCB' }, -- { name: 'Maestro', value: 'Maestro' }, -- { name: 'UnionPay', value: 'UnionPay' }, -- { name: i18nService.t('other'), value: 'Other' }, -- ]; -- this.cardExpMonthOptions = [ -- { name: '-- ' + i18nService.t('select') + ' --', value: null }, -- { name: '01 - ' + i18nService.t('january'), value: '1' }, -- { name: '02 - ' + i18nService.t('february'), value: '2' }, -- { name: '03 - ' + i18nService.t('march'), value: '3' }, -- { name: '04 - ' + i18nService.t('april'), value: '4' }, -- { name: '05 - ' + i18nService.t('may'), value: '5' }, -- { name: '06 - ' + i18nService.t('june'), value: '6' }, -- { name: '07 - ' + i18nService.t('july'), value: '7' }, -- { name: '08 - ' + i18nService.t('august'), value: '8' }, -- { name: '09 - ' + i18nService.t('september'), value: '9' }, -- { name: '10 - ' + i18nService.t('october'), value: '10' }, -- { name: '11 - ' + i18nService.t('november'), value: '11' }, -- { name: '12 - ' + i18nService.t('december'), value: '12' }, -- ]; -- this.identityTitleOptions = [ -- { name: '-- ' + i18nService.t('select') + ' --', value: null }, -- { name: i18nService.t('mr'), value: i18nService.t('mr') }, -- { name: i18nService.t('mrs'), value: i18nService.t('mrs') }, -- { name: i18nService.t('ms'), value: i18nService.t('ms') }, -- { name: i18nService.t('dr'), value: i18nService.t('dr') }, -- ]; -- this.uriMatchOptions = [ -- { name: i18nService.t('defaultMatchDetection'), value: null }, -- { name: i18nService.t('baseDomain'), value: UriMatchType.Domain }, -- { name: i18nService.t('host'), value: UriMatchType.Host }, -- { name: i18nService.t('startsWith'), value: UriMatchType.StartsWith }, -- { name: i18nService.t('regEx'), value: UriMatchType.RegularExpression }, -- { name: i18nService.t('exact'), value: UriMatchType.Exact }, -- { name: i18nService.t('never'), value: UriMatchType.Never }, -- ]; -- this.autofillOnPageLoadOptions = [ -- { name: i18nService.t('autoFillOnPageLoadUseDefault'), value: null }, -- { name: i18nService.t('autoFillOnPageLoadYes'), value: true }, -- { name: i18nService.t('autoFillOnPageLoadNo'), value: false }, -- ]; -- } -- -- async ngOnInit() { -- await this.init(); -- } -- -- async init() { -- if (await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership)) { -- this.allowPersonal = false; -- } else { -- const myEmail = await this.userService.getEmail(); -- this.ownershipOptions.push({ name: myEmail, value: null }); -- } -- -- const orgs = await this.userService.getAllOrganizations(); -- orgs.sort(Utils.getSortFunction(this.i18nService, 'name')).forEach(o => { -- if (o.enabled && o.status === OrganizationUserStatusType.Confirmed) { -- this.ownershipOptions.push({ name: o.name, value: o.id }); -- } -- }); -- if (!this.allowPersonal) { -- this.organizationId = this.ownershipOptions[0].value; -- } -- -- this.writeableCollections = await this.loadCollections(); -- -- this.canUseReprompt = await this.passwordRepromptService.enabled(); -+ @Input() cloneMode: boolean = false; -+ @Input() folderId: string = null; -+ @Input() cipherId: string; -+ @Input() type: CipherType; -+ @Input() collectionIds: string[]; -+ @Input() organizationId: string = null; -+ @Output() onSavedCipher = new EventEmitter(); -+ @Output() onDeletedCipher = new EventEmitter(); -+ @Output() onRestoredCipher = new EventEmitter(); -+ @Output() onCancelled = new EventEmitter(); -+ @Output() onEditAttachments = new EventEmitter(); -+ @Output() onShareCipher = new EventEmitter(); -+ @Output() onEditCollections = new EventEmitter(); -+ @Output() onGeneratePassword = new EventEmitter(); -+ -+ editMode: boolean = false; -+ cipher: CipherView; -+ folders: FolderView[]; -+ collections: CollectionView[] = []; -+ title: string; -+ formPromise: Promise; -+ deletePromise: Promise; -+ restorePromise: Promise; -+ checkPasswordPromise: Promise; -+ showPassword: boolean = false; -+ showCardNumber: boolean = false; -+ showCardCode: boolean = false; -+ cipherType = CipherType; -+ typeOptions: any[]; -+ cardBrandOptions: any[]; -+ cardExpMonthOptions: any[]; -+ identityTitleOptions: any[]; -+ uriMatchOptions: any[]; -+ ownershipOptions: any[] = []; -+ autofillOnPageLoadOptions: any[]; -+ currentDate = new Date(); -+ allowPersonal = true; -+ reprompt: boolean = false; -+ canUseReprompt: boolean = true; -+ -+ protected writeableCollections: CollectionView[]; -+ private previousCipherId: string; -+ -+ constructor( -+ protected cipherService: CipherService, -+ protected folderService: FolderService, -+ protected i18nService: I18nService, -+ protected platformUtilsService: PlatformUtilsService, -+ protected auditService: AuditService, -+ protected stateService: StateService, -+ protected collectionService: CollectionService, -+ protected messagingService: MessagingService, -+ protected eventService: EventService, -+ protected policyService: PolicyService, -+ private logService: LogService, -+ protected passwordRepromptService: PasswordRepromptService, -+ private organizationService: OrganizationService -+ ) { -+ this.typeOptions = [ -+ { name: i18nService.t("typeLogin"), value: CipherType.Login }, -+ { name: i18nService.t("typeCard"), value: CipherType.Card }, -+ { name: i18nService.t("typeIdentity"), value: CipherType.Identity }, -+ { name: i18nService.t("typeSecureNote"), value: CipherType.SecureNote }, -+ ]; -+ this.cardBrandOptions = [ -+ { name: "-- " + i18nService.t("select") + " --", value: null }, -+ { name: "Visa", value: "Visa" }, -+ { name: "Mastercard", value: "Mastercard" }, -+ { name: "American Express", value: "Amex" }, -+ { name: "Discover", value: "Discover" }, -+ { name: "Diners Club", value: "Diners Club" }, -+ { name: "JCB", value: "JCB" }, -+ { name: "Maestro", value: "Maestro" }, -+ { name: "UnionPay", value: "UnionPay" }, -+ { name: i18nService.t("other"), value: "Other" }, -+ ]; -+ this.cardExpMonthOptions = [ -+ { name: "-- " + i18nService.t("select") + " --", value: null }, -+ { name: "01 - " + i18nService.t("january"), value: "1" }, -+ { name: "02 - " + i18nService.t("february"), value: "2" }, -+ { name: "03 - " + i18nService.t("march"), value: "3" }, -+ { name: "04 - " + i18nService.t("april"), value: "4" }, -+ { name: "05 - " + i18nService.t("may"), value: "5" }, -+ { name: "06 - " + i18nService.t("june"), value: "6" }, -+ { name: "07 - " + i18nService.t("july"), value: "7" }, -+ { name: "08 - " + i18nService.t("august"), value: "8" }, -+ { name: "09 - " + i18nService.t("september"), value: "9" }, -+ { name: "10 - " + i18nService.t("october"), value: "10" }, -+ { name: "11 - " + i18nService.t("november"), value: "11" }, -+ { name: "12 - " + i18nService.t("december"), value: "12" }, -+ ]; -+ this.identityTitleOptions = [ -+ { name: "-- " + i18nService.t("select") + " --", value: null }, -+ { name: i18nService.t("mr"), value: i18nService.t("mr") }, -+ { name: i18nService.t("mrs"), value: i18nService.t("mrs") }, -+ { name: i18nService.t("ms"), value: i18nService.t("ms") }, -+ { name: i18nService.t("dr"), value: i18nService.t("dr") }, -+ ]; -+ this.uriMatchOptions = [ -+ { name: i18nService.t("defaultMatchDetection"), value: null }, -+ { name: i18nService.t("baseDomain"), value: UriMatchType.Domain }, -+ { name: i18nService.t("host"), value: UriMatchType.Host }, -+ { name: i18nService.t("startsWith"), value: UriMatchType.StartsWith }, -+ { name: i18nService.t("regEx"), value: UriMatchType.RegularExpression }, -+ { name: i18nService.t("exact"), value: UriMatchType.Exact }, -+ { name: i18nService.t("never"), value: UriMatchType.Never }, -+ ]; -+ this.autofillOnPageLoadOptions = [ -+ { name: i18nService.t("autoFillOnPageLoadUseDefault"), value: null }, -+ { name: i18nService.t("autoFillOnPageLoadYes"), value: true }, -+ { name: i18nService.t("autoFillOnPageLoadNo"), value: false }, -+ ]; -+ } -+ -+ async ngOnInit() { -+ await this.init(); -+ } -+ -+ async init() { -+ if (this.ownershipOptions.length) { -+ this.ownershipOptions = []; - } -- -- async load() { -- this.editMode = this.cipherId != null; -- if (this.editMode) { -- this.editMode = true; -- if (this.cloneMode) { -- this.cloneMode = true; -- this.title = this.i18nService.t('addItem'); -- } else { -- this.title = this.i18nService.t('editItem'); -- } -- } else { -- this.title = this.i18nService.t('addItem'); -- } -- -- const addEditCipherInfo: any = await this.stateService.get('addEditCipherInfo'); -- if (addEditCipherInfo != null) { -- this.cipher = addEditCipherInfo.cipher; -- this.collectionIds = addEditCipherInfo.collectionIds; -- } -- await this.stateService.remove('addEditCipherInfo'); -- -- if (this.cipher == null) { -- if (this.editMode) { -- const cipher = await this.loadCipher(); -- this.cipher = await cipher.decrypt(); -- -- // Adjust Cipher Name if Cloning -- if (this.cloneMode) { -- this.cipher.name += ' - ' + this.i18nService.t('clone'); -- // If not allowing personal ownership, update cipher's org Id to prompt downstream changes -- if (this.cipher.organizationId == null && !this.allowPersonal) { -- this.cipher.organizationId = this.organizationId; -- } -- } -- } else { -- this.cipher = new CipherView(); -- this.cipher.organizationId = this.organizationId == null ? null : this.organizationId; -- this.cipher.folderId = this.folderId; -- this.cipher.type = this.type == null ? CipherType.Login : this.type; -- this.cipher.login = new LoginView(); -- this.cipher.login.uris = [new LoginUriView()]; -- this.cipher.card = new CardView(); -- this.cipher.identity = new IdentityView(); -- this.cipher.secureNote = new SecureNoteView(); -- this.cipher.secureNote.type = SecureNoteType.Generic; -- this.cipher.reprompt = CipherRepromptType.None; -- } -- } -- -- if (this.cipher != null && (!this.editMode || addEditCipherInfo != null || this.cloneMode)) { -- await this.organizationChanged(); -- if (this.collectionIds != null && this.collectionIds.length > 0 && this.collections.length > 0) { -- this.collections.forEach(c => { -- if (this.collectionIds.indexOf(c.id) > -1) { -- (c as any).checked = true; -- } -- }); -- } -- } -- -- this.folders = await this.folderService.getAllDecrypted(); -- -- if (this.editMode && this.previousCipherId !== this.cipherId) { -- this.eventService.collect(EventType.Cipher_ClientViewed, this.cipherId); -- } -- this.previousCipherId = this.cipherId; -- this.reprompt = this.cipher.reprompt !== CipherRepromptType.None; -+ if (await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership)) { -+ this.allowPersonal = false; -+ } else { -+ const myEmail = await this.stateService.getEmail(); -+ this.ownershipOptions.push({ name: myEmail, value: null }); - } - -- async submit(): Promise { -- if (this.cipher.isDeleted) { -- return this.restore(); -- } -- -- if (this.cipher.name == null || this.cipher.name === '') { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('nameRequired')); -- return false; -- } -+ const orgs = await this.organizationService.getAll(); -+ orgs.sort(Utils.getSortFunction(this.i18nService, "name")).forEach((o) => { -+ if (o.enabled && o.status === OrganizationUserStatusType.Confirmed) { -+ this.ownershipOptions.push({ name: o.name, value: o.id }); -+ } -+ }); -+ if (!this.allowPersonal) { -+ this.organizationId = this.ownershipOptions[0].value; -+ } - -- if ((!this.editMode || this.cloneMode) && !this.allowPersonal && this.cipher.organizationId == null) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('personalOwnershipSubmitError')); -- return false; -- } -+ this.writeableCollections = await this.loadCollections(); -+ -+ this.canUseReprompt = await this.passwordRepromptService.enabled(); -+ } -+ -+ async load() { -+ this.editMode = this.cipherId != null; -+ if (this.editMode) { -+ this.editMode = true; -+ if (this.cloneMode) { -+ this.cloneMode = true; -+ this.title = this.i18nService.t("addItem"); -+ } else { -+ this.title = this.i18nService.t("editItem"); -+ } -+ } else { -+ this.title = this.i18nService.t("addItem"); -+ } - -- if ((!this.editMode || this.cloneMode) && this.cipher.type === CipherType.Login && -- this.cipher.login.uris != null && this.cipher.login.uris.length === 1 && -- (this.cipher.login.uris[0].uri == null || this.cipher.login.uris[0].uri === '')) { -- this.cipher.login.uris = null; -- } -+ const addEditCipherInfo: any = await this.stateService.getAddEditCipherInfo(); -+ if (addEditCipherInfo != null) { -+ this.cipher = addEditCipherInfo.cipher; -+ this.collectionIds = addEditCipherInfo.collectionIds; -+ } -+ await this.stateService.setAddEditCipherInfo(null); - -- // Allows saving of selected collections during "Add" and "Clone" flows -- if ((!this.editMode || this.cloneMode) && this.cipher.organizationId != null) { -- this.cipher.collectionIds = this.collections == null ? [] : -- this.collections.filter(c => (c as any).checked).map(c => c.id); -- } -+ if (this.cipher == null) { -+ if (this.editMode) { -+ const cipher = await this.loadCipher(); -+ this.cipher = await cipher.decrypt(); - -- // Clear current Cipher Id to trigger "Add" cipher flow -+ // Adjust Cipher Name if Cloning - if (this.cloneMode) { -- this.cipher.id = null; -- } -- -- const cipher = await this.encryptCipher(); -- try { -- this.formPromise = this.saveCipher(cipher); -- await this.formPromise; -- this.cipher.id = cipher.id; -- this.platformUtilsService.showToast('success', null, -- this.i18nService.t(this.editMode && !this.cloneMode ? 'editedItem' : 'addedItem')); -- this.onSavedCipher.emit(this.cipher); -- this.messagingService.send(this.editMode && !this.cloneMode ? 'editedCipher' : 'addedCipher'); -- return true; -- } catch (e) { -- this.logService.error(e); -- } -- -- return false; -+ this.cipher.name += " - " + this.i18nService.t("clone"); -+ // If not allowing personal ownership, update cipher's org Id to prompt downstream changes -+ if (this.cipher.organizationId == null && !this.allowPersonal) { -+ this.cipher.organizationId = this.organizationId; -+ } -+ } -+ } else { -+ this.cipher = new CipherView(); -+ this.cipher.organizationId = this.organizationId == null ? null : this.organizationId; -+ this.cipher.folderId = this.folderId; -+ this.cipher.type = this.type == null ? CipherType.Login : this.type; -+ this.cipher.login = new LoginView(); -+ this.cipher.login.uris = [new LoginUriView()]; -+ this.cipher.card = new CardView(); -+ this.cipher.identity = new IdentityView(); -+ this.cipher.secureNote = new SecureNoteView(); -+ this.cipher.secureNote.type = SecureNoteType.Generic; -+ this.cipher.reprompt = CipherRepromptType.None; -+ } - } - -- addUri() { -- if (this.cipher.type !== CipherType.Login) { -- return; -- } -- -- if (this.cipher.login.uris == null) { -- this.cipher.login.uris = []; -- } -- -- this.cipher.login.uris.push(new LoginUriView()); -+ if (this.cipher != null && (!this.editMode || addEditCipherInfo != null || this.cloneMode)) { -+ await this.organizationChanged(); -+ if ( -+ this.collectionIds != null && -+ this.collectionIds.length > 0 && -+ this.collections.length > 0 -+ ) { -+ this.collections.forEach((c) => { -+ if (this.collectionIds.indexOf(c.id) > -1) { -+ (c as any).checked = true; -+ } -+ }); -+ } - } - -- removeUri(uri: LoginUriView) { -- if (this.cipher.type !== CipherType.Login || this.cipher.login.uris == null) { -- return; -- } -+ this.folders = await this.folderService.getAllDecrypted(); - -- const i = this.cipher.login.uris.indexOf(uri); -- if (i > -1) { -- this.cipher.login.uris.splice(i, 1); -- } -+ if (this.editMode && this.previousCipherId !== this.cipherId) { -+ this.eventService.collect(EventType.Cipher_ClientViewed, this.cipherId); - } -+ this.previousCipherId = this.cipherId; -+ this.reprompt = this.cipher.reprompt !== CipherRepromptType.None; -+ } - -- trackByFunction(index: number, item: any) { -- return index; -+ async submit(): Promise { -+ if (this.cipher.isDeleted) { -+ return this.restore(); - } - -- cancel() { -- this.onCancelled.emit(this.cipher); -+ if (this.cipher.name == null || this.cipher.name === "") { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("nameRequired") -+ ); -+ return false; - } - -- attachments() { -- this.onEditAttachments.emit(this.cipher); -+ if ( -+ (!this.editMode || this.cloneMode) && -+ !this.allowPersonal && -+ this.cipher.organizationId == null -+ ) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("personalOwnershipSubmitError") -+ ); -+ return false; - } - -- share() { -- this.onShareCipher.emit(this.cipher); -+ if ( -+ (!this.editMode || this.cloneMode) && -+ this.cipher.type === CipherType.Login && -+ this.cipher.login.uris != null && -+ this.cipher.login.uris.length === 1 && -+ (this.cipher.login.uris[0].uri == null || this.cipher.login.uris[0].uri === "") -+ ) { -+ this.cipher.login.uris = null; - } - -- editCollections() { -- this.onEditCollections.emit(this.cipher); -+ // Allows saving of selected collections during "Add" and "Clone" flows -+ if ((!this.editMode || this.cloneMode) && this.cipher.organizationId != null) { -+ this.cipher.collectionIds = -+ this.collections == null -+ ? [] -+ : this.collections.filter((c) => (c as any).checked).map((c) => c.id); - } - -- async delete(): Promise { -- const confirmed = await this.platformUtilsService.showDialog( -- this.i18nService.t(this.cipher.isDeleted ? 'permanentlyDeleteItemConfirmation' : 'deleteItemConfirmation'), -- this.i18nService.t('deleteItem'), this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); -- if (!confirmed) { -- return false; -- } -- -- try { -- this.deletePromise = this.deleteCipher(); -- await this.deletePromise; -- this.platformUtilsService.showToast('success', null, -- this.i18nService.t(this.cipher.isDeleted ? 'permanentlyDeletedItem' : 'deletedItem')); -- this.onDeletedCipher.emit(this.cipher); -- this.messagingService.send(this.cipher.isDeleted ? 'permanentlyDeletedCipher' : 'deletedCipher'); -- } catch (e) { -- this.logService.error(e); -- } -- -- return true; -+ // Clear current Cipher Id to trigger "Add" cipher flow -+ if (this.cloneMode) { -+ this.cipher.id = null; - } - -- async restore(): Promise { -- if (!this.cipher.isDeleted) { -- return false; -- } -+ const cipher = await this.encryptCipher(); -+ try { -+ this.formPromise = this.saveCipher(cipher); -+ await this.formPromise; -+ this.cipher.id = cipher.id; -+ this.platformUtilsService.showToast( -+ "success", -+ null, -+ this.i18nService.t(this.editMode && !this.cloneMode ? "editedItem" : "addedItem") -+ ); -+ this.onSavedCipher.emit(this.cipher); -+ this.messagingService.send(this.editMode && !this.cloneMode ? "editedCipher" : "addedCipher"); -+ return true; -+ } catch (e) { -+ this.logService.error(e); -+ } - -- const confirmed = await this.platformUtilsService.showDialog( -- this.i18nService.t('restoreItemConfirmation'), this.i18nService.t('restoreItem'), -- this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); -- if (!confirmed) { -- return false; -- } -+ return false; -+ } - -- try { -- this.restorePromise = this.restoreCipher(); -- await this.restorePromise; -- this.platformUtilsService.showToast('success', null, this.i18nService.t('restoredItem')); -- this.onRestoredCipher.emit(this.cipher); -- this.messagingService.send('restoredCipher'); -- } catch (e) { -- this.logService.error(e); -- } -+ addUri() { -+ if (this.cipher.type !== CipherType.Login) { -+ return; -+ } - -- return true; -+ if (this.cipher.login.uris == null) { -+ this.cipher.login.uris = []; - } - -- async generatePassword(): Promise { -- if (this.cipher.login != null && this.cipher.login.password != null && this.cipher.login.password.length) { -- const confirmed = await this.platformUtilsService.showDialog( -- this.i18nService.t('overwritePasswordConfirmation'), this.i18nService.t('overwritePassword'), -- this.i18nService.t('yes'), this.i18nService.t('no')); -- if (!confirmed) { -- return false; -- } -- } -+ this.cipher.login.uris.push(new LoginUriView()); -+ } - -- this.onGeneratePassword.emit(); -- return true; -+ removeUri(uri: LoginUriView) { -+ if (this.cipher.type !== CipherType.Login || this.cipher.login.uris == null) { -+ return; - } - -- togglePassword() { -- this.showPassword = !this.showPassword; -- document.getElementById('loginPassword').focus(); -- if (this.editMode && this.showPassword) { -- this.eventService.collect(EventType.Cipher_ClientToggledPasswordVisible, this.cipherId); -- } -+ const i = this.cipher.login.uris.indexOf(uri); -+ if (i > -1) { -+ this.cipher.login.uris.splice(i, 1); - } -- -- async toggleCardNumber() { -- this.showCardNumber = !this.showCardNumber; -- if (this.showCardNumber) { -- this.eventService.collect(EventType.Cipher_ClientToggledCardNumberVisible, this.cipherId); -- } -+ } -+ -+ trackByFunction(index: number, item: any) { -+ return index; -+ } -+ -+ cancel() { -+ this.onCancelled.emit(this.cipher); -+ } -+ -+ attachments() { -+ this.onEditAttachments.emit(this.cipher); -+ } -+ -+ share() { -+ this.onShareCipher.emit(this.cipher); -+ } -+ -+ editCollections() { -+ this.onEditCollections.emit(this.cipher); -+ } -+ -+ async delete(): Promise { -+ const confirmed = await this.platformUtilsService.showDialog( -+ this.i18nService.t( -+ this.cipher.isDeleted ? "permanentlyDeleteItemConfirmation" : "deleteItemConfirmation" -+ ), -+ this.i18nService.t("deleteItem"), -+ this.i18nService.t("yes"), -+ this.i18nService.t("no"), -+ "warning" -+ ); -+ if (!confirmed) { -+ return false; - } - -- toggleCardCode() { -- this.showCardCode = !this.showCardCode; -- document.getElementById('cardCode').focus(); -- if (this.editMode && this.showCardCode) { -- this.eventService.collect(EventType.Cipher_ClientToggledCardCodeVisible, this.cipherId); -- } -+ try { -+ this.deletePromise = this.deleteCipher(); -+ await this.deletePromise; -+ this.platformUtilsService.showToast( -+ "success", -+ null, -+ this.i18nService.t(this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem") -+ ); -+ this.onDeletedCipher.emit(this.cipher); -+ this.messagingService.send( -+ this.cipher.isDeleted ? "permanentlyDeletedCipher" : "deletedCipher" -+ ); -+ } catch (e) { -+ this.logService.error(e); - } - -- toggleUriOptions(uri: LoginUriView) { -- const u = (uri as any); -- u.showOptions = u.showOptions == null && uri.match != null ? false : !u.showOptions; -- } -+ return true; -+ } - -- loginUriMatchChanged(uri: LoginUriView) { -- const u = (uri as any); -- u.showOptions = u.showOptions == null ? true : u.showOptions; -+ async restore(): Promise { -+ if (!this.cipher.isDeleted) { -+ return false; - } - -- async organizationChanged() { -- if (this.writeableCollections != null) { -- this.writeableCollections.forEach(c => (c as any).checked = false); -- } -- if (this.cipher.organizationId != null) { -- this.collections = this.writeableCollections.filter(c => c.organizationId === this.cipher.organizationId); -- const org = await this.userService.getOrganization(this.cipher.organizationId); -- if (org != null) { -- this.cipher.organizationUseTotp = org.useTotp; -- } -- } else { -- this.collections = []; -- } -+ const confirmed = await this.platformUtilsService.showDialog( -+ this.i18nService.t("restoreItemConfirmation"), -+ this.i18nService.t("restoreItem"), -+ this.i18nService.t("yes"), -+ this.i18nService.t("no"), -+ "warning" -+ ); -+ if (!confirmed) { -+ return false; - } - -- async checkPassword() { -- if (this.checkPasswordPromise != null) { -- return; -- } -+ try { -+ this.restorePromise = this.restoreCipher(); -+ await this.restorePromise; -+ this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItem")); -+ this.onRestoredCipher.emit(this.cipher); -+ this.messagingService.send("restoredCipher"); -+ } catch (e) { -+ this.logService.error(e); -+ } - -- if (this.cipher.login == null || this.cipher.login.password == null || this.cipher.login.password === '') { -- return; -- } -+ return true; -+ } -+ -+ async generatePassword(): Promise { -+ if ( -+ this.cipher.login != null && -+ this.cipher.login.password != null && -+ this.cipher.login.password.length -+ ) { -+ const confirmed = await this.platformUtilsService.showDialog( -+ this.i18nService.t("overwritePasswordConfirmation"), -+ this.i18nService.t("overwritePassword"), -+ this.i18nService.t("yes"), -+ this.i18nService.t("no") -+ ); -+ if (!confirmed) { -+ return false; -+ } -+ } - -- this.checkPasswordPromise = this.auditService.passwordLeaked(this.cipher.login.password); -- const matches = await this.checkPasswordPromise; -- this.checkPasswordPromise = null; -+ this.onGeneratePassword.emit(); -+ return true; -+ } - -- if (matches > 0) { -- this.platformUtilsService.showToast('warning', null, -- this.i18nService.t('passwordExposed', matches.toString())); -- } else { -- this.platformUtilsService.showToast('success', null, this.i18nService.t('passwordSafe')); -- } -+ togglePassword() { -+ this.showPassword = !this.showPassword; -+ document.getElementById("loginPassword").focus(); -+ if (this.editMode && this.showPassword) { -+ this.eventService.collect(EventType.Cipher_ClientToggledPasswordVisible, this.cipherId); - } -+ } - -- repromptChanged() { -- this.reprompt = !this.reprompt; -- if (this.reprompt) { -- this.cipher.reprompt = CipherRepromptType.Password; -- } else { -- this.cipher.reprompt = CipherRepromptType.None; -- } -+ async toggleCardNumber() { -+ this.showCardNumber = !this.showCardNumber; -+ if (this.showCardNumber) { -+ this.eventService.collect(EventType.Cipher_ClientToggledCardNumberVisible, this.cipherId); - } -+ } - -- protected async loadCollections() { -- const allCollections = await this.collectionService.getAllDecrypted(); -- return allCollections.filter(c => !c.readOnly); -+ toggleCardCode() { -+ this.showCardCode = !this.showCardCode; -+ document.getElementById("cardCode").focus(); -+ if (this.editMode && this.showCardCode) { -+ this.eventService.collect(EventType.Cipher_ClientToggledCardCodeVisible, this.cipherId); - } -+ } - -- protected loadCipher() { -- return this.cipherService.get(this.cipherId); -- } -+ toggleUriOptions(uri: LoginUriView) { -+ const u = uri as any; -+ u.showOptions = u.showOptions == null && uri.match != null ? false : !u.showOptions; -+ } -+ -+ loginUriMatchChanged(uri: LoginUriView) { -+ const u = uri as any; -+ u.showOptions = u.showOptions == null ? true : u.showOptions; -+ } - -- protected encryptCipher() { -- return this.cipherService.encrypt(this.cipher); -+ async organizationChanged() { -+ if (this.writeableCollections != null) { -+ this.writeableCollections.forEach((c) => ((c as any).checked = false)); - } -+ if (this.cipher.organizationId != null) { -+ this.collections = this.writeableCollections.filter( -+ (c) => c.organizationId === this.cipher.organizationId -+ ); -+ const org = await this.organizationService.get(this.cipher.organizationId); -+ if (org != null) { -+ this.cipher.organizationUseTotp = org.useTotp; -+ } -+ } else { -+ this.collections = []; -+ } -+ } - -- protected saveCipher(cipher: Cipher) { -- return this.cipherService.saveWithServer(cipher); -+ async checkPassword() { -+ if (this.checkPasswordPromise != null) { -+ return; - } - -- protected deleteCipher() { -- return this.cipher.isDeleted ? this.cipherService.deleteWithServer(this.cipher.id) -- : this.cipherService.softDeleteWithServer(this.cipher.id); -+ if ( -+ this.cipher.login == null || -+ this.cipher.login.password == null || -+ this.cipher.login.password === "" -+ ) { -+ return; - } - -- protected restoreCipher() { -- return this.cipherService.restoreWithServer(this.cipher.id); -+ this.checkPasswordPromise = this.auditService.passwordLeaked(this.cipher.login.password); -+ const matches = await this.checkPasswordPromise; -+ this.checkPasswordPromise = null; -+ -+ if (matches > 0) { -+ this.platformUtilsService.showToast( -+ "warning", -+ null, -+ this.i18nService.t("passwordExposed", matches.toString()) -+ ); -+ } else { -+ this.platformUtilsService.showToast("success", null, this.i18nService.t("passwordSafe")); -+ } -+ } -+ -+ repromptChanged() { -+ this.reprompt = !this.reprompt; -+ if (this.reprompt) { -+ this.cipher.reprompt = CipherRepromptType.Password; -+ } else { -+ this.cipher.reprompt = CipherRepromptType.None; - } -+ } -+ -+ protected async loadCollections() { -+ const allCollections = await this.collectionService.getAllDecrypted(); -+ return allCollections.filter((c) => !c.readOnly); -+ } -+ -+ protected loadCipher() { -+ return this.cipherService.get(this.cipherId); -+ } -+ -+ protected encryptCipher() { -+ return this.cipherService.encrypt(this.cipher); -+ } -+ -+ protected saveCipher(cipher: Cipher) { -+ return this.cipherService.saveWithServer(cipher); -+ } -+ -+ protected deleteCipher() { -+ return this.cipher.isDeleted -+ ? this.cipherService.deleteWithServer(this.cipher.id) -+ : this.cipherService.softDeleteWithServer(this.cipher.id); -+ } -+ -+ protected restoreCipher() { -+ return this.cipherService.restoreWithServer(this.cipher.id); -+ } - } -diff --git a/jslib/angular/src/components/attachments.component.ts b/jslib/angular/src/components/attachments.component.ts -index dcbaf19b..bff508e2 100644 ---- a/jslib/angular/src/components/attachments.component.ts -+++ b/jslib/angular/src/components/attachments.component.ts -@@ -1,250 +1,291 @@ --import { -- Directive, -- EventEmitter, -- Input, -- OnInit, -- Output, --} from '@angular/core'; -- --import { ApiService } from 'jslib-common/abstractions/api.service'; --import { CipherService } from 'jslib-common/abstractions/cipher.service'; --import { CryptoService } from 'jslib-common/abstractions/crypto.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { LogService } from 'jslib-common/abstractions/log.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { UserService } from 'jslib-common/abstractions/user.service'; -- --import { Cipher } from 'jslib-common/models/domain/cipher'; --import { ErrorResponse } from 'jslib-common/models/response/errorResponse'; -- --import { AttachmentView } from 'jslib-common/models/view/attachmentView'; --import { CipherView } from 'jslib-common/models/view/cipherView'; -+import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; - --@Directive() --export class AttachmentsComponent implements OnInit { -- @Input() cipherId: string; -- @Output() onUploadedAttachment = new EventEmitter(); -- @Output() onDeletedAttachment = new EventEmitter(); -- @Output() onReuploadedAttachment = new EventEmitter(); -- -- cipher: CipherView; -- cipherDomain: Cipher; -- hasUpdatedKey: boolean; -- canAccessAttachments: boolean; -- formPromise: Promise; -- deletePromises: { [id: string]: Promise; } = {}; -- reuploadPromises: { [id: string]: Promise; } = {}; -- emergencyAccessId?: string = null; -- -- constructor(protected cipherService: CipherService, protected i18nService: I18nService, -- protected cryptoService: CryptoService, protected userService: UserService, -- protected platformUtilsService: PlatformUtilsService, protected apiService: ApiService, -- protected win: Window, private logService: LogService) { } -- -- async ngOnInit() { -- await this.init(); -- } -+import { ApiService } from "jslib-common/abstractions/api.service"; -+import { CipherService } from "jslib-common/abstractions/cipher.service"; -+import { CryptoService } from "jslib-common/abstractions/crypto.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; - -- async submit() { -- if (!this.hasUpdatedKey) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('updateKey')); -- return; -- } -+import { Cipher } from "jslib-common/models/domain/cipher"; -+import { ErrorResponse } from "jslib-common/models/response/errorResponse"; - -- const fileEl = document.getElementById('file') as HTMLInputElement; -- const files = fileEl.files; -- if (files == null || files.length === 0) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('selectFile')); -- return; -- } -+import { AttachmentView } from "jslib-common/models/view/attachmentView"; -+import { CipherView } from "jslib-common/models/view/cipherView"; - -- if (files[0].size > 524288000) { // 500 MB -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('maxFileSize')); -- return; -- } -+@Directive() -+export class AttachmentsComponent implements OnInit { -+ @Input() cipherId: string; -+ @Output() onUploadedAttachment = new EventEmitter(); -+ @Output() onDeletedAttachment = new EventEmitter(); -+ @Output() onReuploadedAttachment = new EventEmitter(); -+ -+ cipher: CipherView; -+ cipherDomain: Cipher; -+ hasUpdatedKey: boolean; -+ canAccessAttachments: boolean; -+ formPromise: Promise; -+ deletePromises: { [id: string]: Promise } = {}; -+ reuploadPromises: { [id: string]: Promise } = {}; -+ emergencyAccessId?: string = null; -+ -+ constructor( -+ protected cipherService: CipherService, -+ protected i18nService: I18nService, -+ protected cryptoService: CryptoService, -+ protected platformUtilsService: PlatformUtilsService, -+ protected apiService: ApiService, -+ protected win: Window, -+ protected logService: LogService, -+ protected stateService: StateService -+ ) {} -+ -+ async ngOnInit() { -+ await this.init(); -+ } -+ -+ async submit() { -+ if (!this.hasUpdatedKey) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("updateKey") -+ ); -+ return; -+ } - -- try { -- this.formPromise = this.saveCipherAttachment(files[0]); -- this.cipherDomain = await this.formPromise; -- this.cipher = await this.cipherDomain.decrypt(); -- this.platformUtilsService.showToast('success', null, this.i18nService.t('attachmentSaved')); -- this.onUploadedAttachment.emit(); -- } catch (e) { -- this.logService.error(e); -- } -+ const fileEl = document.getElementById("file") as HTMLInputElement; -+ const files = fileEl.files; -+ if (files == null || files.length === 0) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("selectFile") -+ ); -+ return; -+ } - -- // reset file input -- // ref: https://stackoverflow.com/a/20552042 -- fileEl.type = ''; -- fileEl.type = 'file'; -- fileEl.value = ''; -+ if (files[0].size > 524288000) { -+ // 500 MB -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("maxFileSize") -+ ); -+ return; - } - -- async delete(attachment: AttachmentView) { -- if (this.deletePromises[attachment.id] != null) { -- return; -- } -+ try { -+ this.formPromise = this.saveCipherAttachment(files[0]); -+ this.cipherDomain = await this.formPromise; -+ this.cipher = await this.cipherDomain.decrypt(); -+ this.platformUtilsService.showToast("success", null, this.i18nService.t("attachmentSaved")); -+ this.onUploadedAttachment.emit(); -+ } catch (e) { -+ this.logService.error(e); -+ } - -- const confirmed = await this.platformUtilsService.showDialog( -- this.i18nService.t('deleteAttachmentConfirmation'), this.i18nService.t('deleteAttachment'), -- this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); -- if (!confirmed) { -- return; -- } -+ // reset file input -+ // ref: https://stackoverflow.com/a/20552042 -+ fileEl.type = ""; -+ fileEl.type = "file"; -+ fileEl.value = ""; -+ } - -- try { -- this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id); -- await this.deletePromises[attachment.id]; -- this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedAttachment')); -- const i = this.cipher.attachments.indexOf(attachment); -- if (i > -1) { -- this.cipher.attachments.splice(i, 1); -- } -- } catch (e) { -- this.logService.error(e); -- } -+ async delete(attachment: AttachmentView) { -+ if (this.deletePromises[attachment.id] != null) { -+ return; -+ } - -- this.deletePromises[attachment.id] = null; -- this.onDeletedAttachment.emit(); -+ const confirmed = await this.platformUtilsService.showDialog( -+ this.i18nService.t("deleteAttachmentConfirmation"), -+ this.i18nService.t("deleteAttachment"), -+ this.i18nService.t("yes"), -+ this.i18nService.t("no"), -+ "warning" -+ ); -+ if (!confirmed) { -+ return; - } - -- async download(attachment: AttachmentView) { -- const a = (attachment as any); -- if (a.downloading) { -- return; -- } -+ try { -+ this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id); -+ await this.deletePromises[attachment.id]; -+ this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedAttachment")); -+ const i = this.cipher.attachments.indexOf(attachment); -+ if (i > -1) { -+ this.cipher.attachments.splice(i, 1); -+ } -+ } catch (e) { -+ this.logService.error(e); -+ } - -- if (!this.canAccessAttachments) { -- this.platformUtilsService.showToast('error', this.i18nService.t('premiumRequired'), -- this.i18nService.t('premiumRequiredDesc')); -- return; -- } -+ this.deletePromises[attachment.id] = null; -+ this.onDeletedAttachment.emit(); -+ } - -- let url: string; -- try { -- const attachmentDownloadResponse = await this.apiService.getAttachmentData(this.cipher.id, attachment.id, -- this.emergencyAccessId); -- url = attachmentDownloadResponse.url; -- } catch (e) { -- if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) { -- url = attachment.url; -- } else if (e instanceof ErrorResponse) { -- throw new Error((e as ErrorResponse).getSingleMessage()); -- } else { -- throw e; -- } -- } -+ async download(attachment: AttachmentView) { -+ const a = attachment as any; -+ if (a.downloading) { -+ return; -+ } - -- a.downloading = true; -- const response = await fetch(new Request(url, { cache: 'no-store' })); -- if (response.status !== 200) { -- this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred')); -- a.downloading = false; -- return; -- } -+ if (!this.canAccessAttachments) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("premiumRequired"), -+ this.i18nService.t("premiumRequiredDesc") -+ ); -+ return; -+ } - -- try { -- const buf = await response.arrayBuffer(); -- const key = attachment.key != null ? attachment.key : -- await this.cryptoService.getOrgKey(this.cipher.organizationId); -- const decBuf = await this.cryptoService.decryptFromBytes(buf, key); -- this.platformUtilsService.saveFile(this.win, decBuf, null, attachment.fileName); -- } catch (e) { -- this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred')); -- } -+ let url: string; -+ try { -+ const attachmentDownloadResponse = await this.apiService.getAttachmentData( -+ this.cipher.id, -+ attachment.id, -+ this.emergencyAccessId -+ ); -+ url = attachmentDownloadResponse.url; -+ } catch (e) { -+ if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) { -+ url = attachment.url; -+ } else if (e instanceof ErrorResponse) { -+ throw new Error((e as ErrorResponse).getSingleMessage()); -+ } else { -+ throw e; -+ } -+ } - -- a.downloading = false; -+ a.downloading = true; -+ const response = await fetch(new Request(url, { cache: "no-store" })); -+ if (response.status !== 200) { -+ this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); -+ a.downloading = false; -+ return; - } - -- protected async init() { -- this.cipherDomain = await this.loadCipher(); -- this.cipher = await this.cipherDomain.decrypt(); -+ try { -+ const buf = await response.arrayBuffer(); -+ const key = -+ attachment.key != null -+ ? attachment.key -+ : await this.cryptoService.getOrgKey(this.cipher.organizationId); -+ const decBuf = await this.cryptoService.decryptFromBytes(buf, key); -+ this.platformUtilsService.saveFile(this.win, decBuf, null, attachment.fileName); -+ } catch (e) { -+ this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); -+ } - -- this.hasUpdatedKey = await this.cryptoService.hasEncKey(); -- const canAccessPremium = await this.userService.canAccessPremium(); -- this.canAccessAttachments = canAccessPremium || this.cipher.organizationId != null; -+ a.downloading = false; -+ } -+ -+ protected async init() { -+ this.cipherDomain = await this.loadCipher(); -+ this.cipher = await this.cipherDomain.decrypt(); -+ -+ this.hasUpdatedKey = await this.cryptoService.hasEncKey(); -+ const canAccessPremium = await this.stateService.getCanAccessPremium(); -+ this.canAccessAttachments = canAccessPremium || this.cipher.organizationId != null; -+ -+ if (!this.canAccessAttachments) { -+ const confirmed = await this.platformUtilsService.showDialog( -+ this.i18nService.t("premiumRequiredDesc"), -+ this.i18nService.t("premiumRequired"), -+ this.i18nService.t("learnMore"), -+ this.i18nService.t("cancel") -+ ); -+ if (confirmed) { -+ this.platformUtilsService.launchUri("https://vault.bitwarden.com/#/?premium=purchase"); -+ } -+ } else if (!this.hasUpdatedKey) { -+ const confirmed = await this.platformUtilsService.showDialog( -+ this.i18nService.t("updateKey"), -+ this.i18nService.t("featureUnavailable"), -+ this.i18nService.t("learnMore"), -+ this.i18nService.t("cancel"), -+ "warning" -+ ); -+ if (confirmed) { -+ this.platformUtilsService.launchUri( -+ "https://help.bitwarden.com/article/update-encryption-key/" -+ ); -+ } -+ } -+ } - -- if (!this.canAccessAttachments) { -- const confirmed = await this.platformUtilsService.showDialog( -- this.i18nService.t('premiumRequiredDesc'), this.i18nService.t('premiumRequired'), -- this.i18nService.t('learnMore'), this.i18nService.t('cancel')); -- if (confirmed) { -- this.platformUtilsService.launchUri('https://vault.bitwarden.com/#/?premium=purchase'); -- } -- } else if (!this.hasUpdatedKey) { -- const confirmed = await this.platformUtilsService.showDialog( -- this.i18nService.t('updateKey'), this.i18nService.t('featureUnavailable'), -- this.i18nService.t('learnMore'), this.i18nService.t('cancel'), 'warning'); -- if (confirmed) { -- this.platformUtilsService.launchUri('https://help.bitwarden.com/article/update-encryption-key/'); -- } -- } -+ protected async reuploadCipherAttachment(attachment: AttachmentView, admin: boolean) { -+ const a = attachment as any; -+ if (attachment.key != null || a.downloading || this.reuploadPromises[attachment.id] != null) { -+ return; - } - -- protected async reuploadCipherAttachment(attachment: AttachmentView, admin: boolean) { -- const a = (attachment as any); -- if (attachment.key != null || a.downloading || this.reuploadPromises[attachment.id] != null) { -- return; -+ try { -+ this.reuploadPromises[attachment.id] = Promise.resolve().then(async () => { -+ // 1. Download -+ a.downloading = true; -+ const response = await fetch(new Request(attachment.url, { cache: "no-store" })); -+ if (response.status !== 200) { -+ this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); -+ a.downloading = false; -+ return; - } - - try { -- this.reuploadPromises[attachment.id] = Promise.resolve().then(async () => { -- // 1. Download -- a.downloading = true; -- const response = await fetch(new Request(attachment.url, { cache: 'no-store' })); -- if (response.status !== 200) { -- this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred')); -- a.downloading = false; -- return; -- } -- -- try { -- // 2. Resave -- const buf = await response.arrayBuffer(); -- const key = attachment.key != null ? attachment.key : -- await this.cryptoService.getOrgKey(this.cipher.organizationId); -- const decBuf = await this.cryptoService.decryptFromBytes(buf, key); -- this.cipherDomain = await this.cipherService.saveAttachmentRawWithServer( -- this.cipherDomain, attachment.fileName, decBuf, admin); -- this.cipher = await this.cipherDomain.decrypt(); -- -- // 3. Delete old -- this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id); -- await this.deletePromises[attachment.id]; -- const foundAttachment = this.cipher.attachments.filter(a2 => a2.id === attachment.id); -- if (foundAttachment.length > 0) { -- const i = this.cipher.attachments.indexOf(foundAttachment[0]); -- if (i > -1) { -- this.cipher.attachments.splice(i, 1); -- } -- } -- -- this.platformUtilsService.showToast('success', null, this.i18nService.t('attachmentSaved')); -- this.onReuploadedAttachment.emit(); -- } catch (e) { -- this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred')); -- } -- -- a.downloading = false; -- }); -- await this.reuploadPromises[attachment.id]; -+ // 2. Resave -+ const buf = await response.arrayBuffer(); -+ const key = -+ attachment.key != null -+ ? attachment.key -+ : await this.cryptoService.getOrgKey(this.cipher.organizationId); -+ const decBuf = await this.cryptoService.decryptFromBytes(buf, key); -+ this.cipherDomain = await this.cipherService.saveAttachmentRawWithServer( -+ this.cipherDomain, -+ attachment.fileName, -+ decBuf, -+ admin -+ ); -+ this.cipher = await this.cipherDomain.decrypt(); -+ -+ // 3. Delete old -+ this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id); -+ await this.deletePromises[attachment.id]; -+ const foundAttachment = this.cipher.attachments.filter((a2) => a2.id === attachment.id); -+ if (foundAttachment.length > 0) { -+ const i = this.cipher.attachments.indexOf(foundAttachment[0]); -+ if (i > -1) { -+ this.cipher.attachments.splice(i, 1); -+ } -+ } -+ -+ this.platformUtilsService.showToast( -+ "success", -+ null, -+ this.i18nService.t("attachmentSaved") -+ ); -+ this.onReuploadedAttachment.emit(); - } catch (e) { -- this.logService.error(e); -+ this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); - } -- } - -- protected loadCipher() { -- return this.cipherService.get(this.cipherId); -+ a.downloading = false; -+ }); -+ await this.reuploadPromises[attachment.id]; -+ } catch (e) { -+ this.logService.error(e); - } -+ } - -- protected saveCipherAttachment(file: File) { -- return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file); -- } -+ protected loadCipher() { -+ return this.cipherService.get(this.cipherId); -+ } - -- protected deleteCipherAttachment(attachmentId: string) { -- return this.cipherService.deleteAttachmentWithServer(this.cipher.id, attachmentId); -- } -+ protected saveCipherAttachment(file: File) { -+ return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file); -+ } -+ -+ protected deleteCipherAttachment(attachmentId: string) { -+ return this.cipherService.deleteAttachmentWithServer(this.cipher.id, attachmentId); -+ } - } -diff --git a/jslib/angular/src/components/avatar.component.ts b/jslib/angular/src/components/avatar.component.ts -index 1760c521..e00a68a8 100644 ---- a/jslib/angular/src/components/avatar.component.ts -+++ b/jslib/angular/src/components/avatar.component.ts -@@ -1,138 +1,143 @@ --import { -- Component, -- Input, -- OnChanges, -- OnInit, --} from '@angular/core'; --import { DomSanitizer } from '@angular/platform-browser'; -+import { Component, Input, OnChanges, OnInit } from "@angular/core"; -+import { DomSanitizer } from "@angular/platform-browser"; - --import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; --import { StateService } from 'jslib-common/abstractions/state.service'; -+import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; - --import { Utils } from 'jslib-common/misc/utils'; -+import { Utils } from "jslib-common/misc/utils"; - - @Component({ -- selector: 'app-avatar', -- template: '', -+ selector: "app-avatar", -+ template: -+ '", - }) - export class AvatarComponent implements OnChanges, OnInit { -- @Input() data: string; -- @Input() email: string; -- @Input() size = 45; -- @Input() charCount = 2; -- @Input() textColor = '#ffffff'; -- @Input() fontSize = 20; -- @Input() fontWeight = 300; -- @Input() dynamic = false; -- @Input() circle = false; -- -- src: string; -- -- constructor(public sanitizer: DomSanitizer, private cryptoFunctionService: CryptoFunctionService, -- private stateService: StateService) { } -- -- ngOnInit() { -- if (!this.dynamic) { -- this.generate(); -- } -+ @Input() data: string; -+ @Input() email: string; -+ @Input() size = 45; -+ @Input() charCount = 2; -+ @Input() textColor = "#ffffff"; -+ @Input() fontSize = 20; -+ @Input() fontWeight = 300; -+ @Input() dynamic = false; -+ @Input() circle = false; -+ -+ src: string; -+ -+ constructor( -+ public sanitizer: DomSanitizer, -+ private cryptoFunctionService: CryptoFunctionService, -+ private stateService: StateService -+ ) {} -+ -+ ngOnInit() { -+ if (!this.dynamic) { -+ this.generate(); - } -+ } - -- ngOnChanges() { -- if (this.dynamic) { -- this.generate(); -- } -+ ngOnChanges() { -+ if (this.dynamic) { -+ this.generate(); - } -- -- private async generate() { -- const enableGravatars = await this.stateService.get('enableGravatars'); -- if (enableGravatars && this.email != null) { -- const hashBytes = await this.cryptoFunctionService.hash(this.email.toLowerCase().trim(), 'md5'); -- const hash = Utils.fromBufferToHex(hashBytes).toLowerCase(); -- this.src = 'https://www.gravatar.com/avatar/' + hash + '?s=' + this.size + '&r=pg&d=retro'; -- } else { -- let chars: string = null; -- const upperData = this.data.toUpperCase(); -- -- if (this.charCount > 1) { -- chars = this.getFirstLetters(upperData, this.charCount); -- } -- if (chars == null) { -- chars = this.unicodeSafeSubstring(upperData, this.charCount); -- } -- -- // If the chars contain an emoji, only show it. -- if (chars.match(Utils.regexpEmojiPresentation)) { -- chars = chars.match(Utils.regexpEmojiPresentation)[0]; -- } -- -- const charObj = this.getCharText(chars); -- const color = this.stringToColor(upperData); -- const svg = this.getSvg(this.size, color); -- svg.appendChild(charObj); -- const html = window.document.createElement('div').appendChild(svg).outerHTML; -- const svgHtml = window.btoa(unescape(encodeURIComponent(html))); -- this.src = 'data:image/svg+xml;base64,' + svgHtml; -- } -- } -- -- private stringToColor(str: string): string { -- let hash = 0; -- for (let i = 0; i < str.length; i++) { -- // tslint:disable-next-line -- hash = str.charCodeAt(i) + ((hash << 5) - hash); -- } -- let color = '#'; -- for (let i = 0; i < 3; i++) { -- // tslint:disable-next-line -- const value = (hash >> (i * 8)) & 0xFF; -- color += ('00' + value.toString(16)).substr(-2); -- } -- return color; -+ } -+ -+ private async generate() { -+ const enableGravatars = await this.stateService.getEnableGravitars(); -+ if (enableGravatars && this.email != null) { -+ const hashBytes = await this.cryptoFunctionService.hash( -+ this.email.toLowerCase().trim(), -+ "md5" -+ ); -+ const hash = Utils.fromBufferToHex(hashBytes).toLowerCase(); -+ this.src = "https://www.gravatar.com/avatar/" + hash + "?s=" + this.size + "&r=pg&d=retro"; -+ } else { -+ let chars: string = null; -+ const upperData = this.data.toUpperCase(); -+ -+ if (this.charCount > 1) { -+ chars = this.getFirstLetters(upperData, this.charCount); -+ } -+ if (chars == null) { -+ chars = this.unicodeSafeSubstring(upperData, this.charCount); -+ } -+ -+ // If the chars contain an emoji, only show it. -+ if (chars.match(Utils.regexpEmojiPresentation)) { -+ chars = chars.match(Utils.regexpEmojiPresentation)[0]; -+ } -+ -+ const charObj = this.getCharText(chars); -+ const color = this.stringToColor(upperData); -+ const svg = this.getSvg(this.size, color); -+ svg.appendChild(charObj); -+ const html = window.document.createElement("div").appendChild(svg).outerHTML; -+ const svgHtml = window.btoa(unescape(encodeURIComponent(html))); -+ this.src = "data:image/svg+xml;base64," + svgHtml; - } -+ } - -- private getFirstLetters(data: string, count: number): string { -- const parts = data.split(' '); -- if (parts.length > 1) { -- let text = ''; -- for (let i = 0; i < count; i++) { -- text += this.unicodeSafeSubstring(parts[i], 1); -- } -- return text; -- } -- return null; -+ private stringToColor(str: string): string { -+ let hash = 0; -+ for (let i = 0; i < str.length; i++) { -+ // tslint:disable-next-line -+ hash = str.charCodeAt(i) + ((hash << 5) - hash); - } -- -- private getSvg(size: number, color: string): HTMLElement { -- const svgTag = window.document.createElement('svg'); -- svgTag.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); -- svgTag.setAttribute('pointer-events', 'none'); -- svgTag.setAttribute('width', size.toString()); -- svgTag.setAttribute('height', size.toString()); -- svgTag.style.backgroundColor = color; -- svgTag.style.width = size + 'px'; -- svgTag.style.height = size + 'px'; -- return svgTag; -+ let color = "#"; -+ for (let i = 0; i < 3; i++) { -+ // tslint:disable-next-line -+ const value = (hash >> (i * 8)) & 0xff; -+ color += ("00" + value.toString(16)).substr(-2); - } -- -- private getCharText(character: string): HTMLElement { -- const textTag = window.document.createElement('text'); -- textTag.setAttribute('text-anchor', 'middle'); -- textTag.setAttribute('y', '50%'); -- textTag.setAttribute('x', '50%'); -- textTag.setAttribute('dy', '0.35em'); -- textTag.setAttribute('pointer-events', 'auto'); -- textTag.setAttribute('fill', this.textColor); -- textTag.setAttribute('font-family', '"Open Sans","Helvetica Neue",Helvetica,Arial,' + -- 'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"'); -- textTag.textContent = character; -- textTag.style.fontWeight = this.fontWeight.toString(); -- textTag.style.fontSize = this.fontSize + 'px'; -- return textTag; -- } -- -- private unicodeSafeSubstring(str: string, count: number) { -- const characters = str.match(/./ug); -- return characters != null ? characters.slice(0, count).join('') : ''; -+ return color; -+ } -+ -+ private getFirstLetters(data: string, count: number): string { -+ const parts = data.split(" "); -+ if (parts.length > 1) { -+ let text = ""; -+ for (let i = 0; i < count; i++) { -+ text += this.unicodeSafeSubstring(parts[i], 1); -+ } -+ return text; - } -+ return null; -+ } -+ -+ private getSvg(size: number, color: string): HTMLElement { -+ const svgTag = window.document.createElement("svg"); -+ svgTag.setAttribute("xmlns", "http://www.w3.org/2000/svg"); -+ svgTag.setAttribute("pointer-events", "none"); -+ svgTag.setAttribute("width", size.toString()); -+ svgTag.setAttribute("height", size.toString()); -+ svgTag.style.backgroundColor = color; -+ svgTag.style.width = size + "px"; -+ svgTag.style.height = size + "px"; -+ return svgTag; -+ } -+ -+ private getCharText(character: string): HTMLElement { -+ const textTag = window.document.createElement("text"); -+ textTag.setAttribute("text-anchor", "middle"); -+ textTag.setAttribute("y", "50%"); -+ textTag.setAttribute("x", "50%"); -+ textTag.setAttribute("dy", "0.35em"); -+ textTag.setAttribute("pointer-events", "auto"); -+ textTag.setAttribute("fill", this.textColor); -+ textTag.setAttribute( -+ "font-family", -+ '"Open Sans","Helvetica Neue",Helvetica,Arial,' + -+ 'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"' -+ ); -+ textTag.textContent = character; -+ textTag.style.fontWeight = this.fontWeight.toString(); -+ textTag.style.fontSize = this.fontSize + "px"; -+ return textTag; -+ } -+ -+ private unicodeSafeSubstring(str: string, count: number) { -+ const characters = str.match(/./gu); -+ return characters != null ? characters.slice(0, count).join("") : ""; -+ } - } -diff --git a/jslib/angular/src/components/callout.component.html b/jslib/angular/src/components/callout.component.html -index 53fc6647..a049d5cb 100644 ---- a/jslib/angular/src/components/callout.component.html -+++ b/jslib/angular/src/components/callout.component.html -@@ -1,27 +1,35 @@ --
--

-- -- {{title}} --

--
-- {{enforcedPolicyMessage}} --
    --
  • -- {{'policyInEffectMinComplexity' | i18n : getPasswordScoreAlertDisplay()}} --
  • --
  • -- {{'policyInEffectMinLength' | i18n : enforcedPolicyOptions?.minLength.toString()}} --
  • --
  • -- {{'policyInEffectUppercase' | i18n}}
  • --
  • -- {{'policyInEffectLowercase' | i18n}}
  • --
  • -- {{'policyInEffectNumbers' | i18n}}
  • --
  • -- {{'policyInEffectSpecial' | i18n : '!@#$%^&*'}}
  • --
--
-- -+
-+

-+ -+ {{ title }} -+

-+
-+ {{ enforcedPolicyMessage }} -+
    -+
  • -+ {{ "policyInEffectMinComplexity" | i18n: getPasswordScoreAlertDisplay() }} -+
  • -+
  • -+ {{ "policyInEffectMinLength" | i18n: enforcedPolicyOptions?.minLength.toString() }} -+
  • -+
  • -+ {{ "policyInEffectUppercase" | i18n }} -+
  • -+
  • -+ {{ "policyInEffectLowercase" | i18n }} -+
  • -+
  • -+ {{ "policyInEffectNumbers" | i18n }} -+
  • -+
  • -+ {{ "policyInEffectSpecial" | i18n: "!@#$%^&*" }} -+
  • -+
-+
-+ -
-diff --git a/jslib/angular/src/components/callout.component.ts b/jslib/angular/src/components/callout.component.ts -index 566d017a..b9685f0e 100644 ---- a/jslib/angular/src/components/callout.component.ts -+++ b/jslib/angular/src/components/callout.component.ts -@@ -1,83 +1,79 @@ --import { -- Component, -- Input, -- OnInit, --} from '@angular/core'; -+import { Component, Input, OnInit } from "@angular/core"; - --import { I18nService } from 'jslib-common/abstractions/i18n.service'; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; - --import { MasterPasswordPolicyOptions } from 'jslib-common/models/domain/masterPasswordPolicyOptions'; -+import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions"; - - @Component({ -- selector: 'app-callout', -- templateUrl: 'callout.component.html', -+ 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; -+ @Input() type = "info"; -+ @Input() icon: string; -+ @Input() title: string; -+ @Input() clickable: boolean; -+ @Input() enforcedPolicyOptions: MasterPasswordPolicyOptions; -+ @Input() enforcedPolicyMessage: string; -+ @Input() useAlertRole = false; - -- calloutStyle: string; -+ calloutStyle: string; - -- constructor(private i18nService: I18nService) { } -+ constructor(private i18nService: I18nService) {} - -- ngOnInit() { -- this.calloutStyle = this.type; -+ ngOnInit() { -+ this.calloutStyle = this.type; - -- if (this.enforcedPolicyMessage === undefined) { -- this.enforcedPolicyMessage = this.i18nService.t('masterPasswordPolicyInEffect'); -- } -+ 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 = 'fa-warning'; -- } -- } else if (this.type === 'error') { -- this.calloutStyle = 'danger'; -- if (this.title === undefined) { -- this.title = this.i18nService.t('error'); -- } -- if (this.icon === undefined) { -- this.icon = 'fa-bolt'; -- } -- } else if (this.type === 'tip') { -- this.calloutStyle = 'success'; -- if (this.title === undefined) { -- this.title = this.i18nService.t('tip'); -- } -- if (this.icon === undefined) { -- this.icon = 'fa-lightbulb-o'; -- } -- } -+ 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 ''; -- } -+ 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 + ')'; -+ 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 + ")"; -+ } - } -diff --git a/jslib/angular/src/components/captchaProtected.component.ts b/jslib/angular/src/components/captchaProtected.component.ts -index f2c92d0f..d13f7151 100644 ---- a/jslib/angular/src/components/captchaProtected.component.ts -+++ b/jslib/angular/src/components/captchaProtected.component.ts -@@ -1,47 +1,55 @@ --import { Directive, Input } from '@angular/core'; -+import { Directive, Input } 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/abstractions/environment.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; - --import { CaptchaIFrame } from 'jslib-common/misc/captcha_iframe'; -+import { CaptchaIFrame } from "jslib-common/misc/captcha_iframe"; - --import { Utils } from 'jslib-common/misc/utils'; -+import { Utils } from "jslib-common/misc/utils"; - - @Directive() - export abstract class CaptchaProtectedComponent { -- @Input() captchaSiteKey: string = null; -- captchaToken: string = null; -- captcha: CaptchaIFrame; -- -- constructor(protected environmentService: EnvironmentService, protected i18nService: I18nService, -- protected platformUtilsService: PlatformUtilsService) { } -- -- async setupCaptcha() { -- const webVaultUrl = this.environmentService.getWebVaultUrl(); -- -- this.captcha = new CaptchaIFrame(window, webVaultUrl, -- this.i18nService, (token: string) => { -- this.captchaToken = token; -- }, (error: string) => { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), error); -- }, (info: string) => { -- this.platformUtilsService.showToast('info', this.i18nService.t('info'), info); -- } -- ); -+ @Input() captchaSiteKey: string = null; -+ captchaToken: string = null; -+ captcha: CaptchaIFrame; -+ -+ constructor( -+ protected environmentService: EnvironmentService, -+ protected i18nService: I18nService, -+ protected platformUtilsService: PlatformUtilsService -+ ) {} -+ -+ async setupCaptcha() { -+ const webVaultUrl = this.environmentService.getWebVaultUrl(); -+ -+ this.captcha = new CaptchaIFrame( -+ window, -+ webVaultUrl, -+ this.i18nService, -+ (token: string) => { -+ this.captchaToken = token; -+ }, -+ (error: string) => { -+ this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), error); -+ }, -+ (info: string) => { -+ this.platformUtilsService.showToast("info", this.i18nService.t("info"), info); -+ } -+ ); -+ } -+ -+ showCaptcha() { -+ return !Utils.isNullOrWhitespace(this.captchaSiteKey); -+ } -+ -+ protected handleCaptchaRequired(response: { captchaSiteKey: string }): boolean { -+ if (Utils.isNullOrWhitespace(response.captchaSiteKey)) { -+ return false; - } - -- showCaptcha() { -- return !Utils.isNullOrWhitespace(this.captchaSiteKey); -- } -- -- protected handleCaptchaRequired(response: { captchaSiteKey: string; }): boolean { -- if (Utils.isNullOrWhitespace(response.captchaSiteKey)) { -- return false; -- } -- -- this.captchaSiteKey = response.captchaSiteKey; -- this.captcha.init(response.captchaSiteKey); -- return true; -- } -+ this.captchaSiteKey = response.captchaSiteKey; -+ this.captcha.init(response.captchaSiteKey); -+ return true; -+ } - } -diff --git a/jslib/angular/src/components/change-password.component.ts b/jslib/angular/src/components/change-password.component.ts -index 5af79115..2431ece2 100644 ---- a/jslib/angular/src/components/change-password.component.ts -+++ b/jslib/angular/src/components/change-password.component.ts -@@ -1,152 +1,197 @@ --import { Directive, OnInit } from '@angular/core'; -+import { Directive, OnInit } from "@angular/core"; - --import { CryptoService } from 'jslib-common/abstractions/crypto.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { MessagingService } from 'jslib-common/abstractions/messaging.service'; --import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { PolicyService } from 'jslib-common/abstractions/policy.service'; --import { UserService } from 'jslib-common/abstractions/user.service'; -+import { CryptoService } from "jslib-common/abstractions/crypto.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { MessagingService } from "jslib-common/abstractions/messaging.service"; -+import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { PolicyService } from "jslib-common/abstractions/policy.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; - --import { EncString } from 'jslib-common/models/domain/encString'; --import { MasterPasswordPolicyOptions } from 'jslib-common/models/domain/masterPasswordPolicyOptions'; --import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey'; -+import { EncString } from "jslib-common/models/domain/encString"; -+import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions"; -+import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey"; - --import { KdfType } from 'jslib-common/enums/kdfType'; -+import { KdfType } from "jslib-common/enums/kdfType"; - - @Directive() - export class ChangePasswordComponent implements OnInit { -- masterPassword: string; -- masterPasswordRetype: string; -- formPromise: Promise; -- masterPasswordScore: number; -- enforcedPolicyOptions: MasterPasswordPolicyOptions; -- -- protected email: string; -- protected kdf: KdfType; -- protected kdfIterations: number; -- -- private masterPasswordStrengthTimeout: any; -- -- constructor(protected i18nService: I18nService, protected cryptoService: CryptoService, -- protected messagingService: MessagingService, protected userService: UserService, -- protected passwordGenerationService: PasswordGenerationService, -- protected platformUtilsService: PlatformUtilsService, protected policyService: PolicyService) { } -- -- async ngOnInit() { -- this.email = await this.userService.getEmail(); -- this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(); -+ masterPassword: string; -+ masterPasswordRetype: string; -+ formPromise: Promise; -+ masterPasswordScore: number; -+ enforcedPolicyOptions: MasterPasswordPolicyOptions; -+ -+ protected email: string; -+ protected kdf: KdfType; -+ protected kdfIterations: number; -+ -+ private masterPasswordStrengthTimeout: any; -+ -+ constructor( -+ protected i18nService: I18nService, -+ protected cryptoService: CryptoService, -+ protected messagingService: MessagingService, -+ protected passwordGenerationService: PasswordGenerationService, -+ protected platformUtilsService: PlatformUtilsService, -+ protected policyService: PolicyService, -+ protected stateService: StateService -+ ) {} -+ -+ async ngOnInit() { -+ this.email = await this.stateService.getEmail(); -+ this.enforcedPolicyOptions ??= await this.policyService.getMasterPasswordPolicyOptions(); -+ } -+ -+ async submit() { -+ if (!(await this.strongPassword())) { -+ return; - } - -- async submit() { -- if (!await this.strongPassword()) { -- return; -- } -- -- if (!await this.setupSubmitActions()) { -- return; -- } -- -- const email = await this.userService.getEmail(); -- if (this.kdf == null) { -- this.kdf = await this.userService.getKdf(); -- } -- if (this.kdfIterations == null) { -- this.kdfIterations = await this.userService.getKdfIterations(); -- } -- const key = await this.cryptoService.makeKey(this.masterPassword, email.trim().toLowerCase(), -- this.kdf, this.kdfIterations); -- const masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key); -- -- let encKey: [SymmetricCryptoKey, EncString] = null; -- const existingEncKey = await this.cryptoService.getEncKey(); -- if (existingEncKey == null) { -- encKey = await this.cryptoService.makeEncKey(key); -- } else { -- encKey = await this.cryptoService.remakeEncKey(key); -- } -- -- await this.performSubmitActions(masterPasswordHash, key, encKey); -+ if (!(await this.setupSubmitActions())) { -+ return; - } - -- async setupSubmitActions(): Promise { -- // Override in sub-class -- // Can be used for additional validation and/or other processes the should occur before changing passwords -- return true; -+ const email = await this.stateService.getEmail(); -+ if (this.kdf == null) { -+ this.kdf = await this.stateService.getKdfType(); - } -- -- async performSubmitActions(masterPasswordHash: string, key: SymmetricCryptoKey, -- encKey: [SymmetricCryptoKey, EncString]) { -- // Override in sub-class -+ if (this.kdfIterations == null) { -+ this.kdfIterations = await this.stateService.getKdfIterations(); -+ } -+ const key = await this.cryptoService.makeKey( -+ this.masterPassword, -+ email.trim().toLowerCase(), -+ this.kdf, -+ this.kdfIterations -+ ); -+ const masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key); -+ -+ let encKey: [SymmetricCryptoKey, EncString] = null; -+ const existingEncKey = await this.cryptoService.getEncKey(); -+ if (existingEncKey == null) { -+ encKey = await this.cryptoService.makeEncKey(key); -+ } else { -+ encKey = await this.cryptoService.remakeEncKey(key); - } - -- async strongPassword(): Promise { -- if (this.masterPassword == null || this.masterPassword === '') { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('masterPassRequired')); -- return false; -- } -- if (this.masterPassword.length < 8) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('masterPassLength')); -- return false; -- } -- if (this.masterPassword !== this.masterPasswordRetype) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('masterPassDoesntMatch')); -- return false; -- } -- -- const strengthResult = this.passwordGenerationService.passwordStrength(this.masterPassword, -- this.getPasswordStrengthUserInput()); -- -- if (this.enforcedPolicyOptions != null && -- !this.policyService.evaluateMasterPassword( -- strengthResult.score, -- this.masterPassword, -- this.enforcedPolicyOptions)) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('masterPasswordPolicyRequirementsNotMet')); -- return false; -- } -- -- if (strengthResult != null && strengthResult.score < 3) { -- const result = await this.platformUtilsService.showDialog(this.i18nService.t('weakMasterPasswordDesc'), -- this.i18nService.t('weakMasterPassword'), this.i18nService.t('yes'), this.i18nService.t('no'), -- 'warning'); -- if (!result) { -- return false; -- } -- } -- -- return true; -+ await this.performSubmitActions(masterPasswordHash, key, encKey); -+ } -+ -+ async setupSubmitActions(): Promise { -+ // Override in sub-class -+ // Can be used for additional validation and/or other processes the should occur before changing passwords -+ return true; -+ } -+ -+ async performSubmitActions( -+ masterPasswordHash: string, -+ key: SymmetricCryptoKey, -+ encKey: [SymmetricCryptoKey, EncString] -+ ) { -+ // Override in sub-class -+ } -+ -+ async strongPassword(): Promise { -+ if (this.masterPassword == null || this.masterPassword === "") { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("masterPassRequired") -+ ); -+ return false; -+ } -+ if (this.masterPassword.length < 8) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("masterPassLength") -+ ); -+ return false; -+ } -+ if (this.masterPassword !== this.masterPasswordRetype) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("masterPassDoesntMatch") -+ ); -+ return false; - } - -- updatePasswordStrength() { -- if (this.masterPasswordStrengthTimeout != null) { -- clearTimeout(this.masterPasswordStrengthTimeout); -- } -- this.masterPasswordStrengthTimeout = setTimeout(() => { -- const strengthResult = this.passwordGenerationService.passwordStrength(this.masterPassword, -- this.getPasswordStrengthUserInput()); -- this.masterPasswordScore = strengthResult == null ? null : strengthResult.score; -- }, 300); -+ const strengthResult = this.passwordGenerationService.passwordStrength( -+ this.masterPassword, -+ this.getPasswordStrengthUserInput() -+ ); -+ -+ if ( -+ this.enforcedPolicyOptions != null && -+ !this.policyService.evaluateMasterPassword( -+ strengthResult.score, -+ this.masterPassword, -+ this.enforcedPolicyOptions -+ ) -+ ) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("masterPasswordPolicyRequirementsNotMet") -+ ); -+ return false; - } - -- async logOut() { -- const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('logOutConfirmation'), -- this.i18nService.t('logOut'), this.i18nService.t('logOut'), this.i18nService.t('cancel')); -- if (confirmed) { -- this.messagingService.send('logout'); -- } -+ if (strengthResult != null && strengthResult.score < 3) { -+ const result = await this.platformUtilsService.showDialog( -+ this.i18nService.t("weakMasterPasswordDesc"), -+ this.i18nService.t("weakMasterPassword"), -+ this.i18nService.t("yes"), -+ this.i18nService.t("no"), -+ "warning" -+ ); -+ if (!result) { -+ return false; -+ } - } - -- private getPasswordStrengthUserInput() { -- let userInput: string[] = []; -- const atPosition = this.email.indexOf('@'); -- if (atPosition > -1) { -- userInput = userInput.concat(this.email.substr(0, atPosition).trim().toLowerCase().split(/[^A-Za-z0-9]/)); -- } -- return userInput; -+ return true; -+ } -+ -+ updatePasswordStrength() { -+ if (this.masterPasswordStrengthTimeout != null) { -+ clearTimeout(this.masterPasswordStrengthTimeout); -+ } -+ this.masterPasswordStrengthTimeout = setTimeout(() => { -+ const strengthResult = this.passwordGenerationService.passwordStrength( -+ this.masterPassword, -+ this.getPasswordStrengthUserInput() -+ ); -+ this.masterPasswordScore = strengthResult == null ? null : strengthResult.score; -+ }, 300); -+ } -+ -+ async logOut() { -+ const confirmed = await this.platformUtilsService.showDialog( -+ this.i18nService.t("logOutConfirmation"), -+ this.i18nService.t("logOut"), -+ this.i18nService.t("logOut"), -+ this.i18nService.t("cancel") -+ ); -+ if (confirmed) { -+ this.messagingService.send("logout"); -+ } -+ } -+ -+ private getPasswordStrengthUserInput() { -+ let userInput: string[] = []; -+ const atPosition = this.email.indexOf("@"); -+ if (atPosition > -1) { -+ userInput = userInput.concat( -+ this.email -+ .substr(0, atPosition) -+ .trim() -+ .toLowerCase() -+ .split(/[^A-Za-z0-9]/) -+ ); - } -+ return userInput; -+ } - } -diff --git a/jslib/angular/src/components/ciphers.component.ts b/jslib/angular/src/components/ciphers.component.ts -index 092eef22..3f724bb5 100644 ---- a/jslib/angular/src/components/ciphers.component.ts -+++ b/jslib/angular/src/components/ciphers.component.ts -@@ -1,95 +1,94 @@ --import { -- Directive, -- EventEmitter, -- Input, -- Output, --} from '@angular/core'; -+import { Directive, EventEmitter, Input, Output } from "@angular/core"; - --import { SearchService } from 'jslib-common/abstractions/search.service'; -+import { SearchService } from "jslib-common/abstractions/search.service"; - --import { CipherView } from 'jslib-common/models/view/cipherView'; -+import { CipherView } from "jslib-common/models/view/cipherView"; - - @Directive() - export class CiphersComponent { -- @Input() activeCipherId: string = null; -- @Output() onCipherClicked = new EventEmitter(); -- @Output() onCipherRightClicked = new EventEmitter(); -- @Output() onAddCipher = new EventEmitter(); -- @Output() onAddCipherOptions = new EventEmitter(); -- -- loaded: boolean = false; -- ciphers: CipherView[] = []; -- searchText: string; -- searchPlaceholder: string = null; -- filter: (cipher: CipherView) => boolean = null; -- deleted: boolean = false; -- -- protected searchPending = false; -- -- private searchTimeout: any = null; -- -- constructor(protected searchService: SearchService) { } -- -- async load(filter: (cipher: CipherView) => boolean = null, deleted: boolean = false) { -- this.deleted = deleted || false; -- await this.applyFilter(filter); -- this.loaded = true; -- } -- -- async reload(filter: (cipher: CipherView) => boolean = null, deleted: boolean = false) { -- this.loaded = false; -- this.ciphers = []; -- await this.load(filter, deleted); -- } -- -- async refresh() { -- await this.reload(this.filter, this.deleted); -- } -- -- async applyFilter(filter: (cipher: CipherView) => boolean = null) { -- this.filter = filter; -- await this.search(null); -- } -- -- async search(timeout: number = null, indexedCiphers?: CipherView[]) { -- this.searchPending = false; -- if (this.searchTimeout != null) { -- clearTimeout(this.searchTimeout); -- } -- if (timeout == null) { -- await this.doSearch(indexedCiphers); -- return; -- } -- this.searchPending = true; -- this.searchTimeout = setTimeout(async () => { -- await this.doSearch(indexedCiphers); -- this.searchPending = false; -- }, timeout); -+ @Input() activeCipherId: string = null; -+ @Output() onCipherClicked = new EventEmitter(); -+ @Output() onCipherRightClicked = new EventEmitter(); -+ @Output() onAddCipher = new EventEmitter(); -+ @Output() onAddCipherOptions = new EventEmitter(); -+ -+ loaded: boolean = false; -+ ciphers: CipherView[] = []; -+ searchText: string; -+ searchPlaceholder: string = null; -+ filter: (cipher: CipherView) => boolean = null; -+ deleted: boolean = false; -+ -+ protected searchPending = false; -+ -+ private searchTimeout: any = null; -+ -+ constructor(protected searchService: SearchService) {} -+ -+ async load(filter: (cipher: CipherView) => boolean = null, deleted: boolean = false) { -+ this.deleted = deleted || false; -+ await this.applyFilter(filter); -+ this.loaded = true; -+ } -+ -+ async reload(filter: (cipher: CipherView) => boolean = null, deleted: boolean = false) { -+ this.loaded = false; -+ this.ciphers = []; -+ await this.load(filter, deleted); -+ } -+ -+ async refresh() { -+ await this.reload(this.filter, this.deleted); -+ } -+ -+ async applyFilter(filter: (cipher: CipherView) => boolean = null) { -+ this.filter = filter; -+ await this.search(null); -+ } -+ -+ async search(timeout: number = null, indexedCiphers?: CipherView[]) { -+ this.searchPending = false; -+ if (this.searchTimeout != null) { -+ clearTimeout(this.searchTimeout); - } -- -- selectCipher(cipher: CipherView) { -- this.onCipherClicked.emit(cipher); -- } -- -- rightClickCipher(cipher: CipherView) { -- this.onCipherRightClicked.emit(cipher); -- } -- -- addCipher() { -- this.onAddCipher.emit(); -- } -- -- addCipherOptions() { -- this.onAddCipherOptions.emit(); -- } -- -- isSearching() { -- return !this.searchPending && this.searchService.isSearchable(this.searchText); -- } -- -- protected deletedFilter: (cipher: CipherView) => boolean = c => c.isDeleted === this.deleted; -- -- protected async doSearch(indexedCiphers?: CipherView[]) { -- this.ciphers = await this.searchService.searchCiphers(this.searchText, [this.filter, this.deletedFilter], indexedCiphers); -+ if (timeout == null) { -+ await this.doSearch(indexedCiphers); -+ return; - } -+ this.searchPending = true; -+ this.searchTimeout = setTimeout(async () => { -+ await this.doSearch(indexedCiphers); -+ this.searchPending = false; -+ }, timeout); -+ } -+ -+ selectCipher(cipher: CipherView) { -+ this.onCipherClicked.emit(cipher); -+ } -+ -+ rightClickCipher(cipher: CipherView) { -+ this.onCipherRightClicked.emit(cipher); -+ } -+ -+ addCipher() { -+ this.onAddCipher.emit(); -+ } -+ -+ addCipherOptions() { -+ this.onAddCipherOptions.emit(); -+ } -+ -+ isSearching() { -+ return !this.searchPending && this.searchService.isSearchable(this.searchText); -+ } -+ -+ protected deletedFilter: (cipher: CipherView) => boolean = (c) => c.isDeleted === this.deleted; -+ -+ protected async doSearch(indexedCiphers?: CipherView[]) { -+ this.ciphers = await this.searchService.searchCiphers( -+ this.searchText, -+ [this.filter, this.deletedFilter], -+ indexedCiphers -+ ); -+ } - } -diff --git a/jslib/angular/src/components/collections.component.ts b/jslib/angular/src/components/collections.component.ts -index 29a846c6..8e55f5e2 100644 ---- a/jslib/angular/src/components/collections.component.ts -+++ b/jslib/angular/src/components/collections.component.ts -@@ -1,90 +1,94 @@ --import { -- Directive, -- EventEmitter, -- Input, -- OnInit, -- Output, --} from '@angular/core'; -+import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; - --import { CipherService } from 'jslib-common/abstractions/cipher.service'; --import { CollectionService } from 'jslib-common/abstractions/collection.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { LogService } from 'jslib-common/abstractions/log.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -+import { CipherService } from "jslib-common/abstractions/cipher.service"; -+import { CollectionService } from "jslib-common/abstractions/collection.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; - --import { CipherView } from 'jslib-common/models/view/cipherView'; --import { CollectionView } from 'jslib-common/models/view/collectionView'; -+import { CipherView } from "jslib-common/models/view/cipherView"; -+import { CollectionView } from "jslib-common/models/view/collectionView"; - --import { Cipher } from 'jslib-common/models/domain/cipher'; -+import { Cipher } from "jslib-common/models/domain/cipher"; - - @Directive() - export class CollectionsComponent implements OnInit { -- @Input() cipherId: string; -- @Input() allowSelectNone = false; -- @Output() onSavedCollections = new EventEmitter(); -+ @Input() cipherId: string; -+ @Input() allowSelectNone = false; -+ @Output() onSavedCollections = new EventEmitter(); - -- formPromise: Promise; -- cipher: CipherView; -- collectionIds: string[]; -- collections: CollectionView[] = []; -+ formPromise: Promise; -+ cipher: CipherView; -+ collectionIds: string[]; -+ collections: CollectionView[] = []; - -- protected cipherDomain: Cipher; -+ protected cipherDomain: Cipher; - -- constructor(protected collectionService: CollectionService, protected platformUtilsService: PlatformUtilsService, -- protected i18nService: I18nService, protected cipherService: CipherService, private logService: LogService) { } -+ constructor( -+ protected collectionService: CollectionService, -+ protected platformUtilsService: PlatformUtilsService, -+ protected i18nService: I18nService, -+ protected cipherService: CipherService, -+ private logService: LogService -+ ) {} - -- async ngOnInit() { -- await this.load(); -- } -+ async ngOnInit() { -+ await this.load(); -+ } - -- async load() { -- this.cipherDomain = await this.loadCipher(); -- this.collectionIds = this.loadCipherCollections(); -- this.cipher = await this.cipherDomain.decrypt(); -- this.collections = await this.loadCollections(); -+ async load() { -+ this.cipherDomain = await this.loadCipher(); -+ this.collectionIds = this.loadCipherCollections(); -+ this.cipher = await this.cipherDomain.decrypt(); -+ this.collections = await this.loadCollections(); - -- this.collections.forEach(c => (c as any).checked = false); -- if (this.collectionIds != null) { -- this.collections.forEach(c => { -- (c as any).checked = this.collectionIds != null && this.collectionIds.indexOf(c.id) > -1; -- }); -- } -+ this.collections.forEach((c) => ((c as any).checked = false)); -+ if (this.collectionIds != null) { -+ this.collections.forEach((c) => { -+ (c as any).checked = this.collectionIds != null && this.collectionIds.indexOf(c.id) > -1; -+ }); - } -+ } - -- async submit() { -- const selectedCollectionIds = this.collections -- .filter(c => !!(c as any).checked) -- .map(c => c.id); -- if (!this.allowSelectNone && selectedCollectionIds.length === 0) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('selectOneCollection')); -- return; -- } -- this.cipherDomain.collectionIds = selectedCollectionIds; -- try { -- this.formPromise = this.saveCollections(); -- await this.formPromise; -- this.onSavedCollections.emit(); -- this.platformUtilsService.showToast('success', null, this.i18nService.t('editedItem')); -- } catch (e) { -- this.logService.error(e); -- } -+ async submit() { -+ const selectedCollectionIds = this.collections -+ .filter((c) => !!(c as any).checked) -+ .map((c) => c.id); -+ if (!this.allowSelectNone && selectedCollectionIds.length === 0) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("selectOneCollection") -+ ); -+ return; - } -- -- protected loadCipher() { -- return this.cipherService.get(this.cipherId); -+ this.cipherDomain.collectionIds = selectedCollectionIds; -+ try { -+ this.formPromise = this.saveCollections(); -+ await this.formPromise; -+ this.onSavedCollections.emit(); -+ this.platformUtilsService.showToast("success", null, this.i18nService.t("editedItem")); -+ } catch (e) { -+ this.logService.error(e); - } -+ } - -- protected loadCipherCollections() { -- return this.cipherDomain.collectionIds; -- } -+ protected loadCipher() { -+ return this.cipherService.get(this.cipherId); -+ } - -- protected async loadCollections() { -- const allCollections = await this.collectionService.getAllDecrypted(); -- return allCollections.filter(c => !c.readOnly && c.organizationId === this.cipher.organizationId); -- } -+ protected loadCipherCollections() { -+ return this.cipherDomain.collectionIds; -+ } - -- protected saveCollections() { -- return this.cipherService.saveCollectionsWithServer(this.cipherDomain); -- } -+ protected async loadCollections() { -+ const allCollections = await this.collectionService.getAllDecrypted(); -+ return allCollections.filter( -+ (c) => !c.readOnly && c.organizationId === this.cipher.organizationId -+ ); -+ } -+ -+ protected saveCollections() { -+ return this.cipherService.saveCollectionsWithServer(this.cipherDomain); -+ } - } -diff --git a/jslib/angular/src/components/environment.component.ts b/jslib/angular/src/components/environment.component.ts -index 6b131ba6..8b7689de 100644 ---- a/jslib/angular/src/components/environment.component.ts -+++ b/jslib/angular/src/components/environment.component.ts -@@ -1,65 +1,63 @@ --import { -- Directive, -- EventEmitter, -- Output, --} from '@angular/core'; -+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/abstractions/environment.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; - - @Directive() - export class EnvironmentComponent { -- @Output() onSaved = new EventEmitter(); -- -- iconsUrl: string; -- identityUrl: string; -- apiUrl: string; -- webVaultUrl: string; -- notificationsUrl: string; -- baseUrl: string; -- showCustom = false; -- -- constructor(protected platformUtilsService: PlatformUtilsService, protected environmentService: EnvironmentService, -- protected i18nService: I18nService) { -- -- const urls = this.environmentService.getUrls(); -- -- this.baseUrl = urls.base || ''; -- this.webVaultUrl = urls.webVault || ''; -- this.apiUrl = urls.api || ''; -- this.identityUrl = urls.identity || ''; -- this.iconsUrl = urls.icons || ''; -- this.notificationsUrl = urls.notifications || ''; -- } -- -- async submit() { -- const resUrls = await this.environmentService.setUrls({ -- base: this.baseUrl, -- api: this.apiUrl, -- identity: this.identityUrl, -- webVault: this.webVaultUrl, -- icons: this.iconsUrl, -- notifications: this.notificationsUrl, -- }); -- -- // re-set urls since service can change them, ex: prefixing https:// -- this.baseUrl = resUrls.base; -- this.apiUrl = resUrls.api; -- this.identityUrl = resUrls.identity; -- this.webVaultUrl = resUrls.webVault; -- this.iconsUrl = resUrls.icons; -- this.notificationsUrl = resUrls.notifications; -- -- this.platformUtilsService.showToast('success', null, this.i18nService.t('environmentSaved')); -- this.saved(); -- } -- -- toggleCustom() { -- this.showCustom = !this.showCustom; -- } -- -- protected saved() { -- this.onSaved.emit(); -- } -+ @Output() onSaved = new EventEmitter(); -+ -+ iconsUrl: string; -+ identityUrl: string; -+ apiUrl: string; -+ webVaultUrl: string; -+ notificationsUrl: string; -+ baseUrl: string; -+ showCustom = false; -+ -+ constructor( -+ protected platformUtilsService: PlatformUtilsService, -+ protected environmentService: EnvironmentService, -+ protected i18nService: I18nService -+ ) { -+ const urls = this.environmentService.getUrls(); -+ -+ this.baseUrl = urls.base || ""; -+ this.webVaultUrl = urls.webVault || ""; -+ this.apiUrl = urls.api || ""; -+ this.identityUrl = urls.identity || ""; -+ this.iconsUrl = urls.icons || ""; -+ this.notificationsUrl = urls.notifications || ""; -+ } -+ -+ async submit() { -+ const resUrls = await this.environmentService.setUrls({ -+ base: this.baseUrl, -+ api: this.apiUrl, -+ identity: this.identityUrl, -+ webVault: this.webVaultUrl, -+ icons: this.iconsUrl, -+ notifications: this.notificationsUrl, -+ }); -+ -+ // re-set urls since service can change them, ex: prefixing https:// -+ this.baseUrl = resUrls.base; -+ this.apiUrl = resUrls.api; -+ this.identityUrl = resUrls.identity; -+ this.webVaultUrl = resUrls.webVault; -+ this.iconsUrl = resUrls.icons; -+ this.notificationsUrl = resUrls.notifications; -+ -+ this.platformUtilsService.showToast("success", null, this.i18nService.t("environmentSaved")); -+ this.saved(); -+ } -+ -+ toggleCustom() { -+ this.showCustom = !this.showCustom; -+ } -+ -+ protected saved() { -+ this.onSaved.emit(); -+ } - } -diff --git a/jslib/angular/src/components/export.component.ts b/jslib/angular/src/components/export.component.ts -index 716f9ef0..17a751fc 100644 ---- a/jslib/angular/src/components/export.component.ts -+++ b/jslib/angular/src/components/export.component.ts -@@ -1,140 +1,156 @@ --import { -- Directive, -- EventEmitter, -- OnInit, -- Output, --} from '@angular/core'; --import { FormBuilder } from '@angular/forms'; -- --import { CryptoService } from 'jslib-common/abstractions/crypto.service'; --import { EventService } from 'jslib-common/abstractions/event.service'; --import { ExportService } from 'jslib-common/abstractions/export.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { LogService } from 'jslib-common/abstractions/log.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { PolicyService } from 'jslib-common/abstractions/policy.service'; --import { UserVerificationService } from 'jslib-common/abstractions/userVerification.service'; -- --import { EventType } from 'jslib-common/enums/eventType'; --import { PolicyType } from 'jslib-common/enums/policyType'; -+import { Directive, EventEmitter, OnInit, Output } from "@angular/core"; -+import { FormBuilder } from "@angular/forms"; -+ -+import { CryptoService } from "jslib-common/abstractions/crypto.service"; -+import { EventService } from "jslib-common/abstractions/event.service"; -+import { ExportService } from "jslib-common/abstractions/export.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { PolicyService } from "jslib-common/abstractions/policy.service"; -+import { UserVerificationService } from "jslib-common/abstractions/userVerification.service"; -+ -+import { EventType } from "jslib-common/enums/eventType"; -+import { PolicyType } from "jslib-common/enums/policyType"; - - @Directive() - export class ExportComponent implements OnInit { -- @Output() onSaved = new EventEmitter(); -- -- formPromise: Promise; -- disabledByPolicy: boolean = false; -- -- exportForm = this.fb.group({ -- format: ['json'], -- secret: [''], -- }); -- -- formatOptions = [ -- { name: '.json', value: 'json' }, -- { name: '.csv', value: 'csv' }, -- { name: '.json (Encrypted)', value: 'encrypted_json' }, -- ]; -- -- constructor(protected cryptoService: CryptoService, protected i18nService: I18nService, -- protected platformUtilsService: PlatformUtilsService, protected exportService: ExportService, -- protected eventService: EventService, private policyService: PolicyService, protected win: Window, -- private logService: LogService, private userVerificationService: UserVerificationService, -- private fb: FormBuilder) { } -- -- async ngOnInit() { -- await this.checkExportDisabled(); -+ @Output() onSaved = new EventEmitter(); -+ -+ formPromise: Promise; -+ disabledByPolicy: boolean = false; -+ -+ exportForm = this.formBuilder.group({ -+ format: ["json"], -+ secret: [""], -+ }); -+ -+ formatOptions = [ -+ { name: ".json", value: "json" }, -+ { name: ".csv", value: "csv" }, -+ { name: ".json (Encrypted)", value: "encrypted_json" }, -+ ]; -+ -+ constructor( -+ protected cryptoService: CryptoService, -+ protected i18nService: I18nService, -+ protected platformUtilsService: PlatformUtilsService, -+ protected exportService: ExportService, -+ protected eventService: EventService, -+ private policyService: PolicyService, -+ protected win: Window, -+ private logService: LogService, -+ private userVerificationService: UserVerificationService, -+ private formBuilder: FormBuilder -+ ) {} -+ -+ async ngOnInit() { -+ await this.checkExportDisabled(); -+ } -+ -+ async checkExportDisabled() { -+ this.disabledByPolicy = await this.policyService.policyAppliesToUser( -+ PolicyType.DisablePersonalVaultExport -+ ); -+ if (this.disabledByPolicy) { -+ this.exportForm.disable(); - } -- -- async checkExportDisabled() { -- this.disabledByPolicy = await this.policyService.policyAppliesToUser(PolicyType.DisablePersonalVaultExport); -- if (this.disabledByPolicy) { -- this.exportForm.disable(); -- } -- } -- -- get encryptedFormat() { -- return this.format === 'encrypted_json'; -+ } -+ -+ get encryptedFormat() { -+ return this.format === "encrypted_json"; -+ } -+ -+ async submit() { -+ if (this.disabledByPolicy) { -+ this.platformUtilsService.showToast( -+ "error", -+ null, -+ this.i18nService.t("personalVaultExportPolicyInEffect") -+ ); -+ return; - } - -- async submit() { -- if (this.disabledByPolicy) { -- this.platformUtilsService.showToast('error', null, this.i18nService.t('personalVaultExportPolicyInEffect')); -- return; -- } -- -- const acceptedWarning = await this.warningDialog(); -- if (!acceptedWarning) { -- return; -- } -- -- const secret = this.exportForm.get('secret').value; -- try { -- await this.userVerificationService.verifyUser(secret); -- } catch (e) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), e.message); -- return; -- } -- -- try { -- this.formPromise = this.getExportData(); -- const data = await this.formPromise; -- this.downloadFile(data); -- this.saved(); -- await this.collectEvent(); -- this.exportForm.get('secret').setValue(''); -- } catch (e) { -- this.logService.error(e); -- } -+ const acceptedWarning = await this.warningDialog(); -+ if (!acceptedWarning) { -+ return; - } - -- async warningDialog() { -- if (this.encryptedFormat) { -- return await this.platformUtilsService.showDialog( -- '

' + this.i18nService.t('encExportKeyWarningDesc') + -- '

' + this.i18nService.t('encExportAccountWarningDesc'), -- this.i18nService.t('confirmVaultExport'), this.i18nService.t('exportVault'), -- this.i18nService.t('cancel'), 'warning', -- true); -- } else { -- return await this.platformUtilsService.showDialog( -- this.i18nService.t('exportWarningDesc'), -- this.i18nService.t('confirmVaultExport'), this.i18nService.t('exportVault'), -- this.i18nService.t('cancel'), 'warning'); -- } -+ const secret = this.exportForm.get("secret").value; -+ try { -+ await this.userVerificationService.verifyUser(secret); -+ } catch (e) { -+ this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message); -+ return; - } - -- protected saved() { -- this.onSaved.emit(); -+ try { -+ this.formPromise = this.getExportData(); -+ const data = await this.formPromise; -+ this.downloadFile(data); -+ this.saved(); -+ await this.collectEvent(); -+ this.exportForm.get("secret").setValue(""); -+ } catch (e) { -+ this.logService.error(e); - } -- -- protected getExportData() { -- return this.exportService.getExport(this.format); -+ } -+ -+ async warningDialog() { -+ if (this.encryptedFormat) { -+ return await this.platformUtilsService.showDialog( -+ "

" + -+ this.i18nService.t("encExportKeyWarningDesc") + -+ "

" + -+ this.i18nService.t("encExportAccountWarningDesc"), -+ this.i18nService.t("confirmVaultExport"), -+ this.i18nService.t("exportVault"), -+ this.i18nService.t("cancel"), -+ "warning", -+ true -+ ); -+ } else { -+ return await this.platformUtilsService.showDialog( -+ this.i18nService.t("exportWarningDesc"), -+ this.i18nService.t("confirmVaultExport"), -+ this.i18nService.t("exportVault"), -+ this.i18nService.t("cancel"), -+ "warning" -+ ); - } -- -- protected getFileName(prefix?: string) { -- let extension = this.format; -- if (this.format === 'encrypted_json') { -- if (prefix == null) { -- prefix = 'encrypted'; -- } else { -- prefix = 'encrypted_' + prefix; -- } -- extension = 'json'; -- } -- return this.exportService.getFileName(prefix, extension); -+ } -+ -+ protected saved() { -+ this.onSaved.emit(); -+ } -+ -+ protected getExportData() { -+ return this.exportService.getExport(this.format); -+ } -+ -+ protected getFileName(prefix?: string) { -+ let extension = this.format; -+ if (this.format === "encrypted_json") { -+ if (prefix == null) { -+ prefix = "encrypted"; -+ } else { -+ prefix = "encrypted_" + prefix; -+ } -+ extension = "json"; - } -+ return this.exportService.getFileName(prefix, extension); -+ } - -- protected async collectEvent(): Promise { -- await this.eventService.collect(EventType.User_ClientExportedVault); -- } -+ protected async collectEvent(): Promise { -+ await this.eventService.collect(EventType.User_ClientExportedVault); -+ } - -- get format() { -- return this.exportForm.get('format').value; -- } -+ get format() { -+ return this.exportForm.get("format").value; -+ } - -- private downloadFile(csv: string): void { -- const fileName = this.getFileName(); -- this.platformUtilsService.saveFile(this.win, csv, { type: 'text/plain' }, fileName); -- } -+ private downloadFile(csv: string): void { -+ const fileName = this.getFileName(); -+ this.platformUtilsService.saveFile(this.win, csv, { type: "text/plain" }, fileName); -+ } - } -diff --git a/jslib/angular/src/components/folder-add-edit.component.ts b/jslib/angular/src/components/folder-add-edit.component.ts -index 554c743f..a3f0707b 100644 ---- a/jslib/angular/src/components/folder-add-edit.component.ts -+++ b/jslib/angular/src/components/folder-add-edit.component.ts -@@ -1,89 +1,97 @@ --import { -- Directive, -- EventEmitter, -- Input, -- OnInit, -- Output, --} from '@angular/core'; -+import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; - --import { FolderService } from 'jslib-common/abstractions/folder.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { LogService } from 'jslib-common/abstractions/log.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -+import { FolderService } from "jslib-common/abstractions/folder.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; - --import { FolderView } from 'jslib-common/models/view/folderView'; -+import { FolderView } from "jslib-common/models/view/folderView"; - - @Directive() - export class FolderAddEditComponent implements OnInit { -- @Input() folderId: string; -- @Output() onSavedFolder = new EventEmitter(); -- @Output() onDeletedFolder = new EventEmitter(); -+ @Input() folderId: string; -+ @Output() onSavedFolder = new EventEmitter(); -+ @Output() onDeletedFolder = new EventEmitter(); - -- editMode: boolean = false; -- folder: FolderView = new FolderView(); -- title: string; -- formPromise: Promise; -- deletePromise: Promise; -+ editMode: boolean = false; -+ folder: FolderView = new FolderView(); -+ title: string; -+ formPromise: Promise; -+ deletePromise: Promise; - -- constructor(protected folderService: FolderService, protected i18nService: I18nService, -- protected platformUtilsService: PlatformUtilsService, private logService: LogService) { } -+ constructor( -+ protected folderService: FolderService, -+ protected i18nService: I18nService, -+ protected platformUtilsService: PlatformUtilsService, -+ private logService: LogService -+ ) {} - -- async ngOnInit() { -- await this.init(); -- } -- -- async submit(): Promise { -- if (this.folder.name == null || this.folder.name === '') { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('nameRequired')); -- return false; -- } -+ async ngOnInit() { -+ await this.init(); -+ } - -- try { -- const folder = await this.folderService.encrypt(this.folder); -- this.formPromise = this.folderService.saveWithServer(folder); -- await this.formPromise; -- this.platformUtilsService.showToast('success', null, -- this.i18nService.t(this.editMode ? 'editedFolder' : 'addedFolder')); -- this.onSavedFolder.emit(this.folder); -- return true; -- } catch (e) { -- this.logService.error(e); -- } -+ async submit(): Promise { -+ if (this.folder.name == null || this.folder.name === "") { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("nameRequired") -+ ); -+ return false; -+ } - -- return false; -+ try { -+ const folder = await this.folderService.encrypt(this.folder); -+ this.formPromise = this.folderService.saveWithServer(folder); -+ await this.formPromise; -+ this.platformUtilsService.showToast( -+ "success", -+ null, -+ this.i18nService.t(this.editMode ? "editedFolder" : "addedFolder") -+ ); -+ this.onSavedFolder.emit(this.folder); -+ return true; -+ } catch (e) { -+ this.logService.error(e); - } - -- async delete(): Promise { -- const confirmed = await this.platformUtilsService.showDialog( -- this.i18nService.t('deleteFolderConfirmation'), this.i18nService.t('deleteFolder'), -- this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); -- if (!confirmed) { -- return false; -- } -+ return false; -+ } - -- try { -- this.deletePromise = this.folderService.deleteWithServer(this.folder.id); -- await this.deletePromise; -- this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedFolder')); -- this.onDeletedFolder.emit(this.folder); -- } catch (e) { -- this.logService.error(e); -- } -+ async delete(): Promise { -+ const confirmed = await this.platformUtilsService.showDialog( -+ this.i18nService.t("deleteFolderConfirmation"), -+ this.i18nService.t("deleteFolder"), -+ this.i18nService.t("yes"), -+ this.i18nService.t("no"), -+ "warning" -+ ); -+ if (!confirmed) { -+ return false; -+ } - -- return true; -+ try { -+ this.deletePromise = this.folderService.deleteWithServer(this.folder.id); -+ await this.deletePromise; -+ this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedFolder")); -+ this.onDeletedFolder.emit(this.folder); -+ } catch (e) { -+ this.logService.error(e); - } - -- protected async init() { -- this.editMode = this.folderId != null; -+ return true; -+ } -+ -+ protected async init() { -+ this.editMode = this.folderId != null; - -- if (this.editMode) { -- this.editMode = true; -- this.title = this.i18nService.t('editFolder'); -- const folder = await this.folderService.get(this.folderId); -- this.folder = await folder.decrypt(); -- } else { -- this.title = this.i18nService.t('addFolder'); -- } -+ if (this.editMode) { -+ this.editMode = true; -+ this.title = this.i18nService.t("editFolder"); -+ const folder = await this.folderService.get(this.folderId); -+ this.folder = await folder.decrypt(); -+ } else { -+ this.title = this.i18nService.t("addFolder"); - } -+ } - } -diff --git a/jslib/angular/src/components/groupings.component.ts b/jslib/angular/src/components/groupings.component.ts -index 1c3c27a1..e8a97c6f 100644 ---- a/jslib/angular/src/components/groupings.component.ts -+++ b/jslib/angular/src/components/groupings.component.ts -@@ -1,168 +1,160 @@ --import { -- Directive, -- EventEmitter, -- Input, -- Output, --} from '@angular/core'; -+import { Directive, EventEmitter, Input, Output } from "@angular/core"; - --import { CipherType } from 'jslib-common/enums/cipherType'; -+import { CipherType } from "jslib-common/enums/cipherType"; - --import { CollectionView } from 'jslib-common/models/view/collectionView'; --import { FolderView } from 'jslib-common/models/view/folderView'; -+import { CollectionView } from "jslib-common/models/view/collectionView"; -+import { FolderView } from "jslib-common/models/view/folderView"; - --import { TreeNode } from 'jslib-common/models/domain/treeNode'; -+import { TreeNode } from "jslib-common/models/domain/treeNode"; - --import { CollectionService } from 'jslib-common/abstractions/collection.service'; --import { FolderService } from 'jslib-common/abstractions/folder.service'; --import { StorageService } from 'jslib-common/abstractions/storage.service'; --import { UserService } from 'jslib-common/abstractions/user.service'; -- --import { ConstantsService } from 'jslib-common/services/constants.service'; -+import { CollectionService } from "jslib-common/abstractions/collection.service"; -+import { FolderService } from "jslib-common/abstractions/folder.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; - - @Directive() - export class GroupingsComponent { -- @Input() showFolders = true; -- @Input() showCollections = true; -- @Input() showFavorites = true; -- @Input() showTrash = true; -- -- @Output() onAllClicked = new EventEmitter(); -- @Output() onFavoritesClicked = new EventEmitter(); -- @Output() onTrashClicked = new EventEmitter(); -- @Output() onCipherTypeClicked = new EventEmitter(); -- @Output() onFolderClicked = new EventEmitter(); -- @Output() onAddFolder = new EventEmitter(); -- @Output() onEditFolder = new EventEmitter(); -- @Output() onCollectionClicked = new EventEmitter(); -- -- folders: FolderView[]; -- nestedFolders: TreeNode[]; -- collections: CollectionView[]; -- nestedCollections: TreeNode[]; -- loaded: boolean = false; -- cipherType = CipherType; -- selectedAll: boolean = false; -- selectedFavorites: boolean = false; -- selectedTrash: boolean = false; -- selectedType: CipherType = null; -- selectedFolder: boolean = false; -- selectedFolderId: string = null; -- selectedCollectionId: string = null; -- -- private collapsedGroupings: Set; -- private collapsedGroupingsKey: string; -- -- constructor(protected collectionService: CollectionService, protected folderService: FolderService, -- protected storageService: StorageService, protected userService: UserService) { } -- -- async load(setLoaded = true) { -- const userId = await this.userService.getUserId(); -- this.collapsedGroupingsKey = ConstantsService.collapsedGroupingsKey + '_' + userId; -- const collapsedGroupings = await this.storageService.get(this.collapsedGroupingsKey); -- if (collapsedGroupings == null) { -- this.collapsedGroupings = new Set(); -- } else { -- this.collapsedGroupings = new Set(collapsedGroupings); -- } -- -- await this.loadFolders(); -- await this.loadCollections(); -- -- if (setLoaded) { -- this.loaded = true; -- } -- } -- -- async loadCollections(organizationId?: string) { -- if (!this.showCollections) { -- return; -- } -- const collections = await this.collectionService.getAllDecrypted(); -- if (organizationId != null) { -- this.collections = collections.filter(c => c.organizationId === organizationId); -- } else { -- this.collections = collections; -- } -- this.nestedCollections = await this.collectionService.getAllNested(this.collections); -- } -- -- async loadFolders() { -- if (!this.showFolders) { -- return; -- } -- this.folders = await this.folderService.getAllDecrypted(); -- this.nestedFolders = await this.folderService.getAllNested(); -- } -- -- selectAll() { -- this.clearSelections(); -- this.selectedAll = true; -- this.onAllClicked.emit(); -- } -- -- selectFavorites() { -- this.clearSelections(); -- this.selectedFavorites = true; -- this.onFavoritesClicked.emit(); -- } -- -- selectTrash() { -- this.clearSelections(); -- this.selectedTrash = true; -- this.onTrashClicked.emit(); -+ @Input() showFolders = true; -+ @Input() showCollections = true; -+ @Input() showFavorites = true; -+ @Input() showTrash = true; -+ -+ @Output() onAllClicked = new EventEmitter(); -+ @Output() onFavoritesClicked = new EventEmitter(); -+ @Output() onTrashClicked = new EventEmitter(); -+ @Output() onCipherTypeClicked = new EventEmitter(); -+ @Output() onFolderClicked = new EventEmitter(); -+ @Output() onAddFolder = new EventEmitter(); -+ @Output() onEditFolder = new EventEmitter(); -+ @Output() onCollectionClicked = new EventEmitter(); -+ -+ folders: FolderView[]; -+ nestedFolders: TreeNode[]; -+ collections: CollectionView[]; -+ nestedCollections: TreeNode[]; -+ loaded: boolean = false; -+ cipherType = CipherType; -+ selectedAll: boolean = false; -+ selectedFavorites: boolean = false; -+ selectedTrash: boolean = false; -+ selectedType: CipherType = null; -+ selectedFolder: boolean = false; -+ selectedFolderId: string = null; -+ selectedCollectionId: string = null; -+ -+ private collapsedGroupings: Set; -+ -+ constructor( -+ protected collectionService: CollectionService, -+ protected folderService: FolderService, -+ protected stateService: StateService -+ ) {} -+ -+ async load(setLoaded = true) { -+ const collapsedGroupings = await this.stateService.getCollapsedGroupings(); -+ if (collapsedGroupings == null) { -+ this.collapsedGroupings = new Set(); -+ } else { -+ this.collapsedGroupings = new Set(collapsedGroupings); - } - -- selectType(type: CipherType) { -- this.clearSelections(); -- this.selectedType = type; -- this.onCipherTypeClicked.emit(type); -- } -+ await this.loadFolders(); -+ await this.loadCollections(); - -- selectFolder(folder: FolderView) { -- this.clearSelections(); -- this.selectedFolder = true; -- this.selectedFolderId = folder.id; -- this.onFolderClicked.emit(folder); -+ if (setLoaded) { -+ this.loaded = true; - } -+ } - -- addFolder() { -- this.onAddFolder.emit(); -+ async loadCollections(organizationId?: string) { -+ if (!this.showCollections) { -+ return; - } -- -- editFolder(folder: FolderView) { -- this.onEditFolder.emit(folder); -+ const collections = await this.collectionService.getAllDecrypted(); -+ if (organizationId != null) { -+ this.collections = collections.filter((c) => c.organizationId === organizationId); -+ } else { -+ this.collections = collections; - } -+ this.nestedCollections = await this.collectionService.getAllNested(this.collections); -+ } - -- selectCollection(collection: CollectionView) { -- this.clearSelections(); -- this.selectedCollectionId = collection.id; -- this.onCollectionClicked.emit(collection); -+ async loadFolders() { -+ if (!this.showFolders) { -+ return; - } -- -- clearSelections() { -- this.selectedAll = false; -- this.selectedFavorites = false; -- this.selectedTrash = false; -- this.selectedType = null; -- this.selectedFolder = false; -- this.selectedFolderId = null; -- this.selectedCollectionId = null; -+ this.folders = await this.folderService.getAllDecrypted(); -+ this.nestedFolders = await this.folderService.getAllNested(); -+ } -+ -+ selectAll() { -+ this.clearSelections(); -+ this.selectedAll = true; -+ this.onAllClicked.emit(); -+ } -+ -+ selectFavorites() { -+ this.clearSelections(); -+ this.selectedFavorites = true; -+ this.onFavoritesClicked.emit(); -+ } -+ -+ selectTrash() { -+ this.clearSelections(); -+ this.selectedTrash = true; -+ this.onTrashClicked.emit(); -+ } -+ -+ selectType(type: CipherType) { -+ this.clearSelections(); -+ this.selectedType = type; -+ this.onCipherTypeClicked.emit(type); -+ } -+ -+ selectFolder(folder: FolderView) { -+ this.clearSelections(); -+ this.selectedFolder = true; -+ this.selectedFolderId = folder.id; -+ this.onFolderClicked.emit(folder); -+ } -+ -+ addFolder() { -+ this.onAddFolder.emit(); -+ } -+ -+ editFolder(folder: FolderView) { -+ this.onEditFolder.emit(folder); -+ } -+ -+ selectCollection(collection: CollectionView) { -+ this.clearSelections(); -+ this.selectedCollectionId = collection.id; -+ this.onCollectionClicked.emit(collection); -+ } -+ -+ clearSelections() { -+ this.selectedAll = false; -+ this.selectedFavorites = false; -+ this.selectedTrash = false; -+ this.selectedType = null; -+ this.selectedFolder = false; -+ this.selectedFolderId = null; -+ this.selectedCollectionId = null; -+ } -+ -+ async collapse(grouping: FolderView | CollectionView, idPrefix = "") { -+ if (grouping.id == null) { -+ return; - } -- -- collapse(grouping: FolderView | CollectionView, idPrefix = '') { -- if (grouping.id == null) { -- return; -- } -- const id = idPrefix + grouping.id; -- if (this.isCollapsed(grouping, idPrefix)) { -- this.collapsedGroupings.delete(id); -- } else { -- this.collapsedGroupings.add(id); -- } -- this.storageService.save(this.collapsedGroupingsKey, this.collapsedGroupings); -+ const id = idPrefix + grouping.id; -+ if (this.isCollapsed(grouping, idPrefix)) { -+ this.collapsedGroupings.delete(id); -+ } else { -+ this.collapsedGroupings.add(id); - } -+ await this.stateService.setCollapsedGroupings(this.collapsedGroupings); -+ } - -- isCollapsed(grouping: FolderView | CollectionView, idPrefix = '') { -- return this.collapsedGroupings.has(idPrefix + grouping.id); -- } -+ isCollapsed(grouping: FolderView | CollectionView, idPrefix = "") { -+ return this.collapsedGroupings.has(idPrefix + grouping.id); -+ } - } -diff --git a/jslib/angular/src/components/hint.component.ts b/jslib/angular/src/components/hint.component.ts -index 8ed7d6ed..9a3d7b2f 100644 ---- a/jslib/angular/src/components/hint.component.ts -+++ b/jslib/angular/src/components/hint.component.ts -@@ -1,46 +1,56 @@ --import { Router } from '@angular/router'; -+import { Router } from "@angular/router"; - --import { PasswordHintRequest } from 'jslib-common/models/request/passwordHintRequest'; -+import { PasswordHintRequest } from "jslib-common/models/request/passwordHintRequest"; - --import { ApiService } from 'jslib-common/abstractions/api.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { LogService } from 'jslib-common/abstractions/log.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -+import { ApiService } from "jslib-common/abstractions/api.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; - - export class HintComponent { -- email: string = ''; -- formPromise: Promise; -+ email: string = ""; -+ formPromise: Promise; - -- protected successRoute = 'login'; -- protected onSuccessfulSubmit: () => void; -+ protected successRoute = "login"; -+ protected onSuccessfulSubmit: () => void; - -- constructor(protected router: Router, protected i18nService: I18nService, -- protected apiService: ApiService, protected platformUtilsService: PlatformUtilsService, -- private logService: LogService) { } -+ constructor( -+ protected router: Router, -+ protected i18nService: I18nService, -+ protected apiService: ApiService, -+ protected platformUtilsService: PlatformUtilsService, -+ private logService: LogService -+ ) {} - -- async submit() { -- if (this.email == null || this.email === '') { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('emailRequired')); -- return; -- } -- if (this.email.indexOf('@') === -1) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('invalidEmail')); -- return; -- } -+ async submit() { -+ if (this.email == null || this.email === "") { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("emailRequired") -+ ); -+ return; -+ } -+ if (this.email.indexOf("@") === -1) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("invalidEmail") -+ ); -+ return; -+ } - -- try { -- this.formPromise = this.apiService.postPasswordHint(new PasswordHintRequest(this.email)); -- await this.formPromise; -- this.platformUtilsService.showToast('success', null, this.i18nService.t('masterPassSent')); -- if (this.onSuccessfulSubmit != null) { -- this.onSuccessfulSubmit(); -- } else if (this.router != null) { -- this.router.navigate([this.successRoute]); -- } -- } catch (e) { -- this.logService.error(e); -- } -+ try { -+ this.formPromise = this.apiService.postPasswordHint(new PasswordHintRequest(this.email)); -+ await this.formPromise; -+ this.platformUtilsService.showToast("success", null, this.i18nService.t("masterPassSent")); -+ if (this.onSuccessfulSubmit != null) { -+ this.onSuccessfulSubmit(); -+ } else if (this.router != null) { -+ this.router.navigate([this.successRoute]); -+ } -+ } catch (e) { -+ this.logService.error(e); - } -+ } - } -diff --git a/jslib/angular/src/components/icon.component.html b/jslib/angular/src/components/icon.component.html -index 00d2c591..a8e4ac9f 100644 ---- a/jslib/angular/src/components/icon.component.html -+++ b/jslib/angular/src/components/icon.component.html -@@ -1,4 +1,4 @@ -

-diff --git a/jslib/angular/src/components/icon.component.ts b/jslib/angular/src/components/icon.component.ts -index b3a75d5a..c5704f27 100644 ---- a/jslib/angular/src/components/icon.component.ts -+++ b/jslib/angular/src/components/icon.component.ts -@@ -1,107 +1,115 @@ --import { -- Component, -- Input, -- OnChanges, --} from '@angular/core'; -+import { Component, Input, OnChanges } from "@angular/core"; - --import { CipherType } from 'jslib-common/enums/cipherType'; -+import { CipherType } from "jslib-common/enums/cipherType"; - --import { CipherView } from 'jslib-common/models/view/cipherView'; -+import { CipherView } from "jslib-common/models/view/cipherView"; - --import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; --import { StateService } from 'jslib-common/abstractions/state.service'; -+import { EnvironmentService } from "jslib-common/abstractions/environment.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; - --import { ConstantsService } from 'jslib-common/services/constants.service'; -+import { Utils } from "jslib-common/misc/utils"; - --import { Utils } from 'jslib-common/misc/utils'; -- --const IconMap: any = { -- 'fa-globe': String.fromCharCode(0xf0ac), -- 'fa-sticky-note-o': String.fromCharCode(0xf24a), -- 'fa-id-card-o': String.fromCharCode(0xf2c3), -- 'fa-credit-card': String.fromCharCode(0xf09d), -- 'fa-android': String.fromCharCode(0xf17b), -- 'fa-apple': String.fromCharCode(0xf179), -+/** -+ * 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 = { -+ 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', -+ selector: "app-vault-icon", -+ templateUrl: "icon.component.html", - }) - export class IconComponent implements OnChanges { -- @Input() cipher: CipherView; -- icon: string; -- image: string; -- fallbackImage: string; -- imageEnabled: boolean; -+ @Input() cipher: CipherView; -+ icon: string; -+ image: string; -+ fallbackImage: string; -+ imageEnabled: boolean; - -- private iconsUrl: string; -+ private iconsUrl: string; - -- constructor(environmentService: EnvironmentService, protected stateService: StateService) { -- this.iconsUrl = environmentService.getIconsUrl(); -- } -+ 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.get(ConstantsService.disableFaviconKey)); -- this.load(); -- } -+ 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(); -+ } - -- get iconCode(): string { -- return IconMap[this.icon]; -+ 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; - } -+ } - -- protected load() { -- switch (this.cipher.type) { -- case CipherType.Login: -- this.icon = 'fa-globe'; -- this.setLoginIcon(); -- break; -- case CipherType.SecureNote: -- this.icon = 'fa-sticky-note-o'; -- break; -- case CipherType.Card: -- this.icon = 'fa-credit-card'; -- break; -- case CipherType.Identity: -- this.icon = 'fa-id-card-o'; -- break; -- default: -- break; -- } -- } -+ private setLoginIcon() { -+ if (this.cipher.login.uri) { -+ let hostnameUri = this.cipher.login.uri; -+ let isWebsite = false; - -- private setLoginIcon() { -- if (this.cipher.login.uri) { -- let hostnameUri = this.cipher.login.uri; -- let isWebsite = false; -- -- if (hostnameUri.indexOf('androidapp://') === 0) { -- this.icon = 'fa-android'; -- this.image = null; -- } else if (hostnameUri.indexOf('iosapp://') === 0) { -- this.icon = 'fa-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 (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/fa-globe.png'; -- } catch (e) { -- // Ignore error since the fallback icon will be shown if image is null. -- } -- } -- } else { -- this.image = null; -+ 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]; - } -+ } - } -diff --git a/jslib/angular/src/components/lock.component.ts b/jslib/angular/src/components/lock.component.ts -index bd11e9ac..04672dcd 100644 ---- a/jslib/angular/src/components/lock.component.ts -+++ b/jslib/angular/src/components/lock.component.ts -@@ -1,208 +1,279 @@ --import { Directive, OnInit } from '@angular/core'; --import { Router } from '@angular/router'; -+import { Directive, NgZone, OnInit } from "@angular/core"; -+import { Router } from "@angular/router"; -+import { take } from "rxjs/operators"; - --import { ApiService } from 'jslib-common/abstractions/api.service'; --import { CryptoService } from 'jslib-common/abstractions/crypto.service'; --import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; --import { LogService } from 'jslib-common/abstractions/log.service'; --import { MessagingService } from 'jslib-common/abstractions/messaging.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { StateService } from 'jslib-common/abstractions/state.service'; --import { StorageService } from 'jslib-common/abstractions/storage.service'; --import { UserService } from 'jslib-common/abstractions/user.service'; --import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; -+import { ApiService } from "jslib-common/abstractions/api.service"; -+import { CryptoService } from "jslib-common/abstractions/crypto.service"; -+import { EnvironmentService } from "jslib-common/abstractions/environment.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { MessagingService } from "jslib-common/abstractions/messaging.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; -+import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service"; - --import { ConstantsService } from 'jslib-common/services/constants.service'; -+import { EncString } from "jslib-common/models/domain/encString"; -+import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey"; - --import { EncString } from 'jslib-common/models/domain/encString'; --import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey'; -+import { SecretVerificationRequest } from "jslib-common/models/request/secretVerificationRequest"; - --import { SecretVerificationRequest } from 'jslib-common/models/request/secretVerificationRequest'; -+import { Utils } from "jslib-common/misc/utils"; - --import { Utils } from 'jslib-common/misc/utils'; -- --import { HashPurpose } from 'jslib-common/enums/hashPurpose'; -+import { HashPurpose } from "jslib-common/enums/hashPurpose"; -+import { KeySuffixOptions } from "jslib-common/enums/keySuffixOptions"; - - @Directive() - export class LockComponent implements OnInit { -- masterPassword: string = ''; -- pin: string = ''; -- showPassword: boolean = false; -- email: string; -- pinLock: boolean = false; -- webVaultHostname: string = ''; -- formPromise: Promise; -- supportsBiometric: boolean; -- biometricLock: boolean; -- biometricText: string; -- hideInput: boolean; -- -- protected successRoute: string = 'vault'; -- protected onSuccessfulSubmit: () => void; -- -- private invalidPinAttempts = 0; -- private pinSet: [boolean, boolean]; -- -- constructor(protected router: Router, protected i18nService: I18nService, -- protected platformUtilsService: PlatformUtilsService, protected messagingService: MessagingService, -- protected userService: UserService, protected cryptoService: CryptoService, -- protected storageService: StorageService, protected vaultTimeoutService: VaultTimeoutService, -- protected environmentService: EnvironmentService, protected stateService: StateService, -- protected apiService: ApiService, private logService: LogService, -- private keyConnectorService: KeyConnectorService) { } -- -- async ngOnInit() { -- this.pinSet = await this.vaultTimeoutService.isPinLockSet(); -- this.pinLock = (this.pinSet[0] && this.vaultTimeoutService.pinProtectedKey != null) || this.pinSet[1]; -- this.supportsBiometric = await this.platformUtilsService.supportsBiometric(); -- this.biometricLock = await this.vaultTimeoutService.isBiometricLockSet() && -- (await this.cryptoService.hasKeyStored('biometric') || !this.platformUtilsService.supportsSecureStorage()); -- this.biometricText = await this.storageService.get(ConstantsService.biometricText); -- this.email = await this.userService.getEmail(); -- const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector(); -- this.hideInput = usesKeyConnector && !this.pinLock; -- -- // Users with key connector and without biometric or pin has no MP to unlock using -- if (usesKeyConnector && !(this.biometricLock || this.pinLock)) { -- await this.vaultTimeoutService.logOut(); -- } -+ masterPassword: string = ""; -+ pin: string = ""; -+ showPassword: boolean = false; -+ email: string; -+ pinLock: boolean = false; -+ webVaultHostname: string = ""; -+ formPromise: Promise; -+ supportsBiometric: boolean; -+ biometricLock: boolean; -+ biometricText: string; -+ hideInput: boolean; -+ -+ protected successRoute: string = "vault"; -+ protected onSuccessfulSubmit: () => Promise; -+ -+ private invalidPinAttempts = 0; -+ private pinSet: [boolean, boolean]; -+ -+ constructor( -+ protected router: Router, -+ protected i18nService: I18nService, -+ protected platformUtilsService: PlatformUtilsService, -+ protected messagingService: MessagingService, -+ protected cryptoService: CryptoService, -+ protected vaultTimeoutService: VaultTimeoutService, -+ protected environmentService: EnvironmentService, -+ protected stateService: StateService, -+ protected apiService: ApiService, -+ private logService: LogService, -+ private keyConnectorService: KeyConnectorService, -+ protected ngZone: NgZone -+ ) {} -+ -+ async ngOnInit() { -+ this.stateService.activeAccount.subscribe(async (_userId) => { -+ await this.load(); -+ }); -+ } - -- const webVaultUrl = this.environmentService.getWebVaultUrl(); -- const vaultUrl = webVaultUrl === 'https://vault.bitwarden.com' ? 'https://bitwarden.com' : webVaultUrl; -- this.webVaultHostname = Utils.getHostname(vaultUrl); -+ async submit() { -+ if (this.pinLock && (this.pin == null || this.pin === "")) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("pinRequired") -+ ); -+ return; - } -+ if (!this.pinLock && (this.masterPassword == null || this.masterPassword === "")) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("masterPassRequired") -+ ); -+ return; -+ } -+ -+ const kdf = await this.stateService.getKdfType(); -+ const kdfIterations = await this.stateService.getKdfIterations(); - -- async submit() { -- if (this.pinLock && (this.pin == null || this.pin === '')) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('pinRequired')); -- return; -+ if (this.pinLock) { -+ let failed = true; -+ try { -+ if (this.pinSet[0]) { -+ const key = await this.cryptoService.makeKeyFromPin( -+ this.pin, -+ this.email, -+ kdf, -+ kdfIterations, -+ await this.stateService.getDecryptedPinProtected() -+ ); -+ const encKey = await this.cryptoService.getEncKey(key); -+ const protectedPin = await this.stateService.getProtectedPin(); -+ const decPin = await this.cryptoService.decryptToUtf8( -+ new EncString(protectedPin), -+ encKey -+ ); -+ failed = decPin !== this.pin; -+ if (!failed) { -+ await this.setKeyAndContinue(key); -+ } -+ } else { -+ const key = await this.cryptoService.makeKeyFromPin( -+ this.pin, -+ this.email, -+ kdf, -+ kdfIterations -+ ); -+ failed = false; -+ await this.setKeyAndContinue(key); - } -- if (!this.pinLock && (this.masterPassword == null || this.masterPassword === '')) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('masterPassRequired')); -- return; -+ } catch { -+ failed = true; -+ } -+ -+ if (failed) { -+ this.invalidPinAttempts++; -+ if (this.invalidPinAttempts >= 5) { -+ this.messagingService.send("logout"); -+ return; - } -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("invalidPin") -+ ); -+ } -+ } else { -+ const key = await this.cryptoService.makeKey( -+ this.masterPassword, -+ this.email, -+ kdf, -+ kdfIterations -+ ); -+ const storedKeyHash = await this.cryptoService.getKeyHash(); - -- const kdf = await this.userService.getKdf(); -- const kdfIterations = await this.userService.getKdfIterations(); -- -- if (this.pinLock) { -- let failed = true; -- try { -- if (this.pinSet[0]) { -- const key = await this.cryptoService.makeKeyFromPin(this.pin, this.email, kdf, kdfIterations, -- this.vaultTimeoutService.pinProtectedKey); -- const encKey = await this.cryptoService.getEncKey(key); -- const protectedPin = await this.storageService.get(ConstantsService.protectedPin); -- const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey); -- failed = decPin !== this.pin; -- if (!failed) { -- await this.setKeyAndContinue(key); -- } -- } else { -- const key = await this.cryptoService.makeKeyFromPin(this.pin, this.email, kdf, kdfIterations); -- failed = false; -- await this.setKeyAndContinue(key); -- } -- } catch { -- failed = true; -- } -- -- if (failed) { -- this.invalidPinAttempts++; -- if (this.invalidPinAttempts >= 5) { -- this.messagingService.send('logout'); -- return; -- } -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('invalidPin')); -- } -- } else { -- const key = await this.cryptoService.makeKey(this.masterPassword, this.email, kdf, kdfIterations); -- const storedKeyHash = await this.cryptoService.getKeyHash(); -- -- let passwordValid = false; -- -- if (storedKeyHash != null) { -- passwordValid = await this.cryptoService.compareAndUpdateKeyHash(this.masterPassword, key); -- } else { -- const request = new SecretVerificationRequest(); -- const serverKeyHash = await this.cryptoService.hashPassword(this.masterPassword, key, -- HashPurpose.ServerAuthorization); -- request.masterPasswordHash = serverKeyHash; -- try { -- this.formPromise = this.apiService.postAccountVerifyPassword(request); -- await this.formPromise; -- passwordValid = true; -- const localKeyHash = await this.cryptoService.hashPassword(this.masterPassword, key, -- HashPurpose.LocalAuthorization); -- await this.cryptoService.setKeyHash(localKeyHash); -- } catch (e) { -- this.logService.error(e); -- } -- } -- -- if (passwordValid) { -- if (this.pinSet[0]) { -- const protectedPin = await this.storageService.get(ConstantsService.protectedPin); -- const encKey = await this.cryptoService.getEncKey(key); -- const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey); -- const pinKey = await this.cryptoService.makePinKey(decPin, this.email, kdf, kdfIterations); -- this.vaultTimeoutService.pinProtectedKey = await this.cryptoService.encrypt(key.key, pinKey); -- } -- this.setKeyAndContinue(key); -- } else { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('invalidMasterPassword')); -- } -+ let passwordValid = false; -+ -+ if (storedKeyHash != null) { -+ passwordValid = await this.cryptoService.compareAndUpdateKeyHash(this.masterPassword, key); -+ } else { -+ const request = new SecretVerificationRequest(); -+ const serverKeyHash = await this.cryptoService.hashPassword( -+ this.masterPassword, -+ key, -+ HashPurpose.ServerAuthorization -+ ); -+ request.masterPasswordHash = serverKeyHash; -+ try { -+ this.formPromise = this.apiService.postAccountVerifyPassword(request); -+ await this.formPromise; -+ passwordValid = true; -+ const localKeyHash = await this.cryptoService.hashPassword( -+ this.masterPassword, -+ key, -+ HashPurpose.LocalAuthorization -+ ); -+ await this.cryptoService.setKeyHash(localKeyHash); -+ } catch (e) { -+ this.logService.error(e); - } -- } -+ } - -- async logOut() { -- const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('logOutConfirmation'), -- this.i18nService.t('logOut'), this.i18nService.t('logOut'), this.i18nService.t('cancel')); -- if (confirmed) { -- this.messagingService.send('logout'); -+ if (passwordValid) { -+ if (this.pinSet[0]) { -+ const protectedPin = await this.stateService.getProtectedPin(); -+ const encKey = await this.cryptoService.getEncKey(key); -+ const decPin = await this.cryptoService.decryptToUtf8( -+ new EncString(protectedPin), -+ encKey -+ ); -+ const pinKey = await this.cryptoService.makePinKey( -+ decPin, -+ this.email, -+ kdf, -+ kdfIterations -+ ); -+ await this.stateService.setDecryptedPinProtected( -+ await this.cryptoService.encrypt(key.key, pinKey) -+ ); - } -+ await this.setKeyAndContinue(key); -+ } else { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("invalidMasterPassword") -+ ); -+ } - } -+ } - -- async unlockBiometric(): Promise { -- if (!this.biometricLock) { -- return; -- } -+ async logOut() { -+ const confirmed = await this.platformUtilsService.showDialog( -+ this.i18nService.t("logOutConfirmation"), -+ this.i18nService.t("logOut"), -+ this.i18nService.t("logOut"), -+ this.i18nService.t("cancel") -+ ); -+ if (confirmed) { -+ this.messagingService.send("logout"); -+ } -+ } - -- const success = (await this.cryptoService.getKey('biometric')) != null; -+ async unlockBiometric(): Promise { -+ if (!this.biometricLock) { -+ return; -+ } - -- if (success) { -- await this.doContinue(); -- } -+ const success = (await this.cryptoService.getKey(KeySuffixOptions.Biometric)) != null; - -- return success; -+ if (success) { -+ await this.doContinue(); - } - -- togglePassword() { -- this.showPassword = !this.showPassword; -- document.getElementById(this.pinLock ? 'pin' : 'masterPassword').focus(); -+ return success; -+ } -+ -+ togglePassword() { -+ this.showPassword = !this.showPassword; -+ const input = document.getElementById(this.pinLock ? "pin" : "masterPassword"); -+ if (this.ngZone.isStable) { -+ input.focus(); -+ } else { -+ this.ngZone.onStable.pipe(take(1)).subscribe(() => input.focus()); - } -+ } -+ -+ private async setKeyAndContinue(key: SymmetricCryptoKey) { -+ await this.cryptoService.setKey(key); -+ await this.doContinue(); -+ } - -- private async setKeyAndContinue(key: SymmetricCryptoKey) { -- await this.cryptoService.setKey(key); -- this.doContinue(); -+ private async doContinue() { -+ await this.stateService.setBiometricLocked(false); -+ await this.stateService.setEverBeenUnlocked(true); -+ const disableFavicon = await this.stateService.getDisableFavicon(); -+ await this.stateService.setDisableFavicon(!!disableFavicon); -+ this.messagingService.send("unlocked"); -+ if (this.onSuccessfulSubmit != null) { -+ await this.onSuccessfulSubmit(); -+ } else if (this.router != null) { -+ this.router.navigate([this.successRoute]); - } -+ } - -- private async doContinue() { -- this.vaultTimeoutService.biometricLocked = false; -- this.vaultTimeoutService.everBeenUnlocked = true; -- const disableFavicon = await this.storageService.get(ConstantsService.disableFaviconKey); -- await this.stateService.save(ConstantsService.disableFaviconKey, !!disableFavicon); -- this.messagingService.send('unlocked'); -- if (this.onSuccessfulSubmit != null) { -- this.onSuccessfulSubmit(); -- } else if (this.router != null) { -- this.router.navigate([this.successRoute]); -- } -+ private async load() { -+ this.pinSet = await this.vaultTimeoutService.isPinLockSet(); -+ this.pinLock = -+ (this.pinSet[0] && (await this.stateService.getDecryptedPinProtected()) != null) || -+ this.pinSet[1]; -+ this.supportsBiometric = await this.platformUtilsService.supportsBiometric(); -+ this.biometricLock = -+ (await this.vaultTimeoutService.isBiometricLockSet()) && -+ ((await this.cryptoService.hasKeyStored(KeySuffixOptions.Biometric)) || -+ !this.platformUtilsService.supportsSecureStorage()); -+ this.biometricText = await this.stateService.getBiometricText(); -+ this.email = await this.stateService.getEmail(); -+ const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector(); -+ this.hideInput = usesKeyConnector && !this.pinLock; -+ -+ // Users with key connector and without biometric or pin has no MP to unlock using -+ if (usesKeyConnector && !(this.biometricLock || this.pinLock)) { -+ await this.vaultTimeoutService.logOut(); - } -+ -+ const webVaultUrl = this.environmentService.getWebVaultUrl(); -+ const vaultUrl = -+ webVaultUrl === "https://vault.bitwarden.com" ? "https://bitwarden.com" : webVaultUrl; -+ this.webVaultHostname = Utils.getHostname(vaultUrl); -+ } - } -diff --git a/jslib/angular/src/components/login.component.ts b/jslib/angular/src/components/login.component.ts -index bfac5f53..e860ca9c 100644 ---- a/jslib/angular/src/components/login.component.ts -+++ b/jslib/angular/src/components/login.component.ts -@@ -1,169 +1,189 @@ --import { -- Directive, -- Input, -- OnInit, --} from '@angular/core'; -+import { Directive, Input, NgZone, OnInit } from "@angular/core"; - --import { Router } from '@angular/router'; -+import { Router } from "@angular/router"; - --import { AuthResult } from 'jslib-common/models/domain/authResult'; -+import { take } from "rxjs/operators"; - --import { AuthService } from 'jslib-common/abstractions/auth.service'; --import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; --import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { LogService } from 'jslib-common/abstractions/log.service'; --import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { StateService } from 'jslib-common/abstractions/state.service'; --import { StorageService } from 'jslib-common/abstractions/storage.service'; -+import { AuthResult } from "jslib-common/models/domain/authResult"; - --import { ConstantsService } from 'jslib-common/services/constants.service'; -+import { AuthService } from "jslib-common/abstractions/auth.service"; -+import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service"; -+import { EnvironmentService } from "jslib-common/abstractions/environment.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; - --import { Utils } from 'jslib-common/misc/utils'; -+import { Utils } from "jslib-common/misc/utils"; - --import { CaptchaProtectedComponent } from './captchaProtected.component'; -- --const Keys = { -- rememberedEmail: 'rememberedEmail', -- rememberEmail: 'rememberEmail', --}; -+import { CaptchaProtectedComponent } from "./captchaProtected.component"; - - @Directive() - export class LoginComponent extends CaptchaProtectedComponent implements OnInit { -- @Input() email: string = ''; -- @Input() rememberEmail = true; -- -- masterPassword: string = ''; -- showPassword: boolean = false; -- formPromise: Promise; -- onSuccessfulLogin: () => Promise; -- onSuccessfulLoginNavigate: () => Promise; -- onSuccessfulLoginTwoFactorNavigate: () => Promise; -- onSuccessfulLoginForceResetNavigate: () => Promise; -- -- protected twoFactorRoute = '2fa'; -- protected successRoute = 'vault'; -- protected forcePasswordResetRoute = 'update-temp-password'; -- -- constructor(protected authService: AuthService, protected router: Router, -- platformUtilsService: PlatformUtilsService, i18nService: I18nService, -- protected stateService: StateService, environmentService: EnvironmentService, -- protected passwordGenerationService: PasswordGenerationService, -- protected cryptoFunctionService: CryptoFunctionService, private storageService: StorageService, -- protected logService: LogService) { -- super(environmentService, i18nService, platformUtilsService); -+ @Input() email: string = ""; -+ @Input() rememberEmail = true; -+ -+ masterPassword: string = ""; -+ showPassword: boolean = false; -+ formPromise: Promise; -+ onSuccessfulLogin: () => Promise; -+ onSuccessfulLoginNavigate: () => Promise; -+ onSuccessfulLoginTwoFactorNavigate: () => Promise; -+ onSuccessfulLoginForceResetNavigate: () => Promise; -+ -+ protected twoFactorRoute = "2fa"; -+ protected successRoute = "vault"; -+ protected forcePasswordResetRoute = "update-temp-password"; -+ protected alwaysRememberEmail: boolean = false; -+ -+ constructor( -+ protected authService: AuthService, -+ protected router: Router, -+ platformUtilsService: PlatformUtilsService, -+ i18nService: I18nService, -+ protected stateService: StateService, -+ environmentService: EnvironmentService, -+ protected passwordGenerationService: PasswordGenerationService, -+ protected cryptoFunctionService: CryptoFunctionService, -+ protected logService: LogService, -+ protected ngZone: NgZone -+ ) { -+ super(environmentService, i18nService, platformUtilsService); -+ } -+ -+ async ngOnInit() { -+ if (this.email == null || this.email === "") { -+ this.email = await this.stateService.getRememberedEmail(); -+ if (this.email == null) { -+ this.email = ""; -+ } - } -- -- async ngOnInit() { -- if (this.email == null || this.email === '') { -- this.email = await this.storageService.get(Keys.rememberedEmail); -- if (this.email == null) { -- this.email = ''; -- } -- } -- this.rememberEmail = await this.storageService.get(Keys.rememberEmail); -- if (this.rememberEmail == null) { -- this.rememberEmail = true; -- } -- if (Utils.isBrowser && !Utils.isNode) { -- this.focusInput(); -- } -+ if (!this.alwaysRememberEmail) { -+ this.rememberEmail = (await this.stateService.getRememberedEmail()) != null; -+ } -+ if (Utils.isBrowser && !Utils.isNode) { -+ this.focusInput(); -+ } -+ } -+ -+ async submit() { -+ await this.setupCaptcha(); -+ -+ if (this.email == null || this.email === "") { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("emailRequired") -+ ); -+ return; -+ } -+ if (this.email.indexOf("@") === -1) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("invalidEmail") -+ ); -+ return; -+ } -+ if (this.masterPassword == null || this.masterPassword === "") { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("masterPassRequired") -+ ); -+ return; - } - -- async submit() { -- await this.setupCaptcha(); -- -- if (this.email == null || this.email === '') { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('emailRequired')); -- return; -+ try { -+ this.formPromise = this.authService.logIn(this.email, this.masterPassword, this.captchaToken); -+ const response = await this.formPromise; -+ if (this.rememberEmail || this.alwaysRememberEmail) { -+ await this.stateService.setRememberedEmail(this.email); -+ } else { -+ await this.stateService.setRememberedEmail(null); -+ } -+ if (this.handleCaptchaRequired(response)) { -+ return; -+ } else if (response.twoFactor) { -+ if (this.onSuccessfulLoginTwoFactorNavigate != null) { -+ this.onSuccessfulLoginTwoFactorNavigate(); -+ } else { -+ this.router.navigate([this.twoFactorRoute]); - } -- if (this.email.indexOf('@') === -1) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('invalidEmail')); -- return; -+ } else if (response.forcePasswordReset) { -+ if (this.onSuccessfulLoginForceResetNavigate != null) { -+ this.onSuccessfulLoginForceResetNavigate(); -+ } else { -+ this.router.navigate([this.forcePasswordResetRoute]); - } -- if (this.masterPassword == null || this.masterPassword === '') { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('masterPassRequired')); -- return; -+ } else { -+ const disableFavicon = await this.stateService.getDisableFavicon(); -+ await this.stateService.setDisableFavicon(!!disableFavicon); -+ if (this.onSuccessfulLogin != null) { -+ this.onSuccessfulLogin(); - } -- -- try { -- this.formPromise = this.authService.logIn(this.email, this.masterPassword, this.captchaToken); -- const response = await this.formPromise; -- await this.storageService.save(Keys.rememberEmail, this.rememberEmail); -- if (this.rememberEmail) { -- await this.storageService.save(Keys.rememberedEmail, this.email); -- } else { -- await this.storageService.remove(Keys.rememberedEmail); -- } -- if (this.handleCaptchaRequired(response)) { -- return; -- } else if (response.twoFactor) { -- if (this.onSuccessfulLoginTwoFactorNavigate != null) { -- this.onSuccessfulLoginTwoFactorNavigate(); -- } else { -- this.router.navigate([this.twoFactorRoute]); -- } -- } else if (response.forcePasswordReset) { -- if (this.onSuccessfulLoginForceResetNavigate != null) { -- this.onSuccessfulLoginForceResetNavigate(); -- } else { -- this.router.navigate([this.forcePasswordResetRoute]); -- } -- } else { -- const disableFavicon = await this.storageService.get(ConstantsService.disableFaviconKey); -- await this.stateService.save(ConstantsService.disableFaviconKey, !!disableFavicon); -- if (this.onSuccessfulLogin != null) { -- this.onSuccessfulLogin(); -- } -- if (this.onSuccessfulLoginNavigate != null) { -- this.onSuccessfulLoginNavigate(); -- } else { -- this.router.navigate([this.successRoute]); -- } -- } -- } catch (e) { -- this.logService.error(e); -+ if (this.onSuccessfulLoginNavigate != null) { -+ this.onSuccessfulLoginNavigate(); -+ } else { -+ this.router.navigate([this.successRoute]); - } -+ } -+ } catch (e) { -+ this.logService.error(e); - } -- -- togglePassword() { -- this.showPassword = !this.showPassword; -- document.getElementById('masterPassword').focus(); -- } -- -- async launchSsoBrowser(clientId: string, ssoRedirectUri: string) { -- // Generate necessary sso params -- const passwordOptions: any = { -- type: 'password', -- length: 64, -- uppercase: true, -- lowercase: true, -- numbers: true, -- special: false, -- }; -- const state = await this.passwordGenerationService.generatePassword(passwordOptions); -- const ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); -- const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, 'sha256'); -- const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); -- -- // Save sso params -- await this.storageService.save(ConstantsService.ssoStateKey, state); -- await this.storageService.save(ConstantsService.ssoCodeVerifierKey, ssoCodeVerifier); -- -- // Build URI -- const webUrl = this.environmentService.getWebVaultUrl(); -- -- // Launch browser -- this.platformUtilsService.launchUri(webUrl + '/#/sso?clientId=' + clientId + -- '&redirectUri=' + encodeURIComponent(ssoRedirectUri) + -- '&state=' + state + '&codeChallenge=' + codeChallenge); -- } -- -- protected focusInput() { -- document.getElementById(this.email == null || this.email === '' ? 'email' : 'masterPassword').focus(); -+ } -+ -+ togglePassword() { -+ this.showPassword = !this.showPassword; -+ if (this.ngZone.isStable) { -+ document.getElementById("masterPassword").focus(); -+ } else { -+ this.ngZone.onStable -+ .pipe(take(1)) -+ .subscribe(() => document.getElementById("masterPassword").focus()); - } -+ } -+ -+ async launchSsoBrowser(clientId: string, ssoRedirectUri: string) { -+ // Generate necessary sso params -+ const passwordOptions: any = { -+ type: "password", -+ length: 64, -+ uppercase: true, -+ lowercase: true, -+ numbers: true, -+ special: false, -+ }; -+ const state = await this.passwordGenerationService.generatePassword(passwordOptions); -+ const ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); -+ const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256"); -+ const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); -+ -+ // Save sso params -+ await this.stateService.setSsoState(state); -+ await this.stateService.setSsoCodeVerifier(ssoCodeVerifier); -+ -+ // Build URI -+ const webUrl = this.environmentService.getWebVaultUrl(); -+ -+ // Launch browser -+ this.platformUtilsService.launchUri( -+ webUrl + -+ "/#/sso?clientId=" + -+ clientId + -+ "&redirectUri=" + -+ encodeURIComponent(ssoRedirectUri) + -+ "&state=" + -+ state + -+ "&codeChallenge=" + -+ codeChallenge -+ ); -+ } -+ -+ protected focusInput() { -+ document -+ .getElementById(this.email == null || this.email === "" ? "email" : "masterPassword") -+ .focus(); -+ } - } -diff --git a/jslib/angular/src/components/modal/dynamic-modal.component.ts b/jslib/angular/src/components/modal/dynamic-modal.component.ts -index 5194a44f..4d529f34 100644 ---- a/jslib/angular/src/components/modal/dynamic-modal.component.ts -+++ b/jslib/angular/src/components/modal/dynamic-modal.component.ts -@@ -1,76 +1,80 @@ - import { -- AfterViewInit, -- ChangeDetectorRef, -- Component, -- ComponentRef, -- ElementRef, -- OnDestroy, -- Type, -- ViewChild, -- ViewContainerRef --} from '@angular/core'; -+ AfterViewInit, -+ ChangeDetectorRef, -+ Component, -+ ComponentRef, -+ ElementRef, -+ OnDestroy, -+ Type, -+ ViewChild, -+ ViewContainerRef, -+} from "@angular/core"; - --import { -- ConfigurableFocusTrap, -- ConfigurableFocusTrapFactory, --} from '@angular/cdk/a11y'; -+import { ConfigurableFocusTrap, ConfigurableFocusTrapFactory } from "@angular/cdk/a11y"; - --import { ModalService } from '../../services/modal.service'; -+import { ModalService } from "../../services/modal.service"; - --import { ModalRef } from './modal.ref'; -+import { ModalRef } from "./modal.ref"; - - @Component({ -- selector: 'app-modal', -- template: '', -+ selector: "app-modal", -+ template: "", - }) - export class DynamicModalComponent implements AfterViewInit, OnDestroy { -- componentRef: ComponentRef; -+ componentRef: ComponentRef; - -- @ViewChild('modalContent', { read: ViewContainerRef, static: true }) modalContentRef: ViewContainerRef; -+ @ViewChild("modalContent", { read: ViewContainerRef, static: true }) -+ modalContentRef: ViewContainerRef; - -- childComponentType: Type; -- setComponentParameters: (component: any) => void; -+ childComponentType: Type; -+ setComponentParameters: (component: any) => void; - -- private focusTrap: ConfigurableFocusTrap; -+ private focusTrap: ConfigurableFocusTrap; - -- constructor(private modalService: ModalService, private cd: ChangeDetectorRef, -- private el: ElementRef, private focusTrapFactory: ConfigurableFocusTrapFactory, -- public modalRef: ModalRef) { } -+ constructor( -+ private modalService: ModalService, -+ private cd: ChangeDetectorRef, -+ private el: ElementRef, -+ private focusTrapFactory: ConfigurableFocusTrapFactory, -+ public modalRef: ModalRef -+ ) {} - -- ngAfterViewInit() { -- this.loadChildComponent(this.childComponentType); -- if (this.setComponentParameters != null) { -- this.setComponentParameters(this.componentRef.instance); -- } -- this.cd.detectChanges(); -+ ngAfterViewInit() { -+ this.loadChildComponent(this.childComponentType); -+ if (this.setComponentParameters != null) { -+ this.setComponentParameters(this.componentRef.instance); -+ } -+ this.cd.detectChanges(); - -- this.modalRef.created(this.el.nativeElement); -- this.focusTrap = this.focusTrapFactory.create(this.el.nativeElement.querySelector('.modal-dialog')); -- if (this.el.nativeElement.querySelector('[appAutoFocus]') == null) { -- this.focusTrap.focusFirstTabbableElementWhenReady(); -- } -+ this.modalRef.created(this.el.nativeElement); -+ this.focusTrap = this.focusTrapFactory.create( -+ this.el.nativeElement.querySelector(".modal-dialog") -+ ); -+ if (this.el.nativeElement.querySelector("[appAutoFocus]") == null) { -+ this.focusTrap.focusFirstTabbableElementWhenReady(); - } -+ } - -- loadChildComponent(componentType: Type) { -- const componentFactory = this.modalService.resolveComponentFactory(componentType); -+ loadChildComponent(componentType: Type) { -+ const componentFactory = this.modalService.resolveComponentFactory(componentType); - -- this.modalContentRef.clear(); -- this.componentRef = this.modalContentRef.createComponent(componentFactory); -- } -+ this.modalContentRef.clear(); -+ this.componentRef = this.modalContentRef.createComponent(componentFactory); -+ } - -- ngOnDestroy() { -- if (this.componentRef) { -- this.componentRef.destroy(); -- } -- this.focusTrap.destroy(); -+ ngOnDestroy() { -+ if (this.componentRef) { -+ this.componentRef.destroy(); - } -+ this.focusTrap.destroy(); -+ } - -- close() { -- this.modalRef.close(); -- } -+ close() { -+ this.modalRef.close(); -+ } - -- getFocus() { -- const autoFocusEl = this.el.nativeElement.querySelector('[appAutoFocus]') as HTMLElement; -- autoFocusEl?.focus(); -- } -+ getFocus() { -+ const autoFocusEl = this.el.nativeElement.querySelector("[appAutoFocus]") as HTMLElement; -+ autoFocusEl?.focus(); -+ } - } -diff --git a/jslib/angular/src/components/modal/modal-injector.ts b/jslib/angular/src/components/modal/modal-injector.ts -index 8477608e..188e5584 100644 ---- a/jslib/angular/src/components/modal/modal-injector.ts -+++ b/jslib/angular/src/components/modal/modal-injector.ts -@@ -1,15 +1,10 @@ --import { -- InjectFlags, -- InjectionToken, -- Injector, -- Type --} from '@angular/core'; -+import { InjectFlags, InjectionToken, Injector, Type } from "@angular/core"; - - export class ModalInjector implements Injector { -- constructor(private _parentInjector: Injector, private _additionalTokens: WeakMap) {} -+ constructor(private _parentInjector: Injector, private _additionalTokens: WeakMap) {} - -- get(token: Type | InjectionToken, notFoundValue?: T, flags?: InjectFlags): T; -- get(token: any, notFoundValue?: any, flags?: any) { -- return this._additionalTokens.get(token) ?? this._parentInjector.get(token, notFoundValue); -- } -+ get(token: Type | InjectionToken, notFoundValue?: T, flags?: InjectFlags): T; -+ get(token: any, notFoundValue?: any, flags?: any) { -+ return this._additionalTokens.get(token) ?? this._parentInjector.get(token, notFoundValue); -+ } - } -diff --git a/jslib/angular/src/components/modal/modal.ref.ts b/jslib/angular/src/components/modal/modal.ref.ts -index 116a6057..a80acebb 100644 ---- a/jslib/angular/src/components/modal/modal.ref.ts -+++ b/jslib/angular/src/components/modal/modal.ref.ts -@@ -1,51 +1,50 @@ --import { Observable, Subject } from 'rxjs'; --import { first } from 'rxjs/operators'; -+import { Observable, Subject } from "rxjs"; -+import { first } from "rxjs/operators"; - - export class ModalRef { -- -- onCreated: Observable; // Modal added to the DOM. -- onClose: Observable; // Initiated close. -- onClosed: Observable; // Modal was closed (Remove element from DOM) -- onShow: Observable; // Start showing modal -- onShown: Observable; // Modal is fully visible -- -- private readonly _onCreated = new Subject(); -- private readonly _onClose = new Subject(); -- private readonly _onClosed = new Subject(); -- private readonly _onShow = new Subject(); -- private readonly _onShown = new Subject(); -- private lastResult: any; -- -- constructor() { -- this.onCreated = this._onCreated.asObservable(); -- this.onClose = this._onClose.asObservable(); -- this.onClosed = this._onClosed.asObservable(); -- this.onShow = this._onShow.asObservable(); -- this.onShown = this._onShow.asObservable(); -- } -- -- show() { -- this._onShow.next(); -- } -- -- shown() { -- this._onShown.next(); -- } -- -- close(result?: any) { -- this.lastResult = result; -- this._onClose.next(result); -- } -- -- closed() { -- this._onClosed.next(this.lastResult); -- } -- -- created(el: HTMLElement) { -- this._onCreated.next(el); -- } -- -- onClosedPromise(): Promise { -- return this.onClosed.pipe(first()).toPromise(); -- } -+ onCreated: Observable; // Modal added to the DOM. -+ onClose: Observable; // Initiated close. -+ onClosed: Observable; // Modal was closed (Remove element from DOM) -+ onShow: Observable; // Start showing modal -+ onShown: Observable; // Modal is fully visible -+ -+ private readonly _onCreated = new Subject(); -+ private readonly _onClose = new Subject(); -+ private readonly _onClosed = new Subject(); -+ private readonly _onShow = new Subject(); -+ private readonly _onShown = new Subject(); -+ private lastResult: any; -+ -+ constructor() { -+ this.onCreated = this._onCreated.asObservable(); -+ this.onClose = this._onClose.asObservable(); -+ this.onClosed = this._onClosed.asObservable(); -+ this.onShow = this._onShow.asObservable(); -+ this.onShown = this._onShow.asObservable(); -+ } -+ -+ show() { -+ this._onShow.next(); -+ } -+ -+ shown() { -+ this._onShown.next(); -+ } -+ -+ close(result?: any) { -+ this.lastResult = result; -+ this._onClose.next(result); -+ } -+ -+ closed() { -+ this._onClosed.next(this.lastResult); -+ } -+ -+ created(el: HTMLElement) { -+ this._onCreated.next(el); -+ } -+ -+ onClosedPromise(): Promise { -+ return this.onClosed.pipe(first()).toPromise(); -+ } - } -diff --git a/jslib/angular/src/components/password-generator-history.component.ts b/jslib/angular/src/components/password-generator-history.component.ts -index 67adba56..00f73a9f 100644 ---- a/jslib/angular/src/components/password-generator-history.component.ts -+++ b/jslib/angular/src/components/password-generator-history.component.ts -@@ -1,32 +1,38 @@ --import { Directive, OnInit } from '@angular/core'; -+import { Directive, OnInit } from "@angular/core"; - --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; - --import { GeneratedPasswordHistory } from 'jslib-common/models/domain/generatedPasswordHistory'; -+import { GeneratedPasswordHistory } from "jslib-common/models/domain/generatedPasswordHistory"; - - @Directive() - export class PasswordGeneratorHistoryComponent implements OnInit { -- history: GeneratedPasswordHistory[] = []; -+ history: GeneratedPasswordHistory[] = []; - -- constructor(protected passwordGenerationService: PasswordGenerationService, -- protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService, -- private win: Window) { } -+ constructor( -+ protected passwordGenerationService: PasswordGenerationService, -+ protected platformUtilsService: PlatformUtilsService, -+ protected i18nService: I18nService, -+ private win: Window -+ ) {} - -- async ngOnInit() { -- this.history = await this.passwordGenerationService.getHistory(); -- } -+ async ngOnInit() { -+ this.history = await this.passwordGenerationService.getHistory(); -+ } - -- clear() { -- this.history = []; -- this.passwordGenerationService.clear(); -- } -+ clear() { -+ this.history = []; -+ this.passwordGenerationService.clear(); -+ } - -- copy(password: string) { -- const copyOptions = this.win != null ? { window: this.win } : null; -- this.platformUtilsService.copyToClipboard(password, copyOptions); -- this.platformUtilsService.showToast('info', null, -- this.i18nService.t('valueCopied', this.i18nService.t('password'))); -- } -+ copy(password: string) { -+ const copyOptions = this.win != null ? { window: this.win } : null; -+ this.platformUtilsService.copyToClipboard(password, copyOptions); -+ this.platformUtilsService.showToast( -+ "info", -+ null, -+ this.i18nService.t("valueCopied", this.i18nService.t("password")) -+ ); -+ } - } -diff --git a/jslib/angular/src/components/password-generator.component.ts b/jslib/angular/src/components/password-generator.component.ts -index 6e5a389a..b46cda43 100644 ---- a/jslib/angular/src/components/password-generator.component.ts -+++ b/jslib/angular/src/components/password-generator.component.ts -@@ -1,101 +1,106 @@ --import { -- Directive, -- EventEmitter, -- Input, -- OnInit, -- Output, --} from '@angular/core'; -+import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; - --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; - --import { PasswordGeneratorPolicyOptions } from 'jslib-common/models/domain/passwordGeneratorPolicyOptions'; -+import { PasswordGeneratorPolicyOptions } from "jslib-common/models/domain/passwordGeneratorPolicyOptions"; - - @Directive() - export class PasswordGeneratorComponent implements OnInit { -- @Input() showSelect: boolean = false; -- @Output() onSelected = new EventEmitter(); -- -- passTypeOptions: any[]; -- options: any = {}; -- password: string = '-'; -- showOptions = false; -- avoidAmbiguous = false; -- enforcedPolicyOptions: PasswordGeneratorPolicyOptions; -- -- constructor(protected passwordGenerationService: PasswordGenerationService, -- protected platformUtilsService: PlatformUtilsService, protected i18nService: I18nService, -- private win: Window) { -- this.passTypeOptions = [ -- { name: i18nService.t('password'), value: 'password' }, -- { name: i18nService.t('passphrase'), value: 'passphrase' }, -- ]; -- } -- -- async ngOnInit() { -- const optionsResponse = await this.passwordGenerationService.getOptions(); -- this.options = optionsResponse[0]; -- this.enforcedPolicyOptions = optionsResponse[1]; -- this.avoidAmbiguous = !this.options.ambiguous; -- this.options.type = this.options.type === 'passphrase' ? 'passphrase' : 'password'; -- this.password = await this.passwordGenerationService.generatePassword(this.options); -- await this.passwordGenerationService.addHistory(this.password); -+ @Input() showSelect: boolean = false; -+ @Output() onSelected = new EventEmitter(); -+ -+ passTypeOptions: any[]; -+ options: any = {}; -+ password: string = "-"; -+ showOptions = false; -+ avoidAmbiguous = false; -+ enforcedPolicyOptions: PasswordGeneratorPolicyOptions; -+ -+ constructor( -+ protected passwordGenerationService: PasswordGenerationService, -+ protected platformUtilsService: PlatformUtilsService, -+ protected i18nService: I18nService, -+ private win: Window -+ ) { -+ this.passTypeOptions = [ -+ { name: i18nService.t("password"), value: "password" }, -+ { name: i18nService.t("passphrase"), value: "passphrase" }, -+ ]; -+ } -+ -+ async ngOnInit() { -+ const optionsResponse = await this.passwordGenerationService.getOptions(); -+ this.options = optionsResponse[0]; -+ this.enforcedPolicyOptions = optionsResponse[1]; -+ this.avoidAmbiguous = !this.options.ambiguous; -+ this.options.type = this.options.type === "passphrase" ? "passphrase" : "password"; -+ this.password = await this.passwordGenerationService.generatePassword(this.options); -+ await this.passwordGenerationService.addHistory(this.password); -+ } -+ -+ async sliderChanged() { -+ this.saveOptions(false); -+ await this.passwordGenerationService.addHistory(this.password); -+ } -+ -+ async sliderInput() { -+ this.normalizeOptions(); -+ this.password = await this.passwordGenerationService.generatePassword(this.options); -+ } -+ -+ async saveOptions(regenerate: boolean = true) { -+ this.normalizeOptions(); -+ await this.passwordGenerationService.saveOptions(this.options); -+ -+ if (regenerate) { -+ await this.regenerate(); - } -- -- async sliderChanged() { -- this.saveOptions(false); -- await this.passwordGenerationService.addHistory(this.password); -- } -- -- async sliderInput() { -- this.normalizeOptions(); -- this.password = await this.passwordGenerationService.generatePassword(this.options); -- } -- -- async saveOptions(regenerate: boolean = true) { -- this.normalizeOptions(); -- await this.passwordGenerationService.saveOptions(this.options); -- -- if (regenerate) { -- await this.regenerate(); -+ } -+ -+ async regenerate() { -+ this.password = await this.passwordGenerationService.generatePassword(this.options); -+ await this.passwordGenerationService.addHistory(this.password); -+ } -+ -+ copy() { -+ const copyOptions = this.win != null ? { window: this.win } : null; -+ this.platformUtilsService.copyToClipboard(this.password, copyOptions); -+ this.platformUtilsService.showToast( -+ "info", -+ null, -+ this.i18nService.t("valueCopied", this.i18nService.t("password")) -+ ); -+ } -+ -+ select() { -+ this.onSelected.emit(this.password); -+ } -+ -+ toggleOptions() { -+ this.showOptions = !this.showOptions; -+ } -+ -+ private normalizeOptions() { -+ // Application level normalize options depedent on class variables -+ this.options.ambiguous = !this.avoidAmbiguous; -+ -+ if ( -+ !this.options.uppercase && -+ !this.options.lowercase && -+ !this.options.number && -+ !this.options.special -+ ) { -+ this.options.lowercase = true; -+ if (this.win != null) { -+ const lowercase = this.win.document.querySelector("#lowercase") as HTMLInputElement; -+ if (lowercase) { -+ lowercase.checked = true; - } -+ } - } - -- async regenerate() { -- this.password = await this.passwordGenerationService.generatePassword(this.options); -- await this.passwordGenerationService.addHistory(this.password); -- } -- -- copy() { -- const copyOptions = this.win != null ? { window: this.win } : null; -- this.platformUtilsService.copyToClipboard(this.password, copyOptions); -- this.platformUtilsService.showToast('info', null, -- this.i18nService.t('valueCopied', this.i18nService.t('password'))); -- } -- -- select() { -- this.onSelected.emit(this.password); -- } -- -- toggleOptions() { -- this.showOptions = !this.showOptions; -- } -- -- private normalizeOptions() { -- // Application level normalize options depedent on class variables -- this.options.ambiguous = !this.avoidAmbiguous; -- -- if (!this.options.uppercase && !this.options.lowercase && !this.options.number && !this.options.special) { -- this.options.lowercase = true; -- if (this.win != null) { -- const lowercase = this.win.document.querySelector('#lowercase') as HTMLInputElement; -- if (lowercase) { -- lowercase.checked = true; -- } -- } -- } -- -- this.passwordGenerationService.normalizeOptions(this.options, this.enforcedPolicyOptions); -- } -+ this.passwordGenerationService.normalizeOptions(this.options, this.enforcedPolicyOptions); -+ } - } -diff --git a/jslib/angular/src/components/password-history.component.ts b/jslib/angular/src/components/password-history.component.ts -index 3134c0a4..d5d83b1c 100644 ---- a/jslib/angular/src/components/password-history.component.ts -+++ b/jslib/angular/src/components/password-history.component.ts -@@ -1,33 +1,40 @@ --import { Directive, OnInit } from '@angular/core'; -+import { Directive, OnInit } from "@angular/core"; - --import { CipherService } from 'jslib-common/abstractions/cipher.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -+import { CipherService } from "jslib-common/abstractions/cipher.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; - --import { PasswordHistoryView } from 'jslib-common/models/view/passwordHistoryView'; -+import { PasswordHistoryView } from "jslib-common/models/view/passwordHistoryView"; - - @Directive() - export class PasswordHistoryComponent implements OnInit { -- cipherId: string; -- history: PasswordHistoryView[] = []; -+ cipherId: string; -+ history: PasswordHistoryView[] = []; - -- constructor(protected cipherService: CipherService, protected platformUtilsService: PlatformUtilsService, -- protected i18nService: I18nService, private win: Window) { } -+ constructor( -+ protected cipherService: CipherService, -+ protected platformUtilsService: PlatformUtilsService, -+ protected i18nService: I18nService, -+ private win: Window -+ ) {} - -- async ngOnInit() { -- await this.init(); -- } -+ async ngOnInit() { -+ await this.init(); -+ } - -- copy(password: string) { -- const copyOptions = this.win != null ? { window: this.win } : null; -- this.platformUtilsService.copyToClipboard(password, copyOptions); -- this.platformUtilsService.showToast('info', null, -- this.i18nService.t('valueCopied', this.i18nService.t('password'))); -- } -+ copy(password: string) { -+ const copyOptions = this.win != null ? { window: this.win } : null; -+ this.platformUtilsService.copyToClipboard(password, copyOptions); -+ this.platformUtilsService.showToast( -+ "info", -+ null, -+ this.i18nService.t("valueCopied", this.i18nService.t("password")) -+ ); -+ } - -- protected async init() { -- const cipher = await this.cipherService.get(this.cipherId); -- const decCipher = await cipher.decrypt(); -- this.history = decCipher.passwordHistory == null ? [] : decCipher.passwordHistory; -- } -+ protected async init() { -+ const cipher = await this.cipherService.get(this.cipherId); -+ const decCipher = await cipher.decrypt(); -+ this.history = decCipher.passwordHistory == null ? [] : decCipher.passwordHistory; -+ } - } -diff --git a/jslib/angular/src/components/password-reprompt.component.ts b/jslib/angular/src/components/password-reprompt.component.ts -index 68845394..f547aace 100644 ---- a/jslib/angular/src/components/password-reprompt.component.ts -+++ b/jslib/angular/src/components/password-reprompt.component.ts -@@ -1,30 +1,36 @@ --import { Directive } from '@angular/core'; -+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'; -+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"; - - @Directive() - export class PasswordRepromptComponent { -+ showPassword = false; -+ masterPassword = ""; - -- showPassword = false; -- masterPassword = ''; -+ constructor( -+ private modalRef: ModalRef, -+ private cryptoService: CryptoService, -+ private platformUtilsService: PlatformUtilsService, -+ private i18nService: I18nService -+ ) {} - -- constructor(private modalRef: ModalRef, private cryptoService: CryptoService, private platformUtilsService: PlatformUtilsService, -- private i18nService: I18nService) {} -+ togglePassword() { -+ this.showPassword = !this.showPassword; -+ } - -- 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; - } - -- 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); -- } -+ this.modalRef.close(true); -+ } - } -diff --git a/jslib/angular/src/components/premium.component.ts b/jslib/angular/src/components/premium.component.ts -index 5b5b4c89..62e5d8f1 100644 ---- a/jslib/angular/src/components/premium.component.ts -+++ b/jslib/angular/src/components/premium.component.ts -@@ -1,48 +1,61 @@ --import { Directive, OnInit } from '@angular/core'; -+import { Directive, OnInit } from "@angular/core"; - --import { ApiService } from 'jslib-common/abstractions/api.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { LogService } from 'jslib-common/abstractions/log.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { UserService } from 'jslib-common/abstractions/user.service'; -+import { ApiService } from "jslib-common/abstractions/api.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; - - @Directive() - export class PremiumComponent implements OnInit { -- isPremium: boolean = false; -- price: number = 10; -- refreshPromise: Promise; -+ isPremium: boolean = false; -+ price: number = 10; -+ refreshPromise: Promise; - -- constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, -- protected apiService: ApiService, protected userService: UserService, private logService: LogService) { } -+ constructor( -+ protected i18nService: I18nService, -+ protected platformUtilsService: PlatformUtilsService, -+ protected apiService: ApiService, -+ private logService: LogService, -+ protected stateService: StateService -+ ) {} - -- async ngOnInit() { -- this.isPremium = await this.userService.canAccessPremium(); -- } -+ async ngOnInit() { -+ this.isPremium = await this.stateService.getCanAccessPremium(); -+ } - -- async refresh() { -- try { -- this.refreshPromise = this.apiService.refreshIdentityToken(); -- await this.refreshPromise; -- this.platformUtilsService.showToast('success', null, this.i18nService.t('refreshComplete')); -- this.isPremium = await this.userService.canAccessPremium(); -- } catch (e) { -- this.logService.error(e); -- } -+ async refresh() { -+ try { -+ this.refreshPromise = this.apiService.refreshIdentityToken(); -+ await this.refreshPromise; -+ this.platformUtilsService.showToast("success", null, this.i18nService.t("refreshComplete")); -+ this.isPremium = await this.stateService.getCanAccessPremium(); -+ } catch (e) { -+ this.logService.error(e); - } -+ } - -- async purchase() { -- const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('premiumPurchaseAlert'), -- this.i18nService.t('premiumPurchase'), this.i18nService.t('yes'), this.i18nService.t('cancel')); -- if (confirmed) { -- this.platformUtilsService.launchUri('https://vault.bitwarden.com/#/?premium=purchase'); -- } -+ async purchase() { -+ const confirmed = await this.platformUtilsService.showDialog( -+ this.i18nService.t("premiumPurchaseAlert"), -+ this.i18nService.t("premiumPurchase"), -+ this.i18nService.t("yes"), -+ this.i18nService.t("cancel") -+ ); -+ if (confirmed) { -+ this.platformUtilsService.launchUri("https://vault.bitwarden.com/#/?premium=purchase"); - } -+ } - -- async manage() { -- const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('premiumManageAlert'), -- this.i18nService.t('premiumManage'), this.i18nService.t('yes'), this.i18nService.t('cancel')); -- if (confirmed) { -- this.platformUtilsService.launchUri('https://vault.bitwarden.com/#/?premium=manage'); -- } -+ async manage() { -+ const confirmed = await this.platformUtilsService.showDialog( -+ this.i18nService.t("premiumManageAlert"), -+ this.i18nService.t("premiumManage"), -+ this.i18nService.t("yes"), -+ this.i18nService.t("cancel") -+ ); -+ if (confirmed) { -+ this.platformUtilsService.launchUri("https://vault.bitwarden.com/#/?premium=manage"); - } -+ } - } -diff --git a/jslib/angular/src/components/register.component.ts b/jslib/angular/src/components/register.component.ts -index fd91af29..16ccb6ea 100644 ---- a/jslib/angular/src/components/register.component.ts -+++ b/jslib/angular/src/components/register.component.ts -@@ -1,195 +1,251 @@ --import { Directive, OnInit } from '@angular/core'; --import { Router } from '@angular/router'; -+import { Directive, OnInit } from "@angular/core"; -+import { Router } from "@angular/router"; - --import { KeysRequest } from 'jslib-common/models/request/keysRequest'; --import { ReferenceEventRequest } from 'jslib-common/models/request/referenceEventRequest'; --import { RegisterRequest } from 'jslib-common/models/request/registerRequest'; -+import { KeysRequest } from "jslib-common/models/request/keysRequest"; -+import { ReferenceEventRequest } from "jslib-common/models/request/referenceEventRequest"; -+import { RegisterRequest } from "jslib-common/models/request/registerRequest"; - --import { ApiService } from 'jslib-common/abstractions/api.service'; --import { AuthService } from 'jslib-common/abstractions/auth.service'; --import { CryptoService } from 'jslib-common/abstractions/crypto.service'; --import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { LogService } from 'jslib-common/abstractions/log.service'; --import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { StateService } from 'jslib-common/abstractions/state.service'; -+import { ApiService } from "jslib-common/abstractions/api.service"; -+import { AuthService } from "jslib-common/abstractions/auth.service"; -+import { CryptoService } from "jslib-common/abstractions/crypto.service"; -+import { EnvironmentService } from "jslib-common/abstractions/environment.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; - --import { KdfType } from 'jslib-common/enums/kdfType'; -+import { KdfType } from "jslib-common/enums/kdfType"; - --import { CaptchaProtectedComponent } from './captchaProtected.component'; -+import { CaptchaProtectedComponent } from "./captchaProtected.component"; - - @Directive() - export class RegisterComponent extends CaptchaProtectedComponent implements OnInit { -- name: string = ''; -- email: string = ''; -- masterPassword: string = ''; -- confirmMasterPassword: string = ''; -- hint: string = ''; -- showPassword: boolean = false; -- formPromise: Promise; -- masterPasswordScore: number; -- referenceData: ReferenceEventRequest; -- showTerms = true; -- acceptPolicies: boolean = false; -- -- protected successRoute = 'login'; -- private masterPasswordStrengthTimeout: any; -- -- constructor(protected authService: AuthService, protected router: Router, -- i18nService: I18nService, protected cryptoService: CryptoService, -- protected apiService: ApiService, protected stateService: StateService, -- platformUtilsService: PlatformUtilsService, -- protected passwordGenerationService: PasswordGenerationService, environmentService: EnvironmentService, -- protected logService: LogService) { -- super(environmentService, i18nService, platformUtilsService); -- this.showTerms = !platformUtilsService.isSelfHost(); -+ name: string = ""; -+ email: string = ""; -+ masterPassword: string = ""; -+ confirmMasterPassword: string = ""; -+ hint: string = ""; -+ showPassword: boolean = false; -+ formPromise: Promise; -+ masterPasswordScore: number; -+ referenceData: ReferenceEventRequest; -+ showTerms = true; -+ acceptPolicies: boolean = false; -+ -+ protected successRoute = "login"; -+ private masterPasswordStrengthTimeout: any; -+ -+ constructor( -+ protected authService: AuthService, -+ protected router: Router, -+ i18nService: I18nService, -+ protected cryptoService: CryptoService, -+ protected apiService: ApiService, -+ protected stateService: StateService, -+ platformUtilsService: PlatformUtilsService, -+ protected passwordGenerationService: PasswordGenerationService, -+ environmentService: EnvironmentService, -+ protected logService: LogService -+ ) { -+ super(environmentService, i18nService, platformUtilsService); -+ this.showTerms = !platformUtilsService.isSelfHost(); -+ } -+ -+ async ngOnInit() { -+ this.setupCaptcha(); -+ } -+ -+ get masterPasswordScoreWidth() { -+ return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20; -+ } -+ -+ get masterPasswordScoreColor() { -+ switch (this.masterPasswordScore) { -+ case 4: -+ return "success"; -+ case 3: -+ return "primary"; -+ case 2: -+ return "warning"; -+ default: -+ return "danger"; - } -- -- async ngOnInit() { -- this.setupCaptcha(); -+ } -+ -+ get masterPasswordScoreText() { -+ switch (this.masterPasswordScore) { -+ case 4: -+ return this.i18nService.t("strong"); -+ case 3: -+ return this.i18nService.t("good"); -+ case 2: -+ return this.i18nService.t("weak"); -+ default: -+ return this.masterPasswordScore != null ? this.i18nService.t("weak") : null; - } -- -- get masterPasswordScoreWidth() { -- return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20; -+ } -+ -+ async submit() { -+ if (!this.acceptPolicies && this.showTerms) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("acceptPoliciesError") -+ ); -+ return; - } - -- get masterPasswordScoreColor() { -- switch (this.masterPasswordScore) { -- case 4: -- return 'success'; -- case 3: -- return 'primary'; -- case 2: -- return 'warning'; -- default: -- return 'danger'; -- } -+ if (this.email == null || this.email === "") { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("emailRequired") -+ ); -+ return; - } -- -- get masterPasswordScoreText() { -- switch (this.masterPasswordScore) { -- case 4: -- return this.i18nService.t('strong'); -- case 3: -- return this.i18nService.t('good'); -- case 2: -- return this.i18nService.t('weak'); -- default: -- return this.masterPasswordScore != null ? this.i18nService.t('weak') : null; -- } -+ if (this.email.indexOf("@") === -1) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("invalidEmail") -+ ); -+ return; -+ } -+ if (this.masterPassword == null || this.masterPassword === "") { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("masterPassRequired") -+ ); -+ return; -+ } -+ if (this.masterPassword.length < 8) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("masterPassLength") -+ ); -+ return; -+ } -+ if (this.masterPassword !== this.confirmMasterPassword) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("masterPassDoesntMatch") -+ ); -+ return; - } - -- async submit() { -- if (!this.acceptPolicies && this.showTerms) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('acceptPoliciesError')); -- return; -- } -- -- if (this.email == null || this.email === '') { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('emailRequired')); -- return; -- } -- if (this.email.indexOf('@') === -1) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('invalidEmail')); -- return; -- } -- if (this.masterPassword == null || this.masterPassword === '') { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('masterPassRequired')); -- return; -- } -- if (this.masterPassword.length < 8) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('masterPassLength')); -- return; -- } -- if (this.masterPassword !== this.confirmMasterPassword) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('masterPassDoesntMatch')); -- return; -- } -- -- const strengthResult = this.passwordGenerationService.passwordStrength(this.masterPassword, -- this.getPasswordStrengthUserInput()); -- if (strengthResult != null && strengthResult.score < 3) { -- const result = await this.platformUtilsService.showDialog(this.i18nService.t('weakMasterPasswordDesc'), -- this.i18nService.t('weakMasterPassword'), this.i18nService.t('yes'), this.i18nService.t('no'), -- 'warning'); -- if (!result) { -- return; -- } -- } -- -- if (this.hint === this.masterPassword) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), this.i18nService.t('hintEqualsPassword')); -- return; -- } -- -- this.name = this.name === '' ? null : this.name; -- this.email = this.email.trim().toLowerCase(); -- const kdf = KdfType.PBKDF2_SHA256; -- const useLowerKdf = this.platformUtilsService.isIE(); -- const kdfIterations = useLowerKdf ? 10000 : 100000; -- const key = await this.cryptoService.makeKey(this.masterPassword, this.email, kdf, kdfIterations); -- const encKey = await this.cryptoService.makeEncKey(key); -- const hashedPassword = await this.cryptoService.hashPassword(this.masterPassword, key); -- const keys = await this.cryptoService.makeKeyPair(encKey[0]); -- const request = new RegisterRequest(this.email, this.name, hashedPassword, -- this.hint, encKey[1].encryptedString, kdf, kdfIterations, this.referenceData, this.captchaToken); -- request.keys = new KeysRequest(keys[0], keys[1].encryptedString); -- const orgInvite = await this.stateService.get('orgInvitation'); -- if (orgInvite != null && orgInvite.token != null && orgInvite.organizationUserId != null) { -- request.token = orgInvite.token; -- request.organizationUserId = orgInvite.organizationUserId; -- } -+ const strengthResult = this.passwordGenerationService.passwordStrength( -+ this.masterPassword, -+ this.getPasswordStrengthUserInput() -+ ); -+ if (strengthResult != null && strengthResult.score < 3) { -+ const result = await this.platformUtilsService.showDialog( -+ this.i18nService.t("weakMasterPasswordDesc"), -+ this.i18nService.t("weakMasterPassword"), -+ this.i18nService.t("yes"), -+ this.i18nService.t("no"), -+ "warning" -+ ); -+ if (!result) { -+ return; -+ } -+ } - -- try { -- this.formPromise = this.apiService.postRegister(request); -- try { -- await this.formPromise; -- } catch (e) { -- if (this.handleCaptchaRequired(e)) { -- return; -- } else { -- throw e; -- } -- } -- this.platformUtilsService.showToast('success', null, this.i18nService.t('newAccountCreated')); -- this.router.navigate([this.successRoute], { queryParams: { email: this.email } }); -- } catch (e) { -- this.logService.error(e); -- } -+ if (this.hint === this.masterPassword) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("hintEqualsPassword") -+ ); -+ return; - } - -- togglePassword(confirmField: boolean) { -- this.showPassword = !this.showPassword; -- document.getElementById(confirmField ? 'masterPasswordRetype' : 'masterPassword').focus(); -+ this.name = this.name === "" ? null : this.name; -+ this.email = this.email.trim().toLowerCase(); -+ const kdf = KdfType.PBKDF2_SHA256; -+ const useLowerKdf = this.platformUtilsService.isIE(); -+ const kdfIterations = useLowerKdf ? 10000 : 100000; -+ const key = await this.cryptoService.makeKey( -+ this.masterPassword, -+ this.email, -+ kdf, -+ kdfIterations -+ ); -+ const encKey = await this.cryptoService.makeEncKey(key); -+ const hashedPassword = await this.cryptoService.hashPassword(this.masterPassword, key); -+ const keys = await this.cryptoService.makeKeyPair(encKey[0]); -+ const request = new RegisterRequest( -+ this.email, -+ this.name, -+ hashedPassword, -+ this.hint, -+ encKey[1].encryptedString, -+ kdf, -+ kdfIterations, -+ this.referenceData, -+ this.captchaToken -+ ); -+ request.keys = new KeysRequest(keys[0], keys[1].encryptedString); -+ const orgInvite = await this.stateService.getOrganizationInvitation(); -+ if (orgInvite != null && orgInvite.token != null && orgInvite.organizationUserId != null) { -+ request.token = orgInvite.token; -+ request.organizationUserId = orgInvite.organizationUserId; - } - -- updatePasswordStrength() { -- if (this.masterPasswordStrengthTimeout != null) { -- clearTimeout(this.masterPasswordStrengthTimeout); -+ try { -+ this.formPromise = this.apiService.postRegister(request); -+ try { -+ await this.formPromise; -+ } catch (e) { -+ if (this.handleCaptchaRequired(e)) { -+ return; -+ } else { -+ throw e; - } -- this.masterPasswordStrengthTimeout = setTimeout(() => { -- const strengthResult = this.passwordGenerationService.passwordStrength(this.masterPassword, -- this.getPasswordStrengthUserInput()); -- this.masterPasswordScore = strengthResult == null ? null : strengthResult.score; -- }, 300); -+ } -+ this.platformUtilsService.showToast("success", null, this.i18nService.t("newAccountCreated")); -+ this.router.navigate([this.successRoute], { queryParams: { email: this.email } }); -+ } catch (e) { -+ this.logService.error(e); - } -+ } - -- private getPasswordStrengthUserInput() { -- let userInput: string[] = []; -- const atPosition = this.email.indexOf('@'); -- if (atPosition > -1) { -- userInput = userInput.concat(this.email.substr(0, atPosition).trim().toLowerCase().split(/[^A-Za-z0-9]/)); -- } -- if (this.name != null && this.name !== '') { -- userInput = userInput.concat(this.name.trim().toLowerCase().split(' ')); -- } -- return userInput; -+ togglePassword(confirmField: boolean) { -+ this.showPassword = !this.showPassword; -+ document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus(); -+ } -+ -+ updatePasswordStrength() { -+ if (this.masterPasswordStrengthTimeout != null) { -+ clearTimeout(this.masterPasswordStrengthTimeout); -+ } -+ this.masterPasswordStrengthTimeout = setTimeout(() => { -+ const strengthResult = this.passwordGenerationService.passwordStrength( -+ this.masterPassword, -+ this.getPasswordStrengthUserInput() -+ ); -+ this.masterPasswordScore = strengthResult == null ? null : strengthResult.score; -+ }, 300); -+ } -+ -+ private getPasswordStrengthUserInput() { -+ let userInput: string[] = []; -+ const atPosition = this.email.indexOf("@"); -+ if (atPosition > -1) { -+ userInput = userInput.concat( -+ this.email -+ .substr(0, atPosition) -+ .trim() -+ .toLowerCase() -+ .split(/[^A-Za-z0-9]/) -+ ); -+ } -+ if (this.name != null && this.name !== "") { -+ userInput = userInput.concat(this.name.trim().toLowerCase().split(" ")); - } -+ return userInput; -+ } - } -diff --git a/jslib/angular/src/components/remove-password.component.ts b/jslib/angular/src/components/remove-password.component.ts -index 91c35312..0359ad35 100644 ---- a/jslib/angular/src/components/remove-password.component.ts -+++ b/jslib/angular/src/components/remove-password.component.ts -@@ -1,77 +1,83 @@ --import { -- Directive, -- OnInit, --} from '@angular/core'; --import { Router } from '@angular/router'; -+import { Directive, OnInit } from "@angular/core"; -+import { Router } from "@angular/router"; - --import { ApiService } from 'jslib-common/abstractions/api.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { StorageService } from 'jslib-common/abstractions/storage.service'; --import { SyncService } from 'jslib-common/abstractions/sync.service'; --import { UserService } from 'jslib-common/abstractions/user.service'; -+import { ApiService } from "jslib-common/abstractions/api.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; -+import { SyncService } from "jslib-common/abstractions/sync.service"; - --import { ConstantsService } from 'jslib-common/services/constants.service'; -- --import { Organization } from 'jslib-common/models/domain/organization'; -+import { Organization } from "jslib-common/models/domain/organization"; - - @Directive() - export class RemovePasswordComponent implements OnInit { -+ actionPromise: Promise; -+ continuing: boolean = false; -+ leaving: boolean = false; - -- actionPromise: Promise; -- continuing: boolean = false; -- leaving: boolean = false; -- -- loading: boolean = true; -- organization: Organization; -- email: string; -+ loading: boolean = true; -+ organization: Organization; -+ email: string; - -- constructor(private router: Router, private userService: UserService, -- private apiService: ApiService, private syncService: SyncService, -- private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, -- private keyConnectorService: KeyConnectorService, private storageService: StorageService) { } -+ constructor( -+ private router: Router, -+ private stateService: StateService, -+ private apiService: ApiService, -+ private syncService: SyncService, -+ private platformUtilsService: PlatformUtilsService, -+ private i18nService: I18nService, -+ private keyConnectorService: KeyConnectorService -+ ) {} - -- async ngOnInit() { -- this.organization = await this.keyConnectorService.getManagingOrganization(); -- this.email = await this.userService.getEmail(); -- await this.syncService.fullSync(false); -- this.loading = false; -- } -+ async ngOnInit() { -+ this.organization = await this.keyConnectorService.getManagingOrganization(); -+ this.email = await this.stateService.getEmail(); -+ await this.syncService.fullSync(false); -+ this.loading = false; -+ } - -- async convert() { -- this.continuing = true; -- this.actionPromise = this.keyConnectorService.migrateUser(); -+ async convert() { -+ this.continuing = true; -+ this.actionPromise = this.keyConnectorService.migrateUser(); - -- try { -- await this.actionPromise; -- this.platformUtilsService.showToast('success', null, this.i18nService.t('removedMasterPassword')); -- await this.keyConnectorService.removeConvertAccountRequired(); -- this.router.navigate(['']); -- } catch (e) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), e.message); -- } -+ try { -+ await this.actionPromise; -+ this.platformUtilsService.showToast( -+ "success", -+ null, -+ this.i18nService.t("removedMasterPassword") -+ ); -+ await this.keyConnectorService.removeConvertAccountRequired(); -+ this.router.navigate([""]); -+ } catch (e) { -+ this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message); - } -+ } - -- async leave() { -- const confirmed = await this.platformUtilsService.showDialog( -- this.i18nService.t('leaveOrganizationConfirmation'), this.organization.name, -- this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); -- if (!confirmed) { -- return false; -- } -+ async leave() { -+ const confirmed = await this.platformUtilsService.showDialog( -+ this.i18nService.t("leaveOrganizationConfirmation"), -+ this.organization.name, -+ this.i18nService.t("yes"), -+ this.i18nService.t("no"), -+ "warning" -+ ); -+ if (!confirmed) { -+ return false; -+ } - -- try { -- this.leaving = true; -- this.actionPromise = this.apiService.postLeaveOrganization(this.organization.id).then(() => { -- return this.syncService.fullSync(true); -- }); -- await this.actionPromise; -- this.platformUtilsService.showToast('success', null, this.i18nService.t('leftOrganization')); -- await this.keyConnectorService.removeConvertAccountRequired(); -- this.router.navigate(['']); -- } catch (e) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), e); -- } -+ try { -+ this.leaving = true; -+ this.actionPromise = this.apiService.postLeaveOrganization(this.organization.id).then(() => { -+ return this.syncService.fullSync(true); -+ }); -+ await this.actionPromise; -+ this.platformUtilsService.showToast("success", null, this.i18nService.t("leftOrganization")); -+ await this.keyConnectorService.removeConvertAccountRequired(); -+ this.router.navigate([""]); -+ } catch (e) { -+ this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e); - } -+ } - } -diff --git a/jslib/angular/src/components/send/add-edit.component.ts b/jslib/angular/src/components/send/add-edit.component.ts -index 70bd87ba..3573260c 100644 ---- a/jslib/angular/src/components/send/add-edit.component.ts -+++ b/jslib/angular/src/components/send/add-edit.component.ts -@@ -1,274 +1,296 @@ --import { DatePipe } from '@angular/common'; --import { -- Directive, -- EventEmitter, -- Input, -- OnInit, -- Output --} from '@angular/core'; -- --import { PolicyType } from 'jslib-common/enums/policyType'; --import { SendType } from 'jslib-common/enums/sendType'; -- --import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { LogService } from 'jslib-common/abstractions/log.service'; --import { MessagingService } from 'jslib-common/abstractions/messaging.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { PolicyService } from 'jslib-common/abstractions/policy.service'; --import { SendService } from 'jslib-common/abstractions/send.service'; --import { UserService } from 'jslib-common/abstractions/user.service'; -- --import { SendFileView } from 'jslib-common/models/view/sendFileView'; --import { SendTextView } from 'jslib-common/models/view/sendTextView'; --import { SendView } from 'jslib-common/models/view/sendView'; -- --import { EncArrayBuffer } from 'jslib-common/models/domain/encArrayBuffer'; --import { Send } from 'jslib-common/models/domain/send'; -+import { DatePipe } from "@angular/common"; -+import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; -+ -+import { PolicyType } from "jslib-common/enums/policyType"; -+import { SendType } from "jslib-common/enums/sendType"; -+ -+import { EnvironmentService } from "jslib-common/abstractions/environment.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { MessagingService } from "jslib-common/abstractions/messaging.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { PolicyService } from "jslib-common/abstractions/policy.service"; -+import { SendService } from "jslib-common/abstractions/send.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; -+ -+import { SendFileView } from "jslib-common/models/view/sendFileView"; -+import { SendTextView } from "jslib-common/models/view/sendTextView"; -+import { SendView } from "jslib-common/models/view/sendView"; -+ -+import { EncArrayBuffer } from "jslib-common/models/domain/encArrayBuffer"; -+import { Send } from "jslib-common/models/domain/send"; - - @Directive() - export class AddEditComponent implements OnInit { -- @Input() sendId: string; -- @Input() type: SendType; -- -- @Output() onSavedSend = new EventEmitter(); -- @Output() onDeletedSend = new EventEmitter(); -- @Output() onCancelled = new EventEmitter(); -- -- copyLink = false; -- disableSend = false; -- disableHideEmail = false; -- send: SendView; -- deletionDate: string; -- expirationDate: string; -- hasPassword: boolean; -- password: string; -- showPassword = false; -- formPromise: Promise; -- deletePromise: Promise; -- sendType = SendType; -- typeOptions: any[]; -- canAccessPremium = true; -- emailVerified = true; -- alertShown = false; -- showOptions = false; -- -- private sendLinkBaseUrl: string; -- -- constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, -- protected environmentService: EnvironmentService, protected datePipe: DatePipe, -- protected sendService: SendService, protected userService: UserService, -- protected messagingService: MessagingService, protected policyService: PolicyService, -- private logService: LogService) { -- this.typeOptions = [ -- { name: i18nService.t('sendTypeFile'), value: SendType.File }, -- { name: i18nService.t('sendTypeText'), value: SendType.Text }, -- ]; -- this.sendLinkBaseUrl = this.environmentService.getSendUrl(); -+ @Input() sendId: string; -+ @Input() type: SendType; -+ -+ @Output() onSavedSend = new EventEmitter(); -+ @Output() onDeletedSend = new EventEmitter(); -+ @Output() onCancelled = new EventEmitter(); -+ -+ copyLink = false; -+ disableSend = false; -+ disableHideEmail = false; -+ send: SendView; -+ deletionDate: string; -+ expirationDate: string; -+ hasPassword: boolean; -+ password: string; -+ showPassword = false; -+ formPromise: Promise; -+ deletePromise: Promise; -+ sendType = SendType; -+ typeOptions: any[]; -+ canAccessPremium = true; -+ emailVerified = true; -+ alertShown = false; -+ showOptions = false; -+ -+ private sendLinkBaseUrl: string; -+ -+ constructor( -+ protected i18nService: I18nService, -+ protected platformUtilsService: PlatformUtilsService, -+ protected environmentService: EnvironmentService, -+ protected datePipe: DatePipe, -+ protected sendService: SendService, -+ protected messagingService: MessagingService, -+ protected policyService: PolicyService, -+ private logService: LogService, -+ protected stateService: StateService -+ ) { -+ this.typeOptions = [ -+ { name: i18nService.t("sendTypeFile"), value: SendType.File }, -+ { name: i18nService.t("sendTypeText"), value: SendType.Text }, -+ ]; -+ this.sendLinkBaseUrl = this.environmentService.getSendUrl(); -+ } -+ -+ get link(): string { -+ if (this.send.id != null && this.send.accessId != null) { -+ return this.sendLinkBaseUrl + this.send.accessId + "/" + this.send.urlB64Key; - } -- -- get link(): string { -- if (this.send.id != null && this.send.accessId != null) { -- return this.sendLinkBaseUrl + this.send.accessId + '/' + this.send.urlB64Key; -- } -- return null; -- } -- -- get isSafari() { -- return this.platformUtilsService.isSafari(); -+ return null; -+ } -+ -+ get isSafari() { -+ return this.platformUtilsService.isSafari(); -+ } -+ -+ get isDateTimeLocalSupported(): boolean { -+ return !(this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari()); -+ } -+ -+ async ngOnInit() { -+ await this.load(); -+ } -+ -+ get editMode(): boolean { -+ return this.sendId != null; -+ } -+ -+ get title(): string { -+ return this.i18nService.t(this.editMode ? "editSend" : "createSend"); -+ } -+ -+ setDates(event: { deletionDate: string; expirationDate: string }) { -+ this.deletionDate = event.deletionDate; -+ this.expirationDate = event.expirationDate; -+ } -+ -+ async load() { -+ this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend); -+ this.disableHideEmail = await this.policyService.policyAppliesToUser( -+ PolicyType.SendOptions, -+ (p) => p.data.disableHideEmail -+ ); -+ -+ this.canAccessPremium = await this.stateService.getCanAccessPremium(); -+ this.emailVerified = await this.stateService.getEmailVerified(); -+ if (!this.canAccessPremium || !this.emailVerified) { -+ this.type = SendType.Text; - } - -- get isDateTimeLocalSupported(): boolean { -- return !(this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari()); -+ if (this.send == null) { -+ if (this.editMode) { -+ const send = await this.loadSend(); -+ this.send = await send.decrypt(); -+ } else { -+ this.send = new SendView(); -+ this.send.type = this.type == null ? SendType.File : this.type; -+ this.send.file = new SendFileView(); -+ this.send.text = new SendTextView(); -+ this.send.deletionDate = new Date(); -+ this.send.deletionDate.setDate(this.send.deletionDate.getDate() + 7); -+ } - } - -- async ngOnInit() { -- await this.load(); -+ this.hasPassword = this.send.password != null && this.send.password.trim() !== ""; -+ } -+ -+ async submit(): Promise { -+ if (this.disableSend) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("sendDisabledWarning") -+ ); -+ return false; - } - -- get editMode(): boolean { -- return this.sendId != null; -+ if (this.send.name == null || this.send.name === "") { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("nameRequired") -+ ); -+ return false; - } - -- get title(): string { -- return this.i18nService.t( -- this.editMode ? -- 'editSend' : -- 'createSend' -+ let file: File = null; -+ if (this.send.type === SendType.File && !this.editMode) { -+ const fileEl = document.getElementById("file") as HTMLInputElement; -+ const files = fileEl.files; -+ if (files == null || files.length === 0) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("selectFile") -+ ); -+ return; -+ } -+ -+ file = files[0]; -+ if (files[0].size > 524288000) { -+ // 500 MB -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("maxFileSize") - ); -+ return; -+ } - } - -- setDates(event: {deletionDate: string, expirationDate: string}) { -- this.deletionDate = event.deletionDate; -- this.expirationDate = event.expirationDate; -+ if (this.password != null && this.password.trim() === "") { -+ this.password = null; - } - -- async load() { -- this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend); -- this.disableHideEmail = await this.policyService.policyAppliesToUser(PolicyType.SendOptions, -- p => p.data.disableHideEmail); -- -- this.canAccessPremium = await this.userService.canAccessPremium(); -- this.emailVerified = await this.userService.getEmailVerified(); -- if (!this.canAccessPremium || !this.emailVerified) { -- this.type = SendType.Text; -- } -- -- if (this.send == null) { -- if (this.editMode) { -- const send = await this.loadSend(); -- this.send = await send.decrypt(); -- } else { -- this.send = new SendView(); -- this.send.type = this.type == null ? SendType.File : this.type; -- this.send.file = new SendFileView(); -- this.send.text = new SendTextView(); -- this.send.deletionDate = new Date(); -- this.send.deletionDate.setDate(this.send.deletionDate.getDate() + 7); -- } -+ this.formPromise = this.encryptSend(file).then(async (encSend) => { -+ const uploadPromise = this.sendService.saveWithServer(encSend); -+ await uploadPromise; -+ if (this.send.id == null) { -+ this.send.id = encSend[0].id; -+ } -+ if (this.send.accessId == null) { -+ this.send.accessId = encSend[0].accessId; -+ } -+ this.onSavedSend.emit(this.send); -+ if (this.copyLink && this.link != null) { -+ const copySuccess = await this.copyLinkToClipboard(this.link); -+ if (copySuccess ?? true) { -+ this.platformUtilsService.showToast( -+ "success", -+ null, -+ this.i18nService.t(this.editMode ? "editedSend" : "createdSend") -+ ); -+ } else { -+ await this.platformUtilsService.showDialog( -+ this.i18nService.t(this.editMode ? "editedSend" : "createdSend"), -+ null, -+ this.i18nService.t("ok"), -+ null, -+ "success", -+ null -+ ); -+ await this.copyLinkToClipboard(this.link); - } -- -- this.hasPassword = this.send.password != null && this.send.password.trim() !== ''; -+ } -+ }); -+ try { -+ await this.formPromise; -+ return true; -+ } catch (e) { -+ this.logService.error(e); - } -+ return false; -+ } - -- async submit(): Promise { -- if (this.disableSend) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('sendDisabledWarning')); -- return false; -- } -- -- if (this.send.name == null || this.send.name === '') { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('nameRequired')); -- return false; -- } -- -- let file: File = null; -- if (this.send.type === SendType.File && !this.editMode) { -- const fileEl = document.getElementById('file') as HTMLInputElement; -- const files = fileEl.files; -- if (files == null || files.length === 0) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('selectFile')); -- return; -- } -- -- file = files[0]; -- if (files[0].size > 524288000) { // 500 MB -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('maxFileSize')); -- return; -- } -- } -- -- if (this.password != null && this.password.trim() === '') { -- this.password = null; -- } -- -- this.formPromise = this.encryptSend(file) -- .then(async encSend => { -- const uploadPromise = this.sendService.saveWithServer(encSend); -- await uploadPromise; -- if (this.send.id == null) { -- this.send.id = encSend[0].id; -- } -- if (this.send.accessId == null) { -- this.send.accessId = encSend[0].accessId; -- } -- this.onSavedSend.emit(this.send); -- if (this.copyLink && this.link != null) { -- const copySuccess = await this.copyLinkToClipboard(this.link); -- if (copySuccess ?? true) { -- this.platformUtilsService.showToast('success', null, -- this.i18nService.t(this.editMode ? 'editedSend' : 'createdSend')); -- } else { -- await this.platformUtilsService.showDialog( -- this.i18nService.t(this.editMode ? 'editedSend' : 'createdSend'), null, -- this.i18nService.t('ok'), null, 'success', null); -- await this.copyLinkToClipboard(this.link); -- } -- } -- }); -- try { -- await this.formPromise; -- return true; -- } catch (e) { -- this.logService.error(e); -- } -- return false; -- } -+ async copyLinkToClipboard(link: string): Promise { -+ return Promise.resolve(this.platformUtilsService.copyToClipboard(link)); -+ } - -- async copyLinkToClipboard(link: string): Promise { -- return Promise.resolve(this.platformUtilsService.copyToClipboard(link)); -+ async delete(): Promise { -+ if (this.deletePromise != null) { -+ return false; - } -- -- async delete(): Promise { -- if (this.deletePromise != null) { -- return false; -- } -- const confirmed = await this.platformUtilsService.showDialog( -- this.i18nService.t('deleteSendConfirmation'), -- this.i18nService.t('deleteSend'), -- this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); -- if (!confirmed) { -- return false; -- } -- -- try { -- this.deletePromise = this.sendService.deleteWithServer(this.send.id); -- await this.deletePromise; -- this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedSend')); -- await this.load(); -- this.onDeletedSend.emit(this.send); -- return true; -- } catch (e) { -- this.logService.error(e); -- } -- -- return false; -+ const confirmed = await this.platformUtilsService.showDialog( -+ this.i18nService.t("deleteSendConfirmation"), -+ this.i18nService.t("deleteSend"), -+ this.i18nService.t("yes"), -+ this.i18nService.t("no"), -+ "warning" -+ ); -+ if (!confirmed) { -+ return false; - } - -- typeChanged() { -- if (this.send.type === SendType.File && !this.alertShown) { -- if (!this.canAccessPremium) { -- this.alertShown = true; -- this.messagingService.send('premiumRequired'); -- } else if (!this.emailVerified) { -- this.alertShown = true; -- this.messagingService.send('emailVerificationRequired'); -- } -- } -+ try { -+ this.deletePromise = this.sendService.deleteWithServer(this.send.id); -+ await this.deletePromise; -+ this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedSend")); -+ await this.load(); -+ this.onDeletedSend.emit(this.send); -+ return true; -+ } catch (e) { -+ this.logService.error(e); - } - -- toggleOptions() { -- this.showOptions = !this.showOptions; -+ return false; -+ } -+ -+ typeChanged() { -+ if (this.send.type === SendType.File && !this.alertShown) { -+ if (!this.canAccessPremium) { -+ this.alertShown = true; -+ this.messagingService.send("premiumRequired"); -+ } else if (!this.emailVerified) { -+ this.alertShown = true; -+ this.messagingService.send("emailVerificationRequired"); -+ } - } -+ } - -- protected async loadSend(): Promise { -- return this.sendService.get(this.sendId); -- } -+ toggleOptions() { -+ this.showOptions = !this.showOptions; -+ } - -- protected async encryptSend(file: File): Promise<[Send, EncArrayBuffer]> { -- const sendData = await this.sendService.encrypt(this.send, file, this.password, null); -+ protected async loadSend(): Promise { -+ return this.sendService.get(this.sendId); -+ } - -- // Parse dates -- try { -- sendData[0].deletionDate = this.deletionDate == null ? null : new Date(this.deletionDate); -- } catch { -- sendData[0].deletionDate = null; -- } -- try { -- sendData[0].expirationDate = this.expirationDate == null ? null : new Date(this.expirationDate); -- } catch { -- sendData[0].expirationDate = null; -- } -+ protected async encryptSend(file: File): Promise<[Send, EncArrayBuffer]> { -+ const sendData = await this.sendService.encrypt(this.send, file, this.password, null); - -- return sendData; -+ // Parse dates -+ try { -+ sendData[0].deletionDate = this.deletionDate == null ? null : new Date(this.deletionDate); -+ } catch { -+ sendData[0].deletionDate = null; - } -- -- protected togglePasswordVisible() { -- this.showPassword = !this.showPassword; -- document.getElementById('password').focus(); -+ try { -+ sendData[0].expirationDate = -+ this.expirationDate == null ? null : new Date(this.expirationDate); -+ } catch { -+ sendData[0].expirationDate = null; - } -+ -+ return sendData; -+ } -+ -+ protected togglePasswordVisible() { -+ this.showPassword = !this.showPassword; -+ document.getElementById("password").focus(); -+ } - } -diff --git a/jslib/angular/src/components/send/efflux-dates.component.ts b/jslib/angular/src/components/send/efflux-dates.component.ts -index b774d3df..5f3aae13 100644 ---- a/jslib/angular/src/components/send/efflux-dates.component.ts -+++ b/jslib/angular/src/components/send/efflux-dates.component.ts -@@ -1,341 +1,354 @@ --import { DatePipe } from '@angular/common'; --import { -- Directive, -- EventEmitter, -- Input, -- OnInit, -- Output --} from '@angular/core'; --import { FormControl, FormGroup } from '@angular/forms'; -- --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -+import { DatePipe } from "@angular/common"; -+import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; -+import { FormControl, FormGroup } from "@angular/forms"; -+ -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; - - // Different BrowserPath = different controls. - enum BrowserPath { -- // Native datetime-locale. -- // We are happy. -- Default = 'default', -+ // Native datetime-locale. -+ // We are happy. -+ Default = "default", - -- // Native date and time inputs, but no datetime-locale. -- // We use individual date and time inputs and create a datetime programatically on submit. -- Firefox = 'firefox', -+ // Native date and time inputs, but no datetime-locale. -+ // We use individual date and time inputs and create a datetime programatically on submit. -+ Firefox = "firefox", - -- // No native date, time, or datetime-locale inputs. -- // We use a polyfill for dates and a dropdown for times. -- Safari = 'safari', -+ // No native date, time, or datetime-locale inputs. -+ // We use a polyfill for dates and a dropdown for times. -+ Safari = "safari", - } - - enum DateField { -- DeletionDate = 'deletion', -- ExpriationDate = 'expiration', -+ DeletionDate = "deletion", -+ ExpriationDate = "expiration", - } - - // Value = hours - enum DatePreset { -- OneHour = 1, -- OneDay = 24, -- TwoDays = 48, -- ThreeDays = 72, -- SevenDays = 168, -- ThirtyDays = 720, -- Custom = 0, -- Never = null, -+ OneHour = 1, -+ OneDay = 24, -+ TwoDays = 48, -+ ThreeDays = 72, -+ SevenDays = 168, -+ ThirtyDays = 720, -+ Custom = 0, -+ Never = null, - } - - // TimeOption is used for the dropdown implementation of custom times - // twelveHour = displayed time; twentyFourHour = time used in logic - interface TimeOption { -- twelveHour: string; -- twentyFourHour: string; -+ twelveHour: string; -+ twentyFourHour: string; - } - - @Directive() - export class EffluxDatesComponent implements OnInit { -- @Input() readonly initialDeletionDate: Date; -- @Input() readonly initialExpirationDate: Date; -- @Input() readonly editMode: boolean; -- @Input() readonly disabled: boolean; -- -- @Output() datesChanged = new EventEmitter<{deletionDate: string, expirationDate: string}>(); -- -- get browserPath(): BrowserPath { -- if (this.platformUtilsService.isFirefox()) { -- return BrowserPath.Firefox; -- } else if (this.platformUtilsService.isSafari()) { -- return BrowserPath.Safari; -- } -- return BrowserPath.Default; -+ @Input() readonly initialDeletionDate: Date; -+ @Input() readonly initialExpirationDate: Date; -+ @Input() readonly editMode: boolean; -+ @Input() readonly disabled: boolean; -+ -+ @Output() datesChanged = new EventEmitter<{ deletionDate: string; expirationDate: string }>(); -+ -+ get browserPath(): BrowserPath { -+ if (this.platformUtilsService.isFirefox()) { -+ return BrowserPath.Firefox; -+ } else if (this.platformUtilsService.isSafari()) { -+ return BrowserPath.Safari; - } -- -- datesForm = new FormGroup({ -- selectedDeletionDatePreset: new FormControl(), -- selectedExpirationDatePreset: new FormControl(), -- defaultDeletionDateTime: new FormControl(), -- defaultExpirationDateTime: new FormControl(), -- fallbackDeletionDate: new FormControl(), -- fallbackDeletionTime: new FormControl(), -- fallbackExpirationDate: new FormControl(), -- fallbackExpirationTime: new FormControl(), -- }); -- -- deletionDatePresets: any[] = [ -- { name: this.i18nService.t('oneHour'), value: DatePreset.OneHour }, -- { name: this.i18nService.t('oneDay'), value: DatePreset.OneDay }, -- { name: this.i18nService.t('days', '2'), value: DatePreset.TwoDays }, -- { name: this.i18nService.t('days', '3'), value: DatePreset.ThreeDays }, -- { name: this.i18nService.t('days', '7'), value: DatePreset.SevenDays }, -- { name: this.i18nService.t('days', '30'), value: DatePreset.ThirtyDays }, -- { name: this.i18nService.t('custom'), value: DatePreset.Custom }, -- ]; -- -- expirationDatePresets: any[] = [ -- { name: this.i18nService.t('never'), value: DatePreset.Never }, -- ].concat([...this.deletionDatePresets]); -- -- get selectedDeletionDatePreset(): FormControl { -- return this.datesForm.get('selectedDeletionDatePreset') as FormControl; -- } -- -- get selectedExpirationDatePreset(): FormControl { -- return this.datesForm.get('selectedExpirationDatePreset') as FormControl; -- } -- -- get defaultDeletionDateTime(): FormControl { -- return this.datesForm.get('defaultDeletionDateTime') as FormControl; -- } -- -- get defaultExpirationDateTime(): FormControl { -- return this.datesForm.get('defaultExpirationDateTime') as FormControl; -- } -- -- get fallbackDeletionDate(): FormControl { -- return this.datesForm.get('fallbackDeletionDate') as FormControl; -- } -- -- get fallbackDeletionTime(): FormControl { -- return this.datesForm.get('fallbackDeletionTime') as FormControl; -- } -- -- get fallbackExpirationDate(): FormControl { -- return this.datesForm.get('fallbackExpirationDate') as FormControl; -- } -- -- get fallbackExpirationTime(): FormControl { -- return this.datesForm.get('fallbackExpirationTime') as FormControl; -- } -- -- // Should be able to call these at any time and compute a submitable value -- get formattedDeletionDate(): string { -- switch (this.selectedDeletionDatePreset.value as DatePreset) { -- case DatePreset.Never: -- this.selectedDeletionDatePreset.setValue(DatePreset.SevenDays); -- return this.formattedDeletionDate; -- case DatePreset.Custom: -- switch (this.browserPath) { -- case BrowserPath.Safari: -- case BrowserPath.Firefox: -- return this.fallbackDeletionDate.value + 'T' + this.fallbackDeletionTime.value; -- default: -- return this.defaultDeletionDateTime.value; -- } -- default: -- const now = new Date(); -- const miliseconds = now.setTime(now.getTime() + -- (this.selectedDeletionDatePreset.value as number * 60 * 60 * 1000)) ; -- return new Date(miliseconds).toString(); -+ return BrowserPath.Default; -+ } -+ -+ datesForm = new FormGroup({ -+ selectedDeletionDatePreset: new FormControl(), -+ selectedExpirationDatePreset: new FormControl(), -+ defaultDeletionDateTime: new FormControl(), -+ defaultExpirationDateTime: new FormControl(), -+ fallbackDeletionDate: new FormControl(), -+ fallbackDeletionTime: new FormControl(), -+ fallbackExpirationDate: new FormControl(), -+ fallbackExpirationTime: new FormControl(), -+ }); -+ -+ deletionDatePresets: any[] = [ -+ { name: this.i18nService.t("oneHour"), value: DatePreset.OneHour }, -+ { name: this.i18nService.t("oneDay"), value: DatePreset.OneDay }, -+ { name: this.i18nService.t("days", "2"), value: DatePreset.TwoDays }, -+ { name: this.i18nService.t("days", "3"), value: DatePreset.ThreeDays }, -+ { name: this.i18nService.t("days", "7"), value: DatePreset.SevenDays }, -+ { name: this.i18nService.t("days", "30"), value: DatePreset.ThirtyDays }, -+ { name: this.i18nService.t("custom"), value: DatePreset.Custom }, -+ ]; -+ -+ expirationDatePresets: any[] = [ -+ { name: this.i18nService.t("never"), value: DatePreset.Never }, -+ ].concat([...this.deletionDatePresets]); -+ -+ get selectedDeletionDatePreset(): FormControl { -+ return this.datesForm.get("selectedDeletionDatePreset") as FormControl; -+ } -+ -+ get selectedExpirationDatePreset(): FormControl { -+ return this.datesForm.get("selectedExpirationDatePreset") as FormControl; -+ } -+ -+ get defaultDeletionDateTime(): FormControl { -+ return this.datesForm.get("defaultDeletionDateTime") as FormControl; -+ } -+ -+ get defaultExpirationDateTime(): FormControl { -+ return this.datesForm.get("defaultExpirationDateTime") as FormControl; -+ } -+ -+ get fallbackDeletionDate(): FormControl { -+ return this.datesForm.get("fallbackDeletionDate") as FormControl; -+ } -+ -+ get fallbackDeletionTime(): FormControl { -+ return this.datesForm.get("fallbackDeletionTime") as FormControl; -+ } -+ -+ get fallbackExpirationDate(): FormControl { -+ return this.datesForm.get("fallbackExpirationDate") as FormControl; -+ } -+ -+ get fallbackExpirationTime(): FormControl { -+ return this.datesForm.get("fallbackExpirationTime") as FormControl; -+ } -+ -+ // Should be able to call these at any time and compute a submitable value -+ get formattedDeletionDate(): string { -+ switch (this.selectedDeletionDatePreset.value as DatePreset) { -+ case DatePreset.Never: -+ this.selectedDeletionDatePreset.setValue(DatePreset.SevenDays); -+ return this.formattedDeletionDate; -+ case DatePreset.Custom: -+ switch (this.browserPath) { -+ case BrowserPath.Safari: -+ case BrowserPath.Firefox: -+ return this.fallbackDeletionDate.value + "T" + this.fallbackDeletionTime.value; -+ default: -+ return this.defaultDeletionDateTime.value; - } -+ default: -+ const now = new Date(); -+ const miliseconds = now.setTime( -+ now.getTime() + (this.selectedDeletionDatePreset.value as number) * 60 * 60 * 1000 -+ ); -+ return new Date(miliseconds).toString(); - } -+ } - -- get formattedExpirationDate(): string { -- switch (this.selectedExpirationDatePreset.value as DatePreset) { -- case DatePreset.Never: -- return null; -- case DatePreset.Custom: -- switch (this.browserPath) { -- case BrowserPath.Safari: -- case BrowserPath.Firefox: -- if ((!this.fallbackExpirationDate.value || !this.fallbackExpirationTime.value) && -- this.editMode) { -- return null; -- } -- return this.fallbackExpirationDate.value + 'T' + this.fallbackExpirationTime.value; -- default: -- if (!this.defaultExpirationDateTime.value) { -- return null; -- } -- return this.defaultExpirationDateTime.value; -- } -- default: -- const now = new Date(); -- const miliseconds = now.setTime(now.getTime() + -- (this.selectedExpirationDatePreset.value as number * 60 * 60 * 1000)); -- return new Date(miliseconds).toString(); -+ get formattedExpirationDate(): string { -+ switch (this.selectedExpirationDatePreset.value as DatePreset) { -+ case DatePreset.Never: -+ return null; -+ case DatePreset.Custom: -+ switch (this.browserPath) { -+ case BrowserPath.Safari: -+ case BrowserPath.Firefox: -+ if ( -+ (!this.fallbackExpirationDate.value || !this.fallbackExpirationTime.value) && -+ this.editMode -+ ) { -+ return null; -+ } -+ return this.fallbackExpirationDate.value + "T" + this.fallbackExpirationTime.value; -+ default: -+ if (!this.defaultExpirationDateTime.value) { -+ return null; -+ } -+ return this.defaultExpirationDateTime.value; - } -+ default: -+ const now = new Date(); -+ const miliseconds = now.setTime( -+ now.getTime() + (this.selectedExpirationDatePreset.value as number) * 60 * 60 * 1000 -+ ); -+ return new Date(miliseconds).toString(); - } -- // -- -- get safariDeletionTimePresetOptions() { -- return this.safariTimePresetOptions(DateField.DeletionDate); -- } -- -- get safariExpirationTimePresetOptions() { -- return this.safariTimePresetOptions(DateField.ExpriationDate); -- } -- -- private get nextWeek(): Date { -- const nextWeek = new Date(); -- nextWeek.setDate(nextWeek.getDate() + 7); -- return nextWeek; -- } -- -- constructor(protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, -- protected datePipe: DatePipe) { -- } -- -- ngOnInit(): void { -- this.setInitialFormValues(); -- this.emitDates(); -- this.datesForm.valueChanges.subscribe(() => { -- this.emitDates(); -- }); -+ } -+ // -+ -+ get safariDeletionTimePresetOptions() { -+ return this.safariTimePresetOptions(DateField.DeletionDate); -+ } -+ -+ get safariExpirationTimePresetOptions() { -+ return this.safariTimePresetOptions(DateField.ExpriationDate); -+ } -+ -+ private get nextWeek(): Date { -+ const nextWeek = new Date(); -+ nextWeek.setDate(nextWeek.getDate() + 7); -+ return nextWeek; -+ } -+ -+ constructor( -+ protected i18nService: I18nService, -+ protected platformUtilsService: PlatformUtilsService, -+ protected datePipe: DatePipe -+ ) {} -+ -+ ngOnInit(): void { -+ this.setInitialFormValues(); -+ this.emitDates(); -+ this.datesForm.valueChanges.subscribe(() => { -+ this.emitDates(); -+ }); -+ } -+ -+ onDeletionDatePresetSelect(value: DatePreset) { -+ this.selectedDeletionDatePreset.setValue(value); -+ } -+ -+ clearExpiration() { -+ switch (this.browserPath) { -+ case BrowserPath.Safari: -+ case BrowserPath.Firefox: -+ this.fallbackExpirationDate.setValue(null); -+ this.fallbackExpirationTime.setValue(null); -+ break; -+ case BrowserPath.Default: -+ this.defaultExpirationDateTime.setValue(null); -+ break; - } -+ } - -- onDeletionDatePresetSelect(value: DatePreset) { -- this.selectedDeletionDatePreset.setValue(value); -+ protected emitDates() { -+ this.datesChanged.emit({ -+ deletionDate: this.formattedDeletionDate, -+ expirationDate: this.formattedExpirationDate, -+ }); -+ } -+ -+ protected setInitialFormValues() { -+ if (this.editMode) { -+ this.selectedDeletionDatePreset.setValue(DatePreset.Custom); -+ this.selectedExpirationDatePreset.setValue(DatePreset.Custom); -+ switch (this.browserPath) { -+ case BrowserPath.Safari: -+ case BrowserPath.Firefox: -+ this.fallbackDeletionDate.setValue(this.initialDeletionDate.toISOString().slice(0, 10)); -+ this.fallbackDeletionTime.setValue(this.initialDeletionDate.toTimeString().slice(0, 5)); -+ if (this.initialExpirationDate != null) { -+ this.fallbackExpirationDate.setValue( -+ this.initialExpirationDate.toISOString().slice(0, 10) -+ ); -+ this.fallbackExpirationTime.setValue( -+ this.initialExpirationDate.toTimeString().slice(0, 5) -+ ); -+ } -+ break; -+ case BrowserPath.Default: -+ if (this.initialExpirationDate) { -+ this.defaultExpirationDateTime.setValue( -+ this.datePipe.transform(new Date(this.initialExpirationDate), "yyyy-MM-ddTHH:mm") -+ ); -+ } -+ this.defaultDeletionDateTime.setValue( -+ this.datePipe.transform(new Date(this.initialDeletionDate), "yyyy-MM-ddTHH:mm") -+ ); -+ break; -+ } -+ } else { -+ this.selectedDeletionDatePreset.setValue(DatePreset.SevenDays); -+ this.selectedExpirationDatePreset.setValue(DatePreset.Never); -+ -+ switch (this.browserPath) { -+ case BrowserPath.Safari: -+ this.fallbackDeletionDate.setValue(this.nextWeek.toISOString().slice(0, 10)); -+ this.fallbackDeletionTime.setValue( -+ this.safariTimePresetOptions(DateField.DeletionDate)[1].twentyFourHour -+ ); -+ break; -+ default: -+ break; -+ } - } -- -- clearExpiration() { -- switch (this.browserPath) { -- case BrowserPath.Safari: -- case BrowserPath.Firefox: -- this.fallbackExpirationDate.setValue(null); -- this.fallbackExpirationTime.setValue(null); -- break; -- case BrowserPath.Default: -- this.defaultExpirationDateTime.setValue(null); -- break; -+ } -+ -+ protected safariTimePresetOptions(field: DateField): TimeOption[] { -+ // init individual arrays for major sort groups -+ const noon: TimeOption[] = []; -+ const midnight: TimeOption[] = []; -+ const ams: TimeOption[] = []; -+ const pms: TimeOption[] = []; -+ -+ // determine minute skip (5 min, 10 min, 15 min, etc.) -+ const minuteIncrementer = 15; -+ -+ // loop through each hour on a 12 hour system -+ for (let h = 1; h <= 12; h++) { -+ // loop through each minute in the hour using the skip to incriment -+ for (let m = 0; m < 60; m += minuteIncrementer) { -+ // init the final strings that will be added to the lists -+ let hour = h.toString(); -+ let minutes = m.toString(); -+ -+ // add prepending 0s to single digit hours/minutes -+ if (h < 10) { -+ hour = "0" + hour; -+ } -+ if (m < 10) { -+ minutes = "0" + minutes; - } -- } -- -- protected emitDates() { -- this.datesChanged.emit({ -- deletionDate: this.formattedDeletionDate, -- expirationDate: this.formattedExpirationDate, -- }); -- } - -- protected setInitialFormValues() { -- if (this.editMode) { -- this.selectedDeletionDatePreset.setValue(DatePreset.Custom); -- this.selectedExpirationDatePreset.setValue(DatePreset.Custom); -- switch (this.browserPath) { -- case BrowserPath.Safari: -- case BrowserPath.Firefox: -- this.fallbackDeletionDate.setValue(this.initialDeletionDate.toISOString().slice(0, 10)); -- this.fallbackDeletionTime.setValue(this.initialDeletionDate.toTimeString().slice(0, 5)); -- if (this.initialExpirationDate != null) { -- this.fallbackExpirationDate.setValue(this.initialExpirationDate.toISOString().slice(0, 10)); -- this.fallbackExpirationTime.setValue(this.initialExpirationDate.toTimeString().slice(0, 5)); -- } -- break; -- case BrowserPath.Default: -- if (this.initialExpirationDate) { -- this.defaultExpirationDateTime.setValue( -- this.datePipe.transform(new Date(this.initialExpirationDate), 'yyyy-MM-ddTHH:mm')); -- } -- this.defaultDeletionDateTime.setValue(this.datePipe.transform(new Date(this.initialDeletionDate), 'yyyy-MM-ddTHH:mm')); -- break; -- } -+ // build time strings and push to relevant sort groups -+ if (h === 12) { -+ const midnightOption: TimeOption = { -+ twelveHour: `${hour}:${minutes} AM`, -+ twentyFourHour: `00:${minutes}`, -+ }; -+ midnight.push(midnightOption); -+ -+ const noonOption: TimeOption = { -+ twelveHour: `${hour}:${minutes} PM`, -+ twentyFourHour: `${hour}:${minutes}`, -+ }; -+ noon.push(noonOption); - } else { -- this.selectedDeletionDatePreset.setValue(DatePreset.SevenDays); -- this.selectedExpirationDatePreset.setValue(DatePreset.Never); -- -- switch (this.browserPath) { -- case BrowserPath.Safari: -- this.fallbackDeletionDate.setValue(this.nextWeek.toISOString().slice(0, 10)); -- this.fallbackDeletionTime.setValue(this.safariTimePresetOptions(DateField.DeletionDate)[1].twentyFourHour); -- break; -- default: -- break; -- } -+ const amOption: TimeOption = { -+ twelveHour: `${hour}:${minutes} AM`, -+ twentyFourHour: `${hour}:${minutes}`, -+ }; -+ ams.push(amOption); -+ -+ const pmOption: TimeOption = { -+ twelveHour: `${hour}:${minutes} PM`, -+ twentyFourHour: `${h + 12}:${minutes}`, -+ }; -+ pms.push(pmOption); - } -+ } - } - -- protected safariTimePresetOptions(field: DateField): TimeOption[] { -- // init individual arrays for major sort groups -- const noon: TimeOption[] = []; -- const midnight: TimeOption[] = []; -- const ams: TimeOption[] = []; -- const pms: TimeOption[] = []; -- -- // determine minute skip (5 min, 10 min, 15 min, etc.) -- const minuteIncrementer = 15; -- -- // loop through each hour on a 12 hour system -- for (let h = 1; h <= 12; h++) { -- // loop through each minute in the hour using the skip to incriment -- for (let m = 0; m < 60; m += minuteIncrementer) { -- // init the final strings that will be added to the lists -- let hour = h.toString(); -- let minutes = m.toString(); -- -- // add prepending 0s to single digit hours/minutes -- if (h < 10) { -- hour = '0' + hour; -- } -- if (m < 10) { -- minutes = '0' + minutes; -- } -- -- // build time strings and push to relevant sort groups -- if (h === 12) { -- const midnightOption: TimeOption = { -- twelveHour: `${hour}:${minutes} AM`, -- twentyFourHour: `00:${minutes}`, -- }; -- midnight.push(midnightOption); -- -- const noonOption: TimeOption = { -- twelveHour: `${hour}:${minutes} PM`, -- twentyFourHour: `${hour}:${minutes}`, -- }; -- noon.push(noonOption); -- } else { -- const amOption: TimeOption = { -- twelveHour: `${hour}:${minutes} AM`, -- twentyFourHour: `${hour}:${minutes}`, -- }; -- ams.push(amOption); -- -- const pmOption: TimeOption = { -- twelveHour: `${hour}:${minutes} PM`, -- twentyFourHour: `${h + 12}:${minutes}`, -- }; -- pms.push(pmOption); -- } -- } -- } -- -- // bring all the arrays together in the right order -- const validTimes = [...midnight, ...ams, ...noon, ...pms]; -- -- // determine if an unsupported value already exists on the send & add that to the top of the option list -- // example: if the Send was created with a different client -- if (field === DateField.ExpriationDate && this.initialExpirationDate != null && this.editMode) { -- const previousValue: TimeOption = { -- twelveHour: this.datePipe.transform(this.initialExpirationDate, 'hh:mm a'), -- twentyFourHour: this.datePipe.transform(this.initialExpirationDate, 'HH:mm'), -- }; -- return [previousValue, { twelveHour: null, twentyFourHour: null }, ...validTimes]; -- } else if (field === DateField.DeletionDate && this.initialDeletionDate != null && this.editMode) { -- const previousValue: TimeOption = { -- twelveHour: this.datePipe.transform(this.initialDeletionDate, 'hh:mm a'), -- twentyFourHour: this.datePipe.transform(this.initialDeletionDate, 'HH:mm'), -- }; -- return [previousValue, ...validTimes]; -- } else { -- return [{ twelveHour: null, twentyFourHour: null }, ...validTimes]; -- } -+ // bring all the arrays together in the right order -+ const validTimes = [...midnight, ...ams, ...noon, ...pms]; -+ -+ // determine if an unsupported value already exists on the send & add that to the top of the option list -+ // example: if the Send was created with a different client -+ if (field === DateField.ExpriationDate && this.initialExpirationDate != null && this.editMode) { -+ const previousValue: TimeOption = { -+ twelveHour: this.datePipe.transform(this.initialExpirationDate, "hh:mm a"), -+ twentyFourHour: this.datePipe.transform(this.initialExpirationDate, "HH:mm"), -+ }; -+ return [previousValue, { twelveHour: null, twentyFourHour: null }, ...validTimes]; -+ } else if ( -+ field === DateField.DeletionDate && -+ this.initialDeletionDate != null && -+ this.editMode -+ ) { -+ const previousValue: TimeOption = { -+ twelveHour: this.datePipe.transform(this.initialDeletionDate, "hh:mm a"), -+ twentyFourHour: this.datePipe.transform(this.initialDeletionDate, "HH:mm"), -+ }; -+ return [previousValue, ...validTimes]; -+ } else { -+ return [{ twelveHour: null, twentyFourHour: null }, ...validTimes]; - } -+ } - } -diff --git a/jslib/angular/src/components/send/send.component.ts b/jslib/angular/src/components/send/send.component.ts -index 77f36459..20b2c095 100644 ---- a/jslib/angular/src/components/send/send.component.ts -+++ b/jslib/angular/src/components/send/send.component.ts -@@ -1,203 +1,212 @@ --import { -- Directive, -- NgZone, -- OnInit, --} from '@angular/core'; -- --import { PolicyType } from 'jslib-common/enums/policyType'; --import { SendType } from 'jslib-common/enums/sendType'; -- --import { SendView } from 'jslib-common/models/view/sendView'; -- --import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { LogService } from 'jslib-common/abstractions/log.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { PolicyService } from 'jslib-common/abstractions/policy.service'; --import { SearchService } from 'jslib-common/abstractions/search.service'; --import { SendService } from 'jslib-common/abstractions/send.service'; --import { UserService } from 'jslib-common/abstractions/user.service'; -+import { Directive, NgZone, OnInit } from "@angular/core"; - --@Directive() --export class SendComponent implements OnInit { -+import { PolicyType } from "jslib-common/enums/policyType"; -+import { SendType } from "jslib-common/enums/sendType"; - -- disableSend = false; -- sendType = SendType; -- loaded = false; -- loading = true; -- refreshing = false; -- expired: boolean = false; -- type: SendType = null; -- sends: SendView[] = []; -- filteredSends: SendView[] = []; -- searchText: string; -- selectedType: SendType; -- selectedAll: boolean; -- searchPlaceholder: string; -- filter: (cipher: SendView) => boolean; -- searchPending = false; -- hasSearched = false; // search() function called - returns true if text qualifies for search -- -- actionPromise: any; -- onSuccessfulRemovePassword: () => Promise; -- onSuccessfulDelete: () => Promise; -- onSuccessfulLoad: () => Promise; -- -- private searchTimeout: any; -- -- constructor(protected sendService: SendService, protected i18nService: I18nService, -- protected platformUtilsService: PlatformUtilsService, protected environmentService: EnvironmentService, -- protected ngZone: NgZone, protected searchService: SearchService, -- protected policyService: PolicyService, protected userService: UserService, -- private logService: LogService) { } -- -- async ngOnInit() { -- this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend); -- } -+import { SendView } from "jslib-common/models/view/sendView"; - -- async load(filter: (send: SendView) => boolean = null) { -- this.loading = true; -- const sends = await this.sendService.getAllDecrypted(); -- this.sends = sends; -- if (this.onSuccessfulLoad != null) { -- await this.onSuccessfulLoad(); -- } else { -- // Default action -- this.selectAll(); -- } -- this.loading = false; -- this.loaded = true; -- } -+import { EnvironmentService } from "jslib-common/abstractions/environment.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { PolicyService } from "jslib-common/abstractions/policy.service"; -+import { SearchService } from "jslib-common/abstractions/search.service"; -+import { SendService } from "jslib-common/abstractions/send.service"; - -- async reload(filter: (send: SendView) => boolean = null) { -- this.loaded = false; -- this.sends = []; -- await this.load(filter); -+@Directive() -+export class SendComponent implements OnInit { -+ disableSend = false; -+ sendType = SendType; -+ loaded = false; -+ loading = true; -+ refreshing = false; -+ expired: boolean = false; -+ type: SendType = null; -+ sends: SendView[] = []; -+ filteredSends: SendView[] = []; -+ searchText: string; -+ selectedType: SendType; -+ selectedAll: boolean; -+ searchPlaceholder: string; -+ filter: (cipher: SendView) => boolean; -+ searchPending = false; -+ hasSearched = false; // search() function called - returns true if text qualifies for search -+ -+ actionPromise: any; -+ onSuccessfulRemovePassword: () => Promise; -+ onSuccessfulDelete: () => Promise; -+ onSuccessfulLoad: () => Promise; -+ -+ private searchTimeout: any; -+ -+ constructor( -+ protected sendService: SendService, -+ protected i18nService: I18nService, -+ protected platformUtilsService: PlatformUtilsService, -+ protected environmentService: EnvironmentService, -+ protected ngZone: NgZone, -+ protected searchService: SearchService, -+ protected policyService: PolicyService, -+ private logService: LogService -+ ) {} -+ -+ async ngOnInit() { -+ this.disableSend = await this.policyService.policyAppliesToUser(PolicyType.DisableSend); -+ } -+ -+ async load(filter: (send: SendView) => boolean = null) { -+ this.loading = true; -+ const sends = await this.sendService.getAllDecrypted(); -+ this.sends = sends; -+ if (this.onSuccessfulLoad != null) { -+ await this.onSuccessfulLoad(); -+ } else { -+ // Default action -+ this.selectAll(); - } -- -- async refresh() { -- try { -- this.refreshing = true; -- await this.reload(this.filter); -- } finally { -- this.refreshing = false; -- } -+ this.loading = false; -+ this.loaded = true; -+ } -+ -+ async reload(filter: (send: SendView) => boolean = null) { -+ this.loaded = false; -+ this.sends = []; -+ await this.load(filter); -+ } -+ -+ async refresh() { -+ try { -+ this.refreshing = true; -+ await this.reload(this.filter); -+ } finally { -+ this.refreshing = false; - } -+ } - -- async applyFilter(filter: (send: SendView) => boolean = null) { -- this.filter = filter; -- await this.search(null); -- } -+ async applyFilter(filter: (send: SendView) => boolean = null) { -+ this.filter = filter; -+ await this.search(null); -+ } - -- async search(timeout: number = null) { -- this.searchPending = false; -- if (this.searchTimeout != null) { -- clearTimeout(this.searchTimeout); -- } -- if (timeout == null) { -- this.hasSearched = this.searchService.isSearchable(this.searchText); -- this.filteredSends = this.sends.filter(s => this.filter == null || this.filter(s)); -- this.applyTextSearch(); -- return; -- } -- this.searchPending = true; -- this.searchTimeout = setTimeout(async () => { -- this.hasSearched = this.searchService.isSearchable(this.searchText); -- this.filteredSends = this.sends.filter(s => this.filter == null || this.filter(s)); -- this.applyTextSearch(); -- this.searchPending = false; -- }, timeout); -+ async search(timeout: number = null) { -+ this.searchPending = false; -+ if (this.searchTimeout != null) { -+ clearTimeout(this.searchTimeout); - } -- -- async removePassword(s: SendView): Promise { -- if (this.actionPromise != null || s.password == null) { -- return; -- } -- const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('removePasswordConfirmation'), -- this.i18nService.t('removePassword'), -- this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); -- if (!confirmed) { -- return false; -- } -- -- try { -- this.actionPromise = this.sendService.removePasswordWithServer(s.id); -- await this.actionPromise; -- if (this.onSuccessfulRemovePassword != null) { -- this.onSuccessfulRemovePassword(); -- } else { -- // Default actions -- this.platformUtilsService.showToast('success', null, this.i18nService.t('removedPassword')); -- await this.load(); -- } -- } catch (e) { -- this.logService.error(e); -- } -- this.actionPromise = null; -+ if (timeout == null) { -+ this.hasSearched = this.searchService.isSearchable(this.searchText); -+ this.filteredSends = this.sends.filter((s) => this.filter == null || this.filter(s)); -+ this.applyTextSearch(); -+ return; - } -- -- async delete(s: SendView): Promise { -- if (this.actionPromise != null) { -- return false; -- } -- const confirmed = await this.platformUtilsService.showDialog( -- this.i18nService.t('deleteSendConfirmation'), -- this.i18nService.t('deleteSend'), -- this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); -- if (!confirmed) { -- return false; -- } -- -- try { -- this.actionPromise = this.sendService.deleteWithServer(s.id); -- await this.actionPromise; -- -- if (this.onSuccessfulDelete != null) { -- this.onSuccessfulDelete(); -- } else { -- // Default actions -- this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedSend')); -- await this.refresh(); -- } -- } catch (e) { -- this.logService.error(e); -- } -- this.actionPromise = null; -- return true; -+ this.searchPending = true; -+ this.searchTimeout = setTimeout(async () => { -+ this.hasSearched = this.searchService.isSearchable(this.searchText); -+ this.filteredSends = this.sends.filter((s) => this.filter == null || this.filter(s)); -+ this.applyTextSearch(); -+ this.searchPending = false; -+ }, timeout); -+ } -+ -+ async removePassword(s: SendView): Promise { -+ if (this.actionPromise != null || s.password == null) { -+ return; - } -- -- copy(s: SendView) { -- const sendLinkBaseUrl = this.environmentService.getSendUrl(); -- const link = sendLinkBaseUrl + s.accessId + '/' + s.urlB64Key; -- this.platformUtilsService.copyToClipboard(link); -- this.platformUtilsService.showToast('success', null, -- this.i18nService.t('valueCopied', this.i18nService.t('sendLink'))); -+ const confirmed = await this.platformUtilsService.showDialog( -+ this.i18nService.t("removePasswordConfirmation"), -+ this.i18nService.t("removePassword"), -+ this.i18nService.t("yes"), -+ this.i18nService.t("no"), -+ "warning" -+ ); -+ if (!confirmed) { -+ return false; - } - -- searchTextChanged() { -- this.search(200); -+ try { -+ this.actionPromise = this.sendService.removePasswordWithServer(s.id); -+ await this.actionPromise; -+ if (this.onSuccessfulRemovePassword != null) { -+ this.onSuccessfulRemovePassword(); -+ } else { -+ // Default actions -+ this.platformUtilsService.showToast("success", null, this.i18nService.t("removedPassword")); -+ await this.load(); -+ } -+ } catch (e) { -+ this.logService.error(e); - } -+ this.actionPromise = null; -+ } - -- selectAll() { -- this.clearSelections(); -- this.selectedAll = true; -- this.applyFilter(null); -+ async delete(s: SendView): Promise { -+ if (this.actionPromise != null) { -+ return false; - } -- -- selectType(type: SendType) { -- this.clearSelections(); -- this.selectedType = type; -- this.applyFilter(s => s.type === type); -+ const confirmed = await this.platformUtilsService.showDialog( -+ this.i18nService.t("deleteSendConfirmation"), -+ this.i18nService.t("deleteSend"), -+ this.i18nService.t("yes"), -+ this.i18nService.t("no"), -+ "warning" -+ ); -+ if (!confirmed) { -+ return false; - } - -- clearSelections() { -- this.selectedAll = false; -- this.selectedType = null; -+ try { -+ this.actionPromise = this.sendService.deleteWithServer(s.id); -+ await this.actionPromise; -+ -+ if (this.onSuccessfulDelete != null) { -+ this.onSuccessfulDelete(); -+ } else { -+ // Default actions -+ this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedSend")); -+ await this.refresh(); -+ } -+ } catch (e) { -+ this.logService.error(e); - } -- -- private applyTextSearch() { -- if (this.searchText != null) { -- this.filteredSends = this.searchService.searchSends(this.filteredSends, this.searchText); -- } -+ this.actionPromise = null; -+ return true; -+ } -+ -+ copy(s: SendView) { -+ const sendLinkBaseUrl = this.environmentService.getSendUrl(); -+ const link = sendLinkBaseUrl + s.accessId + "/" + s.urlB64Key; -+ this.platformUtilsService.copyToClipboard(link); -+ this.platformUtilsService.showToast( -+ "success", -+ null, -+ this.i18nService.t("valueCopied", this.i18nService.t("sendLink")) -+ ); -+ } -+ -+ searchTextChanged() { -+ this.search(200); -+ } -+ -+ selectAll() { -+ this.clearSelections(); -+ this.selectedAll = true; -+ this.applyFilter(null); -+ } -+ -+ selectType(type: SendType) { -+ this.clearSelections(); -+ this.selectedType = type; -+ this.applyFilter((s) => s.type === type); -+ } -+ -+ clearSelections() { -+ this.selectedAll = false; -+ this.selectedType = null; -+ } -+ -+ private applyTextSearch() { -+ if (this.searchText != null) { -+ this.filteredSends = this.searchService.searchSends(this.filteredSends, this.searchText); - } -+ } - } -diff --git a/jslib/angular/src/components/set-password.component.ts b/jslib/angular/src/components/set-password.component.ts -index c12e32e6..89f6e520 100644 ---- a/jslib/angular/src/components/set-password.component.ts -+++ b/jslib/angular/src/components/set-password.component.ts -@@ -1,151 +1,186 @@ --import { Directive } from '@angular/core'; --import { -- ActivatedRoute, -- Router --} from '@angular/router'; -+import { Directive } from "@angular/core"; -+import { ActivatedRoute, Router } from "@angular/router"; - --import { first } from 'rxjs/operators'; -+import { first } from "rxjs/operators"; - --import { ApiService } from 'jslib-common/abstractions/api.service'; --import { CryptoService } from 'jslib-common/abstractions/crypto.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { MessagingService } from 'jslib-common/abstractions/messaging.service'; --import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { PolicyService } from 'jslib-common/abstractions/policy.service'; --import { SyncService } from 'jslib-common/abstractions/sync.service'; --import { UserService } from 'jslib-common/abstractions/user.service'; -+import { ApiService } from "jslib-common/abstractions/api.service"; -+import { CryptoService } from "jslib-common/abstractions/crypto.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { MessagingService } from "jslib-common/abstractions/messaging.service"; -+import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { PolicyService } from "jslib-common/abstractions/policy.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; -+import { SyncService } from "jslib-common/abstractions/sync.service"; - --import { EncString } from 'jslib-common/models/domain/encString'; --import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey'; -+import { EncString } from "jslib-common/models/domain/encString"; -+import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey"; - --import { KeysRequest } from 'jslib-common/models/request/keysRequest'; --import { OrganizationUserResetPasswordEnrollmentRequest } from 'jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest'; --import { SetPasswordRequest } from 'jslib-common/models/request/setPasswordRequest'; -+import { KeysRequest } from "jslib-common/models/request/keysRequest"; -+import { OrganizationUserResetPasswordEnrollmentRequest } from "jslib-common/models/request/organizationUserResetPasswordEnrollmentRequest"; -+import { SetPasswordRequest } from "jslib-common/models/request/setPasswordRequest"; - --import { ChangePasswordComponent as BaseChangePasswordComponent } from './change-password.component'; -+import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component"; - --import { HashPurpose } from 'jslib-common/enums/hashPurpose'; --import { KdfType } from 'jslib-common/enums/kdfType'; -+import { HashPurpose } from "jslib-common/enums/hashPurpose"; -+import { KdfType } from "jslib-common/enums/kdfType"; - --import { Utils } from 'jslib-common/misc/utils'; -+import { Utils } from "jslib-common/misc/utils"; - - @Directive() - export class SetPasswordComponent extends BaseChangePasswordComponent { -- syncLoading: boolean = true; -- showPassword: boolean = false; -- hint: string = ''; -- identifier: string = null; -- orgId: string; -- resetPasswordAutoEnroll = false; -- -- onSuccessfulChangePassword: () => Promise; -- successRoute = 'vault'; -- -- constructor(i18nService: I18nService, cryptoService: CryptoService, messagingService: MessagingService, -- userService: UserService, passwordGenerationService: PasswordGenerationService, -- platformUtilsService: PlatformUtilsService, policyService: PolicyService, protected router: Router, -- private apiService: ApiService, private syncService: SyncService, private route: ActivatedRoute) { -- super(i18nService, cryptoService, messagingService, userService, passwordGenerationService, -- platformUtilsService, policyService); -+ syncLoading: boolean = true; -+ showPassword: boolean = false; -+ hint: string = ""; -+ identifier: string = null; -+ orgId: string; -+ resetPasswordAutoEnroll = false; -+ -+ onSuccessfulChangePassword: () => Promise; -+ successRoute = "vault"; -+ -+ constructor( -+ i18nService: I18nService, -+ cryptoService: CryptoService, -+ messagingService: MessagingService, -+ passwordGenerationService: PasswordGenerationService, -+ platformUtilsService: PlatformUtilsService, -+ policyService: PolicyService, -+ protected router: Router, -+ private apiService: ApiService, -+ private syncService: SyncService, -+ private route: ActivatedRoute, -+ stateService: StateService -+ ) { -+ super( -+ i18nService, -+ cryptoService, -+ messagingService, -+ passwordGenerationService, -+ platformUtilsService, -+ policyService, -+ stateService -+ ); -+ } -+ -+ async ngOnInit() { -+ await this.syncService.fullSync(true); -+ this.syncLoading = false; -+ -+ this.route.queryParams.pipe(first()).subscribe(async (qParams) => { -+ if (qParams.identifier != null) { -+ this.identifier = qParams.identifier; -+ } -+ }); -+ -+ // Automatic Enrollment Detection -+ if (this.identifier != null) { -+ try { -+ const response = await this.apiService.getOrganizationAutoEnrollStatus(this.identifier); -+ this.orgId = response.id; -+ this.resetPasswordAutoEnroll = response.resetPasswordEnabled; -+ this.enforcedPolicyOptions = -+ await this.policyService.getMasterPasswordPoliciesForInvitedUsers(this.orgId); -+ } catch { -+ this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); -+ } - } - -- async ngOnInit() { -- await this.syncService.fullSync(true); -- this.syncLoading = false; -- -- this.route.queryParams.pipe(first()).subscribe(async qParams => { -- if (qParams.identifier != null) { -- this.identifier = qParams.identifier; -+ super.ngOnInit(); -+ } -+ -+ async setupSubmitActions() { -+ this.kdf = KdfType.PBKDF2_SHA256; -+ const useLowerKdf = this.platformUtilsService.isIE(); -+ this.kdfIterations = useLowerKdf ? 10000 : 100000; -+ return true; -+ } -+ -+ async performSubmitActions( -+ masterPasswordHash: string, -+ key: SymmetricCryptoKey, -+ encKey: [SymmetricCryptoKey, EncString] -+ ) { -+ const keys = await this.cryptoService.makeKeyPair(encKey[0]); -+ const request = new SetPasswordRequest( -+ masterPasswordHash, -+ encKey[1].encryptedString, -+ this.hint, -+ this.kdf, -+ this.kdfIterations, -+ this.identifier, -+ new KeysRequest(keys[0], keys[1].encryptedString) -+ ); -+ try { -+ if (this.resetPasswordAutoEnroll) { -+ this.formPromise = this.apiService -+ .setPassword(request) -+ .then(async () => { -+ await this.onSetPasswordSuccess(key, encKey, keys); -+ return this.apiService.getOrganizationKeys(this.orgId); -+ }) -+ .then(async (response) => { -+ if (response == null) { -+ throw new Error(this.i18nService.t("resetPasswordOrgKeysError")); - } -+ const userId = await this.stateService.getUserId(); -+ const publicKey = Utils.fromB64ToArray(response.publicKey); -+ -+ // RSA Encrypt user's encKey.key with organization public key -+ const userEncKey = await this.cryptoService.getEncKey(); -+ const encryptedKey = await this.cryptoService.rsaEncrypt( -+ userEncKey.key, -+ publicKey.buffer -+ ); -+ -+ const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest(); -+ resetRequest.resetPasswordKey = encryptedKey.encryptedString; -+ -+ return this.apiService.putOrganizationUserResetPasswordEnrollment( -+ this.orgId, -+ userId, -+ resetRequest -+ ); -+ }); -+ } else { -+ this.formPromise = this.apiService.setPassword(request).then(async () => { -+ await this.onSetPasswordSuccess(key, encKey, keys); - }); -+ } - -- // Automatic Enrollment Detection -- if (this.identifier != null) { -- try { -- const response = await this.apiService.getOrganizationAutoEnrollStatus(this.identifier); -- this.orgId = response.id; -- this.resetPasswordAutoEnroll = response.resetPasswordEnabled; -- } catch { -- this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred')); -- } -- } -- -- super.ngOnInit(); -- } -- -- async setupSubmitActions() { -- this.kdf = KdfType.PBKDF2_SHA256; -- const useLowerKdf = this.platformUtilsService.isIE(); -- this.kdfIterations = useLowerKdf ? 10000 : 100000; -- return true; -- } -- -- async performSubmitActions(masterPasswordHash: string, key: SymmetricCryptoKey, -- encKey: [SymmetricCryptoKey, EncString]) { -- const keys = await this.cryptoService.makeKeyPair(encKey[0]); -- const request = new SetPasswordRequest( -- masterPasswordHash, -- encKey[1].encryptedString, -- this.hint, -- this.kdf, -- this.kdfIterations, -- this.identifier, -- new KeysRequest(keys[0], keys[1].encryptedString) -- ); -- try { -- if (this.resetPasswordAutoEnroll) { -- this.formPromise = this.apiService.setPassword(request).then(async () => { -- await this.onSetPasswordSuccess(key, encKey, keys); -- return this.apiService.getOrganizationKeys(this.orgId); -- }).then(async response => { -- if (response == null) { -- throw new Error(this.i18nService.t('resetPasswordOrgKeysError')); -- } -- const userId = await this.userService.getUserId(); -- const publicKey = Utils.fromB64ToArray(response.publicKey); -- -- // RSA Encrypt user's encKey.key with organization public key -- const userEncKey = await this.cryptoService.getEncKey(); -- const encryptedKey = await this.cryptoService.rsaEncrypt(userEncKey.key, publicKey.buffer); -- -- const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest(); -- resetRequest.resetPasswordKey = encryptedKey.encryptedString; -- -- return this.apiService.putOrganizationUserResetPasswordEnrollment(this.orgId, userId, resetRequest); -- }); -- } else { -- this.formPromise = this.apiService.setPassword(request).then(async () => { -- await this.onSetPasswordSuccess(key, encKey, keys); -- }); -- } -- -- await this.formPromise; -- -- if (this.onSuccessfulChangePassword != null) { -- this.onSuccessfulChangePassword(); -- } else { -- this.router.navigate([this.successRoute]); -- } -- } catch { -- this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred')); -- } -- } -- -- togglePassword(confirmField: boolean) { -- this.showPassword = !this.showPassword; -- document.getElementById(confirmField ? 'masterPasswordRetype' : 'masterPassword').focus(); -- } -- -- private async onSetPasswordSuccess(key: SymmetricCryptoKey, encKey: [SymmetricCryptoKey, EncString], keys: [string, EncString]) { -- await this.userService.setInformation(await this.userService.getUserId(), await this.userService.getEmail(), -- this.kdf, this.kdfIterations); -- await this.cryptoService.setKey(key); -- await this.cryptoService.setEncKey(encKey[1].encryptedString); -- await this.cryptoService.setEncPrivateKey(keys[1].encryptedString); -+ await this.formPromise; - -- const localKeyHash = await this.cryptoService.hashPassword(this.masterPassword, key, -- HashPurpose.LocalAuthorization); -- await this.cryptoService.setKeyHash(localKeyHash); -+ if (this.onSuccessfulChangePassword != null) { -+ this.onSuccessfulChangePassword(); -+ } else { -+ this.router.navigate([this.successRoute]); -+ } -+ } catch { -+ this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); - } -+ } -+ -+ togglePassword(confirmField: boolean) { -+ this.showPassword = !this.showPassword; -+ document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus(); -+ } -+ -+ private async onSetPasswordSuccess( -+ key: SymmetricCryptoKey, -+ encKey: [SymmetricCryptoKey, EncString], -+ keys: [string, EncString] -+ ) { -+ await this.stateService.setKdfType(this.kdf); -+ await this.stateService.setKdfIterations(this.kdfIterations); -+ await this.cryptoService.setKey(key); -+ await this.cryptoService.setEncKey(encKey[1].encryptedString); -+ await this.cryptoService.setEncPrivateKey(keys[1].encryptedString); -+ -+ const localKeyHash = await this.cryptoService.hashPassword( -+ this.masterPassword, -+ key, -+ HashPurpose.LocalAuthorization -+ ); -+ await this.cryptoService.setKeyHash(localKeyHash); -+ } - } -diff --git a/jslib/angular/src/components/set-pin.component.ts b/jslib/angular/src/components/set-pin.component.ts -index d2cfae3a..bc1e804e 100644 ---- a/jslib/angular/src/components/set-pin.component.ts -+++ b/jslib/angular/src/components/set-pin.component.ts -@@ -1,59 +1,55 @@ --import { -- Directive, -- OnInit --} from '@angular/core'; -+import { Directive, OnInit } from "@angular/core"; - --import { CryptoService } from 'jslib-common/abstractions/crypto.service'; --import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; --import { StorageService } from 'jslib-common/abstractions/storage.service'; --import { UserService } from 'jslib-common/abstractions/user.service'; --import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; -+import { CryptoService } from "jslib-common/abstractions/crypto.service"; -+import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; - --import { ConstantsService } from 'jslib-common/services/constants.service'; -+import { Utils } from "jslib-common/misc/utils"; - --import { Utils } from 'jslib-common/misc/utils'; -- --import { ModalRef } from './modal/modal.ref'; -+import { ModalRef } from "./modal/modal.ref"; - - @Directive() - export class SetPinComponent implements OnInit { -- -- pin = ''; -- showPin = false; -- masterPassOnRestart = true; -- showMasterPassOnRestart = true; -- -- constructor(private modalRef: ModalRef, private cryptoService: CryptoService, private userService: UserService, -- private storageService: StorageService, private vaultTimeoutService: VaultTimeoutService, -- private keyConnectorService: KeyConnectorService) { } -- -- async ngOnInit() { -- this.showMasterPassOnRestart = this.masterPassOnRestart = !await this.keyConnectorService.getUsesKeyConnector(); -+ pin = ""; -+ showPin = false; -+ masterPassOnRestart = true; -+ showMasterPassOnRestart = true; -+ -+ constructor( -+ private modalRef: ModalRef, -+ private cryptoService: CryptoService, -+ private keyConnectorService: KeyConnectorService, -+ private stateService: StateService -+ ) {} -+ -+ async ngOnInit() { -+ this.showMasterPassOnRestart = this.masterPassOnRestart = -+ !(await this.keyConnectorService.getUsesKeyConnector()); -+ } -+ -+ toggleVisibility() { -+ this.showPin = !this.showPin; -+ } -+ -+ async submit() { -+ if (Utils.isNullOrWhitespace(this.pin)) { -+ this.modalRef.close(false); - } - -- toggleVisibility() { -- this.showPin = !this.showPin; -+ const kdf = await this.stateService.getKdfType(); -+ const kdfIterations = await this.stateService.getKdfIterations(); -+ const email = await this.stateService.getEmail(); -+ const pinKey = await this.cryptoService.makePinKey(this.pin, email, kdf, kdfIterations); -+ const key = await this.cryptoService.getKey(); -+ const pinProtectedKey = await this.cryptoService.encrypt(key.key, pinKey); -+ if (this.masterPassOnRestart) { -+ const encPin = await this.cryptoService.encrypt(this.pin); -+ await this.stateService.setProtectedPin(encPin.encryptedString); -+ await this.stateService.setDecryptedPinProtected(pinProtectedKey); -+ } else { -+ await this.stateService.setEncryptedPinProtected(pinProtectedKey.encryptedString); - } - -- async submit() { -- if (Utils.isNullOrWhitespace(this.pin)) { -- this.modalRef.close(false); -- } -- -- const kdf = await this.userService.getKdf(); -- const kdfIterations = await this.userService.getKdfIterations(); -- const email = await this.userService.getEmail(); -- const pinKey = await this.cryptoService.makePinKey(this.pin, email, kdf, kdfIterations); -- const key = await this.cryptoService.getKey(); -- const pinProtectedKey = await this.cryptoService.encrypt(key.key, pinKey); -- if (this.masterPassOnRestart) { -- const encPin = await this.cryptoService.encrypt(this.pin); -- await this.storageService.save(ConstantsService.protectedPin, encPin.encryptedString); -- this.vaultTimeoutService.pinProtectedKey = pinProtectedKey; -- } else { -- await this.storageService.save(ConstantsService.pinProtectedKey, pinProtectedKey.encryptedString); -- } -- -- this.modalRef.close(true); -- } -+ this.modalRef.close(true); -+ } - } -diff --git a/jslib/angular/src/components/settings/vault-timeout-input.component.ts b/jslib/angular/src/components/settings/vault-timeout-input.component.ts -index 749e7595..2f63f97f 100644 ---- a/jslib/angular/src/components/settings/vault-timeout-input.component.ts -+++ b/jslib/angular/src/components/settings/vault-timeout-input.component.ts -@@ -1,138 +1,140 @@ -+import { Directive, Input, OnInit } from "@angular/core"; - import { -- Directive, -- Input, -- OnInit, --} from '@angular/core'; --import { -- AbstractControl, -- ControlValueAccessor, -- FormBuilder, -- ValidationErrors, -- Validator --} from '@angular/forms'; -+ AbstractControl, -+ ControlValueAccessor, -+ FormBuilder, -+ ValidationErrors, -+ Validator, -+} from "@angular/forms"; - --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { PolicyService } from 'jslib-common/abstractions/policy.service'; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { PolicyService } from "jslib-common/abstractions/policy.service"; - --import { PolicyType } from 'jslib-common/enums/policyType'; --import { Policy } from 'jslib-common/models/domain/policy'; -+import { PolicyType } from "jslib-common/enums/policyType"; -+import { Policy } from "jslib-common/models/domain/policy"; - - @Directive() - export class VaultTimeoutInputComponent implements ControlValueAccessor, Validator, OnInit { -- -- get showCustom() { -- return this.form.get('vaultTimeout').value === VaultTimeoutInputComponent.CUSTOM_VALUE; -+ get showCustom() { -+ return this.form.get("vaultTimeout").value === VaultTimeoutInputComponent.CUSTOM_VALUE; -+ } -+ -+ static CUSTOM_VALUE = -100; -+ -+ form = this.formBuilder.group({ -+ vaultTimeout: [null], -+ custom: this.formBuilder.group({ -+ hours: [null], -+ minutes: [null], -+ }), -+ }); -+ -+ @Input() vaultTimeouts: { name: string; value: number }[]; -+ vaultTimeoutPolicy: Policy; -+ vaultTimeoutPolicyHours: number; -+ vaultTimeoutPolicyMinutes: number; -+ -+ private onChange: (vaultTimeout: number) => void; -+ private validatorChange: () => void; -+ -+ constructor( -+ private formBuilder: FormBuilder, -+ private policyService: PolicyService, -+ private i18nService: I18nService -+ ) {} -+ -+ async ngOnInit() { -+ if (await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout)) { -+ const vaultTimeoutPolicy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout); -+ -+ this.vaultTimeoutPolicy = vaultTimeoutPolicy[0]; -+ this.vaultTimeoutPolicyHours = Math.floor(this.vaultTimeoutPolicy.data.minutes / 60); -+ this.vaultTimeoutPolicyMinutes = this.vaultTimeoutPolicy.data.minutes % 60; -+ -+ this.vaultTimeouts = this.vaultTimeouts.filter( -+ (t) => -+ t.value <= this.vaultTimeoutPolicy.data.minutes && -+ (t.value > 0 || t.value === VaultTimeoutInputComponent.CUSTOM_VALUE) && -+ t.value != null -+ ); -+ this.validatorChange(); - } - -- static CUSTOM_VALUE = -100; -- -- form = this.fb.group({ -- vaultTimeout: [null], -- custom: this.fb.group({ -- hours: [null], -- minutes: [null], -- }), -+ this.form.valueChanges.subscribe(async (value) => { -+ this.onChange(this.getVaultTimeout(value)); - }); - -- @Input() vaultTimeouts: { name: string; value: number; }[]; -- vaultTimeoutPolicy: Policy; -- vaultTimeoutPolicyHours: number; -- vaultTimeoutPolicyMinutes: number; -- -- private onChange: (vaultTimeout: number) => void; -- private validatorChange: () => void; -- -- constructor(private fb: FormBuilder, private policyService: PolicyService, private i18nService: I18nService) { -- } -+ // Assign the previous value to the custom fields -+ this.form.get("vaultTimeout").valueChanges.subscribe((value) => { -+ if (value !== VaultTimeoutInputComponent.CUSTOM_VALUE) { -+ return; -+ } -+ -+ const current = Math.max(this.form.value.vaultTimeout, 0); -+ this.form.patchValue({ -+ custom: { -+ hours: Math.floor(current / 60), -+ minutes: current % 60, -+ }, -+ }); -+ }); -+ } - -- async ngOnInit() { -- if (await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout)) { -- const vaultTimeoutPolicy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout); -- -- this.vaultTimeoutPolicy = vaultTimeoutPolicy[0]; -- this.vaultTimeoutPolicyHours = Math.floor(this.vaultTimeoutPolicy.data.minutes / 60); -- this.vaultTimeoutPolicyMinutes = this.vaultTimeoutPolicy.data.minutes % 60; -- -- this.vaultTimeouts = this.vaultTimeouts.filter(t => -- t.value <= this.vaultTimeoutPolicy.data.minutes && -- (t.value > 0 || t.value === VaultTimeoutInputComponent.CUSTOM_VALUE) && -- t.value != null -- ); -- this.validatorChange(); -- } -- -- this.form.valueChanges.subscribe(async value => { -- this.onChange(this.getVaultTimeout(value)); -- }); -- -- // Assign the previous value to the custom fields -- this.form.get('vaultTimeout').valueChanges.subscribe(value => { -- if (value !== VaultTimeoutInputComponent.CUSTOM_VALUE) { -- return; -- } -- -- const current = Math.max(this.form.value.vaultTimeout, 0); -- this.form.patchValue({ -- custom: { -- hours: Math.floor(current / 60), -- minutes: current % 60, -- }, -- }); -- }); -- } -+ ngOnChanges() { -+ this.vaultTimeouts.push({ -+ name: this.i18nService.t("custom"), -+ value: VaultTimeoutInputComponent.CUSTOM_VALUE, -+ }); -+ } - -- ngOnChanges() { -- this.vaultTimeouts.push({ name: this.i18nService.t('custom'), value: VaultTimeoutInputComponent.CUSTOM_VALUE }); -+ getVaultTimeout(value: any) { -+ if (value.vaultTimeout !== VaultTimeoutInputComponent.CUSTOM_VALUE) { -+ return value.vaultTimeout; - } - -- getVaultTimeout(value: any) { -- if (value.vaultTimeout !== VaultTimeoutInputComponent.CUSTOM_VALUE) { -- return value.vaultTimeout; -- } -+ return value.custom.hours * 60 + value.custom.minutes; -+ } - -- return value.custom.hours * 60 + value.custom.minutes; -+ writeValue(value: number): void { -+ if (value == null) { -+ return; - } - -- writeValue(value: number): void { -- if (value == null) { -- return; -- } -- -- if (this.vaultTimeouts.every(p => p.value !== value)) { -- this.form.setValue({ -- vaultTimeout: VaultTimeoutInputComponent.CUSTOM_VALUE, -- custom: { -- hours: Math.floor(value / 60), -- minutes: value % 60, -- }, -- }); -- return; -- } -- -- this.form.patchValue({ -- vaultTimeout: value, -- }); -+ if (this.vaultTimeouts.every((p) => p.value !== value)) { -+ this.form.setValue({ -+ vaultTimeout: VaultTimeoutInputComponent.CUSTOM_VALUE, -+ custom: { -+ hours: Math.floor(value / 60), -+ minutes: value % 60, -+ }, -+ }); -+ return; - } - -- registerOnChange(onChange: any): void { -- this.onChange = onChange; -- } -+ this.form.patchValue({ -+ vaultTimeout: value, -+ }); -+ } - -- // tslint:disable-next-line -- registerOnTouched(onTouched: any): void {} -+ registerOnChange(onChange: any): void { -+ this.onChange = onChange; -+ } - -- // tslint:disable-next-line -- setDisabledState?(isDisabled: boolean): void { } -+ // tslint:disable-next-line -+ registerOnTouched(onTouched: any): void {} - -- validate(control: AbstractControl): ValidationErrors { -- if (this.vaultTimeoutPolicy && this.vaultTimeoutPolicy?.data?.minutes < control.value) { -- return { policyError: true }; -- } -+ // tslint:disable-next-line -+ setDisabledState?(isDisabled: boolean): void {} - -- return null; -+ validate(control: AbstractControl): ValidationErrors { -+ if (this.vaultTimeoutPolicy && this.vaultTimeoutPolicy?.data?.minutes < control.value) { -+ return { policyError: true }; - } - -- registerOnValidatorChange(fn: () => void): void { -- this.validatorChange = fn; -- } -+ return null; -+ } -+ -+ registerOnValidatorChange(fn: () => void): void { -+ this.validatorChange = fn; -+ } - } -diff --git a/jslib/angular/src/components/share.component.ts b/jslib/angular/src/components/share.component.ts -index e5badc37..6a13e0b2 100644 ---- a/jslib/angular/src/components/share.component.ts -+++ b/jslib/angular/src/components/share.component.ts -@@ -1,108 +1,119 @@ --import { -- Directive, -- EventEmitter, -- Input, -- OnInit, -- Output, --} from '@angular/core'; -+import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; - --import { OrganizationUserStatusType } from 'jslib-common/enums/organizationUserStatusType'; -+import { OrganizationUserStatusType } from "jslib-common/enums/organizationUserStatusType"; - --import { CipherService } from 'jslib-common/abstractions/cipher.service'; --import { CollectionService } from 'jslib-common/abstractions/collection.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { LogService } from 'jslib-common/abstractions/log.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { UserService } from 'jslib-common/abstractions/user.service'; -+import { CipherService } from "jslib-common/abstractions/cipher.service"; -+import { CollectionService } from "jslib-common/abstractions/collection.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { OrganizationService } from "jslib-common/abstractions/organization.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; - --import { Organization } from 'jslib-common/models/domain/organization'; --import { CipherView } from 'jslib-common/models/view/cipherView'; --import { CollectionView } from 'jslib-common/models/view/collectionView'; -+import { Organization } from "jslib-common/models/domain/organization"; -+import { CipherView } from "jslib-common/models/view/cipherView"; -+import { CollectionView } from "jslib-common/models/view/collectionView"; - --import { Utils } from 'jslib-common/misc/utils'; -+import { Utils } from "jslib-common/misc/utils"; - - @Directive() - export class ShareComponent implements OnInit { -- @Input() cipherId: string; -- @Input() organizationId: string; -- @Output() onSharedCipher = new EventEmitter(); -- -- formPromise: Promise; -- cipher: CipherView; -- collections: CollectionView[] = []; -- organizations: Organization[] = []; -- -- protected writeableCollections: CollectionView[] = []; -- -- constructor(protected collectionService: CollectionService, protected platformUtilsService: PlatformUtilsService, -- protected i18nService: I18nService, protected userService: UserService, -- protected cipherService: CipherService, private logService: LogService) { } -- -- async ngOnInit() { -- await this.load(); -+ @Input() cipherId: string; -+ @Input() organizationId: string; -+ @Output() onSharedCipher = new EventEmitter(); -+ -+ formPromise: Promise; -+ cipher: CipherView; -+ collections: CollectionView[] = []; -+ organizations: Organization[] = []; -+ -+ protected writeableCollections: CollectionView[] = []; -+ -+ constructor( -+ protected collectionService: CollectionService, -+ protected platformUtilsService: PlatformUtilsService, -+ protected i18nService: I18nService, -+ protected cipherService: CipherService, -+ private logService: LogService, -+ protected organizationService: OrganizationService -+ ) {} -+ -+ async ngOnInit() { -+ await this.load(); -+ } -+ -+ async load() { -+ const allCollections = await this.collectionService.getAllDecrypted(); -+ this.writeableCollections = allCollections.map((c) => c).filter((c) => !c.readOnly); -+ const orgs = await this.organizationService.getAll(); -+ this.organizations = orgs -+ .sort(Utils.getSortFunction(this.i18nService, "name")) -+ .filter((o) => o.enabled && o.status === OrganizationUserStatusType.Confirmed); -+ -+ const cipherDomain = await this.cipherService.get(this.cipherId); -+ this.cipher = await cipherDomain.decrypt(); -+ if (this.organizationId == null && this.organizations.length > 0) { -+ this.organizationId = this.organizations[0].id; - } -- -- async load() { -- const allCollections = await this.collectionService.getAllDecrypted(); -- this.writeableCollections = allCollections.map(c => c).filter(c => !c.readOnly); -- const orgs = await this.userService.getAllOrganizations(); -- this.organizations = orgs.sort(Utils.getSortFunction(this.i18nService, 'name')) -- .filter(o => o.enabled && o.status === OrganizationUserStatusType.Confirmed); -- -- const cipherDomain = await this.cipherService.get(this.cipherId); -- this.cipher = await cipherDomain.decrypt(); -- if (this.organizationId == null && this.organizations.length > 0) { -- this.organizationId = this.organizations[0].id; -- } -- this.filterCollections(); -+ this.filterCollections(); -+ } -+ -+ filterCollections() { -+ this.writeableCollections.forEach((c) => ((c as any).checked = false)); -+ if (this.organizationId == null || this.writeableCollections.length === 0) { -+ this.collections = []; -+ } else { -+ this.collections = this.writeableCollections.filter( -+ (c) => c.organizationId === this.organizationId -+ ); - } -- -- filterCollections() { -- this.writeableCollections.forEach(c => (c as any).checked = false); -- if (this.organizationId == null || this.writeableCollections.length === 0) { -- this.collections = []; -- } else { -- this.collections = this.writeableCollections.filter(c => c.organizationId === this.organizationId); -- } -+ } -+ -+ async submit(): Promise { -+ const selectedCollectionIds = this.collections -+ .filter((c) => !!(c as any).checked) -+ .map((c) => c.id); -+ if (selectedCollectionIds.length === 0) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("selectOneCollection") -+ ); -+ return; - } - -- async submit(): Promise { -- const selectedCollectionIds = this.collections -- .filter(c => !!(c as any).checked) -- .map(c => c.id); -- if (selectedCollectionIds.length === 0) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('selectOneCollection')); -- return; -- } -- -- const cipherDomain = await this.cipherService.get(this.cipherId); -- const cipherView = await cipherDomain.decrypt(); -- const orgName = this.organizations.find(o => o.id === this.organizationId)?.name ?? this.i18nService.t('organization'); -- -- try { -- this.formPromise = this.cipherService.shareWithServer(cipherView, this.organizationId, -- selectedCollectionIds).then(async () => { -- this.onSharedCipher.emit(); -- this.platformUtilsService.showToast('success', null, -- this.i18nService.t('movedItemToOrg', cipherView.name, orgName)); -- }); -- await this.formPromise; -- return true; -- } catch (e) { -- this.logService.error(e); -- } -- return false; -+ const cipherDomain = await this.cipherService.get(this.cipherId); -+ const cipherView = await cipherDomain.decrypt(); -+ const orgName = -+ this.organizations.find((o) => o.id === this.organizationId)?.name ?? -+ this.i18nService.t("organization"); -+ -+ try { -+ this.formPromise = this.cipherService -+ .shareWithServer(cipherView, this.organizationId, selectedCollectionIds) -+ .then(async () => { -+ this.onSharedCipher.emit(); -+ this.platformUtilsService.showToast( -+ "success", -+ null, -+ this.i18nService.t("movedItemToOrg", cipherView.name, orgName) -+ ); -+ }); -+ await this.formPromise; -+ return true; -+ } catch (e) { -+ this.logService.error(e); - } -- -- get canSave() { -- if (this.collections != null) { -- for (let i = 0; i < this.collections.length; i++) { -- if ((this.collections[i] as any).checked) { -- return true; -- } -- } -+ return false; -+ } -+ -+ get canSave() { -+ if (this.collections != null) { -+ for (let i = 0; i < this.collections.length; i++) { -+ if ((this.collections[i] as any).checked) { -+ return true; - } -- return false; -+ } - } -+ return false; -+ } - } -diff --git a/jslib/angular/src/components/sso.component.ts b/jslib/angular/src/components/sso.component.ts -index 1ab8e2f4..b3628863 100644 ---- a/jslib/angular/src/components/sso.component.ts -+++ b/jslib/angular/src/components/sso.component.ts -@@ -1,214 +1,263 @@ --import { Directive } from '@angular/core'; --import { -- ActivatedRoute, -- Router, --} from '@angular/router'; -+import { Directive } from "@angular/core"; -+import { ActivatedRoute, Router } from "@angular/router"; - --import { first } from 'rxjs/operators'; -+import { first } from "rxjs/operators"; - --import { ApiService } from 'jslib-common/abstractions/api.service'; --import { AuthService } from 'jslib-common/abstractions/auth.service'; --import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; --import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { LogService } from 'jslib-common/abstractions/log.service'; --import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { StateService } from 'jslib-common/abstractions/state.service'; --import { StorageService } from 'jslib-common/abstractions/storage.service'; -+import { ApiService } from "jslib-common/abstractions/api.service"; -+import { AuthService } from "jslib-common/abstractions/auth.service"; -+import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service"; -+import { EnvironmentService } from "jslib-common/abstractions/environment.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; - --import { ConstantsService } from 'jslib-common/services/constants.service'; -+import { Utils } from "jslib-common/misc/utils"; - --import { Utils } from 'jslib-common/misc/utils'; -+import { AuthResult } from "jslib-common/models/domain/authResult"; - --import { AuthResult } from 'jslib-common/models/domain/authResult'; -+import { switchMap } from 'rxjs/operators'; - - @Directive() - export class SsoComponent { -- identifier: string; -- loggingIn = false; -- -- formPromise: Promise; -- initiateSsoFormPromise: Promise; -- onSuccessfulLogin: () => Promise; -- onSuccessfulLoginNavigate: () => Promise; -- onSuccessfulLoginTwoFactorNavigate: () => Promise; -- onSuccessfulLoginChangePasswordNavigate: () => Promise; -- onSuccessfulLoginForceResetNavigate: () => Promise; -- -- protected twoFactorRoute = '2fa'; -- protected successRoute = 'lock'; -- protected changePasswordRoute = 'set-password'; -- protected forcePasswordResetRoute = 'update-temp-password'; -- protected clientId: string; -- protected redirectUri: string; -- protected state: string; -- protected codeChallenge: string; -- -- constructor(protected authService: AuthService, protected router: Router, -- protected i18nService: I18nService, protected route: ActivatedRoute, -- protected storageService: StorageService, protected stateService: StateService, -- protected platformUtilsService: PlatformUtilsService, protected apiService: ApiService, -- protected cryptoFunctionService: CryptoFunctionService, protected environmentService: EnvironmentService, -- protected passwordGenerationService: PasswordGenerationService, protected logService: LogService) { } -- -- async ngOnInit() { -- this.route.queryParams.pipe(first()).subscribe(async qParams => { -- if (qParams.code != null && qParams.state != null) { -- const codeVerifier = await this.storageService.get(ConstantsService.ssoCodeVerifierKey); -- const state = await this.storageService.get(ConstantsService.ssoStateKey); -- await this.storageService.remove(ConstantsService.ssoCodeVerifierKey); -- await this.storageService.remove(ConstantsService.ssoStateKey); -- if (qParams.code != null && codeVerifier != null && state != null && this.checkState(state, qParams.state)) { -- await this.logIn(qParams.code, codeVerifier, this.getOrgIdentifierFromState(qParams.state)); -- } -- } else if (qParams.clientId != null && qParams.redirectUri != null && qParams.state != null && -- qParams.codeChallenge != null) { -- this.redirectUri = qParams.redirectUri; -- this.state = qParams.state; -- this.codeChallenge = qParams.codeChallenge; -- this.clientId = qParams.clientId; -- } -- }); -- } -+ identifier: string; -+ loggingIn = false; -+ -+ formPromise: Promise; -+ initiateSsoFormPromise: Promise; -+ onSuccessfulLogin: () => Promise; -+ onSuccessfulLoginNavigate: () => Promise; -+ onSuccessfulLoginTwoFactorNavigate: () => Promise; -+ onSuccessfulLoginChangePasswordNavigate: () => Promise; -+ onSuccessfulLoginForceResetNavigate: () => Promise; -+ -+ protected twoFactorRoute = "2fa"; -+ protected successRoute = "lock"; -+ protected changePasswordRoute = "set-password"; -+ protected forcePasswordResetRoute = "update-temp-password"; -+ protected clientId: string; -+ protected redirectUri: string; -+ protected state: string; -+ protected codeChallenge: string; - -- async submit(returnUri?: string, includeUserIdentifier?: boolean) { -- this.initiateSsoFormPromise = this.preValidate(); -- if (await this.initiateSsoFormPromise) { -- const authorizeUrl = await this.buildAuthorizeUrl(returnUri, includeUserIdentifier); -- this.platformUtilsService.launchUri(authorizeUrl, { sameWindow: true }); -+ constructor( -+ protected authService: AuthService, -+ protected router: Router, -+ protected i18nService: I18nService, -+ protected route: ActivatedRoute, -+ protected stateService: StateService, -+ protected platformUtilsService: PlatformUtilsService, -+ protected apiService: ApiService, -+ protected cryptoFunctionService: CryptoFunctionService, -+ protected environmentService: EnvironmentService, -+ protected passwordGenerationService: PasswordGenerationService, -+ protected logService: LogService -+ ) {} -+ -+ async ngOnInit() { -+ this.route.queryParams.pipe(first()).subscribe(async (qParams) => { -+ // I have no idea why the qParams is empty here - I've hacked in an alternative very messily, but it works. -+ const workingParams = (new URL(window.location.href)).searchParams; -+ const workingSwap = { -+ code: workingParams.get('code'), -+ state: workingParams.get('state'), -+ }; -+ if (workingSwap.code != null && workingSwap.state != null) { -+ const codeVerifier = await this.stateService.getSsoCodeVerifier(); -+ const state = await this.stateService.getSsoState(); -+ await this.stateService.setSsoCodeVerifier(null); -+ await this.stateService.setSsoState(null); -+ if ( -+ workingSwap.code != null && -+ codeVerifier != null && -+ state != null && -+ this.checkState(state, workingSwap.state) -+ ) { -+ await this.logIn( -+ workingSwap.code, -+ codeVerifier, -+ this.getOrgIdentifierFromState(workingSwap.state) -+ ); - } -+ } else if ( -+ qParams.clientId != null && -+ qParams.redirectUri != null && -+ qParams.state != null && -+ qParams.codeChallenge != null -+ ) { -+ this.redirectUri = qParams.redirectUri; -+ this.state = qParams.state; -+ this.codeChallenge = qParams.codeChallenge; -+ this.clientId = qParams.clientId; -+ } -+ }); -+ } -+ -+ async submit(returnUri?: string, includeUserIdentifier?: boolean) { -+ this.initiateSsoFormPromise = this.preValidate(); -+ if (await this.initiateSsoFormPromise) { -+ const authorizeUrl = await this.buildAuthorizeUrl(returnUri, includeUserIdentifier); -+ this.platformUtilsService.launchUri(authorizeUrl, { sameWindow: true }); - } -+ } - -- async preValidate(): Promise { -- if (this.identifier == null || this.identifier === '') { -- this.platformUtilsService.showToast('error', this.i18nService.t('ssoValidationFailed'), -- this.i18nService.t('ssoIdentifierRequired')); -- return false; -- } -- return await this.apiService.preValidateSso(this.identifier); -+ async preValidate(): Promise { -+ if (this.identifier == null || this.identifier === "") { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("ssoValidationFailed"), -+ this.i18nService.t("ssoIdentifierRequired") -+ ); -+ return false; - } -+ return await this.apiService.preValidateSso(this.identifier); -+ } - -- protected async buildAuthorizeUrl(returnUri?: string, includeUserIdentifier?: boolean): Promise { -- let codeChallenge = this.codeChallenge; -- let state = this.state; -- -- const passwordOptions: any = { -- type: 'password', -- length: 64, -- uppercase: true, -- lowercase: true, -- numbers: true, -- special: false, -- }; -+ protected async buildAuthorizeUrl( -+ returnUri?: string, -+ includeUserIdentifier?: boolean -+ ): Promise { -+ let codeChallenge = this.codeChallenge; -+ let state = this.state; - -- if (codeChallenge == null) { -- const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); -- const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, 'sha256'); -- codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); -- await this.storageService.save(ConstantsService.ssoCodeVerifierKey, codeVerifier); -- } -+ const passwordOptions: any = { -+ type: "password", -+ length: 64, -+ uppercase: true, -+ lowercase: true, -+ numbers: true, -+ special: false, -+ }; - -- if (state == null) { -- state = await this.passwordGenerationService.generatePassword(passwordOptions); -- if (returnUri) { -- state += `_returnUri='${returnUri}'`; -- } -- } -+ if (codeChallenge == null) { -+ const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); -+ const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, "sha256"); -+ codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); -+ await this.stateService.setSsoCodeVerifier(codeVerifier); -+ } - -- // Add Organization Identifier to state -- state += `_identifier=${this.identifier}`; -+ if (state == null) { -+ state = await this.passwordGenerationService.generatePassword(passwordOptions); -+ if (returnUri) { -+ state += `_returnUri='${returnUri}'`; -+ } -+ } - -- // Save state (regardless of new or existing) -- await this.storageService.save(ConstantsService.ssoStateKey, state); -+ // Add Organization Identifier to state -+ state += `_identifier=${this.identifier}`; - -- let authorizeUrl = this.environmentService.getIdentityUrl() + '/connect/authorize?' + -- 'client_id=' + this.clientId + '&redirect_uri=' + encodeURIComponent(this.redirectUri) + '&' + -- 'response_type=code&scope=api offline_access&' + -- 'state=' + state + '&code_challenge=' + codeChallenge + '&' + -- 'code_challenge_method=S256&response_mode=query&' + -- 'domain_hint=' + encodeURIComponent(this.identifier); -+ // Save state (regardless of new or existing) -+ await this.stateService.setSsoState(state); - -- if (includeUserIdentifier) { -- const userIdentifier = await this.apiService.getSsoUserIdentifier(); -- authorizeUrl += `&user_identifier=${encodeURIComponent(userIdentifier)}`; -- } -+ let authorizeUrl = -+ this.environmentService.getIdentityUrl() + -+ "/connect/authorize?" + -+ "client_id=" + -+ this.clientId + -+ "&redirect_uri=" + -+ encodeURIComponent(this.redirectUri) + -+ "&" + -+ "response_type=code&scope=api offline_access&" + -+ "state=" + -+ // TODO needed to be encodeURIComponent'ed for me in previous version -+ state + -+ "&code_challenge=" + -+ codeChallenge + -+ "&" + -+ "code_challenge_method=S256&response_mode=query&" + -+ "domain_hint=" + -+ encodeURIComponent(this.identifier); - -- return authorizeUrl; -+ if (includeUserIdentifier) { -+ const userIdentifier = await this.apiService.getSsoUserIdentifier(); -+ authorizeUrl += `&user_identifier=${encodeURIComponent(userIdentifier)}`; - } - -- private async logIn(code: string, codeVerifier: string, orgIdFromState: string) { -- this.loggingIn = true; -- try { -- this.formPromise = this.authService.logInSso(code, codeVerifier, this.redirectUri, orgIdFromState); -- const response = await this.formPromise; -- if (response.twoFactor) { -- if (this.onSuccessfulLoginTwoFactorNavigate != null) { -- this.onSuccessfulLoginTwoFactorNavigate(); -- } else { -- this.router.navigate([this.twoFactorRoute], { -- queryParams: { -- identifier: orgIdFromState, -- sso: 'true', -- }, -- }); -- } -- } else if (response.resetMasterPassword) { -- if (this.onSuccessfulLoginChangePasswordNavigate != null) { -- this.onSuccessfulLoginChangePasswordNavigate(); -- } else { -- this.router.navigate([this.changePasswordRoute], { -- queryParams: { -- identifier: orgIdFromState, -- }, -- }); -- } -- } else if (response.forcePasswordReset) { -- if (this.onSuccessfulLoginForceResetNavigate != null) { -- this.onSuccessfulLoginForceResetNavigate(); -- } else { -- this.router.navigate([this.forcePasswordResetRoute]); -- } -- } else { -- const disableFavicon = await this.storageService.get(ConstantsService.disableFaviconKey); -- await this.stateService.save(ConstantsService.disableFaviconKey, !!disableFavicon); -- if (this.onSuccessfulLogin != null) { -- this.onSuccessfulLogin(); -- } -- if (this.onSuccessfulLoginNavigate != null) { -- this.onSuccessfulLoginNavigate(); -- } else { -- this.router.navigate([this.successRoute]); -- } -- } -- } catch (e) { -- this.logService.error(e); -- if (e.message === 'Unable to reach key connector') { -- this.platformUtilsService.showToast('error', null, this.i18nService.t('ssoKeyConnectorUnavailable')); -- } -- } -- this.loggingIn = false; -- } -+ return authorizeUrl; -+ } - -- private getOrgIdentifierFromState(state: string): string { -- if (state === null || state === undefined) { -- return null; -+ private async logIn(code: string, codeVerifier: string, orgIdFromState: string) { -+ this.loggingIn = true; -+ try { -+ this.formPromise = this.authService.logInSso( -+ code, -+ codeVerifier, -+ this.redirectUri, -+ orgIdFromState -+ ); -+ const response = await this.formPromise; -+ if (response.twoFactor) { -+ if (this.onSuccessfulLoginTwoFactorNavigate != null) { -+ this.onSuccessfulLoginTwoFactorNavigate(); -+ } else { -+ this.router.navigate([this.twoFactorRoute], { -+ queryParams: { -+ identifier: orgIdFromState, -+ sso: "true", -+ }, -+ }); -+ } -+ } else if (response.resetMasterPassword) { -+ if (this.onSuccessfulLoginChangePasswordNavigate != null) { -+ this.onSuccessfulLoginChangePasswordNavigate(); -+ } else { -+ this.router.navigate([this.changePasswordRoute], { -+ queryParams: { -+ identifier: orgIdFromState, -+ }, -+ }); -+ } -+ } else if (response.forcePasswordReset) { -+ if (this.onSuccessfulLoginForceResetNavigate != null) { -+ this.onSuccessfulLoginForceResetNavigate(); -+ } else { -+ this.router.navigate([this.forcePasswordResetRoute]); -+ } -+ } else { -+ const disableFavicon = await this.stateService.getDisableFavicon(); -+ await this.stateService.setDisableFavicon(!!disableFavicon); -+ if (this.onSuccessfulLogin != null) { -+ this.onSuccessfulLogin(); - } -+ if (this.onSuccessfulLoginNavigate != null) { -+ this.onSuccessfulLoginNavigate(); -+ } else { -+ this.router.navigate([this.successRoute]); -+ } -+ } -+ } catch (e) { -+ this.logService.error(e); -+ if (e.message === "Unable to reach key connector") { -+ this.platformUtilsService.showToast( -+ "error", -+ null, -+ this.i18nService.t("ssoKeyConnectorUnavailable") -+ ); -+ } -+ } -+ this.loggingIn = false; -+ } - -- const stateSplit = state.split('_identifier='); -- return stateSplit.length > 1 ? stateSplit[1] : null; -+ private getOrgIdentifierFromState(state: string): string { -+ if (state === null || state === undefined) { -+ return null; - } - -- private checkState(state: string, checkState: string): boolean { -- if (state === null || state === undefined) { -- return false; -- } -- if (checkState === null || checkState === undefined) { -- return false; -- } -+ const stateSplit = state.split("_identifier="); -+ return stateSplit.length > 1 ? stateSplit[1] : null; -+ } - -- const stateSplit = state.split('_identifier='); -- const checkStateSplit = checkState.split('_identifier='); -- return stateSplit[0] === checkStateSplit[0]; -+ private checkState(state: string, checkState: string): boolean { -+ if (state === null || state === undefined) { -+ return false; -+ } -+ if (checkState === null || checkState === undefined) { -+ return false; - } -+ -+ const stateSplit = state.split("_identifier="); -+ const checkStateSplit = checkState.split("_identifier="); -+ return stateSplit[0] === checkStateSplit[0]; -+ } - } -diff --git a/jslib/angular/src/components/toastr.component.ts b/jslib/angular/src/components/toastr.component.ts -new file mode 100644 -index 00000000..0e4ea4ea ---- /dev/null -+++ b/jslib/angular/src/components/toastr.component.ts -@@ -0,0 +1,95 @@ -+import { animate, state, style, transition, trigger } from "@angular/animations"; -+import { CommonModule } from "@angular/common"; -+import { Component, ModuleWithProviders, NgModule } from "@angular/core"; -+import { -+ DefaultNoComponentGlobalConfig, -+ GlobalConfig, -+ Toast as BaseToast, -+ ToastPackage, -+ ToastrService, -+ TOAST_CONFIG, -+} from "ngx-toastr"; -+ -+@Component({ -+ selector: "[toast-component2]", -+ template: ` -+ -+
-+ -+
-+
-+
-+ {{ title }} [{{ duplicatesCount + 1 }}] -+
-+
-+
-+ {{ message }} -+
-+
-+
-+
-+
-+ `, -+ animations: [ -+ trigger("flyInOut", [ -+ state("inactive", style({ opacity: 0 })), -+ state("active", style({ opacity: 1 })), -+ state("removed", style({ opacity: 0 })), -+ transition("inactive => active", animate("{{ easeTime }}ms {{ easing }}")), -+ transition("active => removed", animate("{{ easeTime }}ms {{ easing }}")), -+ ]), -+ ], -+ preserveWhitespaces: false, -+}) -+export class BitwardenToast extends BaseToast { -+ constructor(protected toastrService: ToastrService, public toastPackage: ToastPackage) { -+ super(toastrService, toastPackage); -+ } -+} -+ -+export const BitwardenToastGlobalConfig: GlobalConfig = { -+ ...DefaultNoComponentGlobalConfig, -+ toastComponent: BitwardenToast, -+}; -+ -+@NgModule({ -+ imports: [CommonModule], -+ declarations: [BitwardenToast], -+ exports: [BitwardenToast], -+}) -+export class BitwardenToastModule { -+ static forRoot(config: Partial = {}): ModuleWithProviders { -+ return { -+ ngModule: BitwardenToastModule, -+ providers: [ -+ { -+ provide: TOAST_CONFIG, -+ useValue: { -+ default: BitwardenToastGlobalConfig, -+ config: config, -+ }, -+ }, -+ ], -+ }; -+ } -+} -diff --git a/jslib/angular/src/components/two-factor-options.component.ts b/jslib/angular/src/components/two-factor-options.component.ts -index 5f817a9e..4909757f 100644 ---- a/jslib/angular/src/components/two-factor-options.component.ts -+++ b/jslib/angular/src/components/two-factor-options.component.ts -@@ -1,38 +1,37 @@ --import { -- Directive, -- EventEmitter, -- OnInit, -- Output, --} from '@angular/core'; --import { Router } from '@angular/router'; -+import { Directive, EventEmitter, OnInit, Output } from "@angular/core"; -+import { Router } from "@angular/router"; - --import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType'; -+import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType"; - --import { AuthService } from 'jslib-common/abstractions/auth.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -+import { AuthService } from "jslib-common/abstractions/auth.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; - - @Directive() - export class TwoFactorOptionsComponent implements OnInit { -- @Output() onProviderSelected = new EventEmitter(); -- @Output() onRecoverSelected = new EventEmitter(); -- -- providers: any[] = []; -- -- constructor(protected authService: AuthService, protected router: Router, -- protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, -- protected win: Window) { } -- -- ngOnInit() { -- this.providers = this.authService.getSupportedTwoFactorProviders(this.win); -- } -- -- choose(p: any) { -- this.onProviderSelected.emit(p.type); -- } -- -- recover() { -- this.platformUtilsService.launchUri('https://help.bitwarden.com/article/lost-two-step-device/'); -- this.onRecoverSelected.emit(); -- } -+ @Output() onProviderSelected = new EventEmitter(); -+ @Output() onRecoverSelected = new EventEmitter(); -+ -+ providers: any[] = []; -+ -+ constructor( -+ protected authService: AuthService, -+ protected router: Router, -+ protected i18nService: I18nService, -+ protected platformUtilsService: PlatformUtilsService, -+ protected win: Window -+ ) {} -+ -+ ngOnInit() { -+ this.providers = this.authService.getSupportedTwoFactorProviders(this.win); -+ } -+ -+ choose(p: any) { -+ this.onProviderSelected.emit(p.type); -+ } -+ -+ recover() { -+ this.platformUtilsService.launchUri("https://help.bitwarden.com/article/lost-two-step-device/"); -+ this.onRecoverSelected.emit(); -+ } - } -diff --git a/jslib/angular/src/components/two-factor.component.ts b/jslib/angular/src/components/two-factor.component.ts -index 5ad45a25..f3813a96 100644 ---- a/jslib/angular/src/components/two-factor.component.ts -+++ b/jslib/angular/src/components/two-factor.component.ts -@@ -1,254 +1,280 @@ --import { Directive, OnDestroy, OnInit } from '@angular/core'; -+import { Directive, OnDestroy, OnInit } from "@angular/core"; - --import { -- ActivatedRoute, -- Router, --} from '@angular/router'; -+import { ActivatedRoute, Router } from "@angular/router"; - --import { first } from 'rxjs/operators'; -+import { first } from "rxjs/operators"; - --import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType'; -+import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType"; - --import { TwoFactorEmailRequest } from 'jslib-common/models/request/twoFactorEmailRequest'; -+import { TwoFactorEmailRequest } from "jslib-common/models/request/twoFactorEmailRequest"; - --import { AuthResult } from 'jslib-common/models/domain/authResult'; -+import { AuthResult } from "jslib-common/models/domain/authResult"; - --import { ApiService } from 'jslib-common/abstractions/api.service'; --import { AuthService } from 'jslib-common/abstractions/auth.service'; --import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { LogService } from 'jslib-common/abstractions/log.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { StateService } from 'jslib-common/abstractions/state.service'; --import { StorageService } from 'jslib-common/abstractions/storage.service'; -+import { ApiService } from "jslib-common/abstractions/api.service"; -+import { AuthService } from "jslib-common/abstractions/auth.service"; -+import { EnvironmentService } from "jslib-common/abstractions/environment.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; - --import { TwoFactorProviders } from 'jslib-common/services/auth.service'; --import { ConstantsService } from 'jslib-common/services/constants.service'; -+import { TwoFactorProviders } from "jslib-common/services/auth.service"; - --import * as DuoWebSDK from 'duo_web_sdk'; --import { WebAuthnIFrame } from 'jslib-common/misc/webauthn_iframe'; -+import * as DuoWebSDK from "duo_web_sdk"; -+import { WebAuthnIFrame } from "jslib-common/misc/webauthn_iframe"; - - @Directive() - export class TwoFactorComponent implements OnInit, OnDestroy { -- token: string = ''; -- remember: boolean = false; -- webAuthnReady: boolean = false; -- webAuthnNewTab: boolean = false; -- providers = TwoFactorProviders; -- providerType = TwoFactorProviderType; -- selectedProviderType: TwoFactorProviderType = TwoFactorProviderType.Authenticator; -- webAuthnSupported: boolean = false; -- webAuthn: WebAuthnIFrame = null; -- title: string = ''; -- twoFactorEmail: string = null; -- formPromise: Promise; -- emailPromise: Promise; -- identifier: string = null; -- onSuccessfulLogin: () => Promise; -- onSuccessfulLoginNavigate: () => Promise; -- -- get webAuthnAllow(): string { -- return `publickey-credentials-get ${this.environmentService.getWebVaultUrl()}`; -- } -- -- protected loginRoute = 'login'; -- protected successRoute = 'vault'; -- -- constructor(protected authService: AuthService, protected router: Router, -- protected i18nService: I18nService, protected apiService: ApiService, -- protected platformUtilsService: PlatformUtilsService, protected win: Window, -- protected environmentService: EnvironmentService, protected stateService: StateService, -- protected storageService: StorageService, protected route: ActivatedRoute, -- protected logService: LogService) { -- this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win); -- } -- -- async ngOnInit() { -- if (!this.authing || this.authService.twoFactorProvidersData == null) { -- this.router.navigate([this.loginRoute]); -- return; -- } -+ token: string = ""; -+ remember: boolean = false; -+ webAuthnReady: boolean = false; -+ webAuthnNewTab: boolean = false; -+ providers = TwoFactorProviders; -+ providerType = TwoFactorProviderType; -+ selectedProviderType: TwoFactorProviderType = TwoFactorProviderType.Authenticator; -+ webAuthnSupported: boolean = false; -+ webAuthn: WebAuthnIFrame = null; -+ title: string = ""; -+ twoFactorEmail: string = null; -+ formPromise: Promise; -+ emailPromise: Promise; -+ identifier: string = null; -+ onSuccessfulLogin: () => Promise; -+ onSuccessfulLoginNavigate: () => Promise; -+ -+ get webAuthnAllow(): string { -+ return `publickey-credentials-get ${this.environmentService.getWebVaultUrl()}`; -+ } -+ -+ protected loginRoute = "login"; -+ protected successRoute = "vault"; -+ -+ constructor( -+ protected authService: AuthService, -+ protected router: Router, -+ protected i18nService: I18nService, -+ protected apiService: ApiService, -+ protected platformUtilsService: PlatformUtilsService, -+ protected win: Window, -+ protected environmentService: EnvironmentService, -+ protected stateService: StateService, -+ protected route: ActivatedRoute, -+ protected logService: LogService -+ ) { -+ this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win); -+ } -+ -+ async ngOnInit() { -+ if (!this.authing || this.authService.twoFactorProvidersData == null) { -+ this.router.navigate([this.loginRoute]); -+ return; -+ } - -- this.route.queryParams.pipe(first()).subscribe(qParams => { -- if (qParams.identifier != null) { -- this.identifier = qParams.identifier; -- } -- }); -+ this.route.queryParams.pipe(first()).subscribe((qParams) => { -+ if (qParams.identifier != null) { -+ this.identifier = qParams.identifier; -+ } -+ }); - -- if (this.needsLock) { -- this.successRoute = 'lock'; -- } -+ if (this.needsLock) { -+ this.successRoute = "lock"; -+ } - -- if (this.win != null && this.webAuthnSupported) { -- const webVaultUrl = this.environmentService.getWebVaultUrl(); -- this.webAuthn = new WebAuthnIFrame(this.win, webVaultUrl, this.webAuthnNewTab, this.platformUtilsService, -- this.i18nService, (token: string) => { -- this.token = token; -- this.submit(); -- }, (error: string) => { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), error); -- }, (info: string) => { -- if (info === 'ready') { -- this.webAuthnReady = true; -- } -- } -- ); -+ if (this.win != null && this.webAuthnSupported) { -+ const webVaultUrl = this.environmentService.getWebVaultUrl(); -+ this.webAuthn = new WebAuthnIFrame( -+ this.win, -+ webVaultUrl, -+ this.webAuthnNewTab, -+ this.platformUtilsService, -+ this.i18nService, -+ (token: string) => { -+ this.token = token; -+ this.submit(); -+ }, -+ (error: string) => { -+ this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), error); -+ }, -+ (info: string) => { -+ if (info === "ready") { -+ this.webAuthnReady = true; -+ } - } -- -- this.selectedProviderType = this.authService.getDefaultTwoFactorProvider(this.webAuthnSupported); -- await this.init(); -+ ); - } - -- ngOnDestroy(): void { -- this.cleanupWebAuthn(); -- this.webAuthn = null; -+ this.selectedProviderType = this.authService.getDefaultTwoFactorProvider( -+ this.webAuthnSupported -+ ); -+ await this.init(); -+ } -+ -+ ngOnDestroy(): void { -+ this.cleanupWebAuthn(); -+ this.webAuthn = null; -+ } -+ -+ async init() { -+ if (this.selectedProviderType == null) { -+ this.title = this.i18nService.t("loginUnavailable"); -+ return; - } - -- async init() { -- if (this.selectedProviderType == null) { -- this.title = this.i18nService.t('loginUnavailable'); -- return; -+ this.cleanupWebAuthn(); -+ this.title = (TwoFactorProviders as any)[this.selectedProviderType].name; -+ const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType); -+ switch (this.selectedProviderType) { -+ case TwoFactorProviderType.WebAuthn: -+ if (!this.webAuthnNewTab) { -+ setTimeout(() => { -+ this.authWebAuthn(); -+ }, 500); - } -- -- this.cleanupWebAuthn(); -- this.title = (TwoFactorProviders as any)[this.selectedProviderType].name; -- const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType); -- switch (this.selectedProviderType) { -- case TwoFactorProviderType.WebAuthn: -- if (!this.webAuthnNewTab) { -- setTimeout(() => { -- this.authWebAuthn(); -- }, 500); -- } -- break; -- case TwoFactorProviderType.Duo: -- case TwoFactorProviderType.OrganizationDuo: -- setTimeout(() => { -- DuoWebSDK.init({ -- iframe: undefined, -- host: providerData.Host, -- sig_request: providerData.Signature, -- submit_callback: async (f: HTMLFormElement) => { -- const sig = f.querySelector('input[name="sig_response"]') as HTMLInputElement; -- if (sig != null) { -- this.token = sig.value; -- await this.submit(); -- } -- }, -- }); -- }, 0); -- break; -- case TwoFactorProviderType.Email: -- this.twoFactorEmail = providerData.Email; -- if (this.authService.twoFactorProvidersData.size > 1) { -- await this.sendEmail(false); -- } -- break; -- default: -- break; -+ break; -+ case TwoFactorProviderType.Duo: -+ case TwoFactorProviderType.OrganizationDuo: -+ setTimeout(() => { -+ DuoWebSDK.init({ -+ iframe: undefined, -+ host: providerData.Host, -+ sig_request: providerData.Signature, -+ submit_callback: async (f: HTMLFormElement) => { -+ const sig = f.querySelector('input[name="sig_response"]') as HTMLInputElement; -+ if (sig != null) { -+ this.token = sig.value; -+ await this.submit(); -+ } -+ }, -+ }); -+ }, 0); -+ break; -+ case TwoFactorProviderType.Email: -+ this.twoFactorEmail = providerData.Email; -+ if (this.authService.twoFactorProvidersData.size > 1) { -+ await this.sendEmail(false); - } -+ break; -+ default: -+ break; - } -- -- async submit() { -- if (this.token == null || this.token === '') { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), -- this.i18nService.t('verificationCodeRequired')); -- return; -- } -- -- if (this.selectedProviderType === TwoFactorProviderType.WebAuthn) { -- if (this.webAuthn != null) { -- this.webAuthn.stop(); -- } else { -- return; -- } -- } else if (this.selectedProviderType === TwoFactorProviderType.Email || -- this.selectedProviderType === TwoFactorProviderType.Authenticator) { -- this.token = this.token.replace(' ', '').trim(); -- } -- -- try { -- await this.doSubmit(); -- } catch { -- if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && this.webAuthn != null) { -- this.webAuthn.start(); -- } -- } -+ } -+ -+ async submit() { -+ if (this.token == null || this.token === "") { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("verificationCodeRequired") -+ ); -+ return; - } - -- async doSubmit() { -- this.formPromise = this.authService.logInTwoFactor(this.selectedProviderType, this.token, this.remember); -- const response: AuthResult = await this.formPromise; -- const disableFavicon = await this.storageService.get(ConstantsService.disableFaviconKey); -- await this.stateService.save(ConstantsService.disableFaviconKey, !!disableFavicon); -- if (this.onSuccessfulLogin != null) { -- this.onSuccessfulLogin(); -- } -- if (response.resetMasterPassword) { -- this.successRoute = 'set-password'; -- } -- if (response.forcePasswordReset) { -- this.successRoute = 'update-temp-password'; -- } -- if (this.onSuccessfulLoginNavigate != null) { -- this.onSuccessfulLoginNavigate(); -- } else { -- this.router.navigate([this.successRoute], { -- queryParams: { -- identifier: this.identifier, -- }, -- }); -- } -+ if (this.selectedProviderType === TwoFactorProviderType.WebAuthn) { -+ if (this.webAuthn != null) { -+ this.webAuthn.stop(); -+ } else { -+ return; -+ } -+ } else if ( -+ this.selectedProviderType === TwoFactorProviderType.Email || -+ this.selectedProviderType === TwoFactorProviderType.Authenticator -+ ) { -+ this.token = this.token.replace(" ", "").trim(); - } - -- async sendEmail(doToast: boolean) { -- if (this.selectedProviderType !== TwoFactorProviderType.Email) { -- return; -- } -- -- if (this.emailPromise != null) { -- return; -- } -+ try { -+ await this.doSubmit(); -+ } catch { -+ if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && this.webAuthn != null) { -+ this.webAuthn.start(); -+ } -+ } -+ } -+ -+ async doSubmit() { -+ this.formPromise = this.authService.logInTwoFactor( -+ this.selectedProviderType, -+ this.token, -+ this.remember -+ ); -+ const response: AuthResult = await this.formPromise; -+ const disableFavicon = await this.stateService.getDisableFavicon(); -+ await this.stateService.setDisableFavicon(!!disableFavicon); -+ if (this.onSuccessfulLogin != null) { -+ this.onSuccessfulLogin(); -+ } -+ if (response.resetMasterPassword) { -+ this.successRoute = "set-password"; -+ } -+ if (response.forcePasswordReset) { -+ this.successRoute = "update-temp-password"; -+ } -+ if (this.onSuccessfulLoginNavigate != null) { -+ this.onSuccessfulLoginNavigate(); -+ } else { -+ this.router.navigate([this.successRoute], { -+ queryParams: { -+ identifier: this.identifier, -+ }, -+ }); -+ } -+ } - -- try { -- const request = new TwoFactorEmailRequest(); -- request.email = this.authService.email; -- request.masterPasswordHash = this.authService.masterPasswordHash; -- this.emailPromise = this.apiService.postTwoFactorEmail(request); -- await this.emailPromise; -- if (doToast) { -- this.platformUtilsService.showToast('success', null, -- this.i18nService.t('verificationCodeEmailSent', this.twoFactorEmail)); -- } -- } catch (e) { -- this.logService.error(e); -- } -+ async sendEmail(doToast: boolean) { -+ if (this.selectedProviderType !== TwoFactorProviderType.Email) { -+ return; -+ } - -- this.emailPromise = null; -+ if (this.emailPromise != null) { -+ return; - } - -- authWebAuthn() { -- const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType); -+ try { -+ const request = new TwoFactorEmailRequest(); -+ request.email = this.authService.email; -+ request.masterPasswordHash = this.authService.masterPasswordHash; -+ this.emailPromise = this.apiService.postTwoFactorEmail(request); -+ await this.emailPromise; -+ if (doToast) { -+ this.platformUtilsService.showToast( -+ "success", -+ null, -+ this.i18nService.t("verificationCodeEmailSent", this.twoFactorEmail) -+ ); -+ } -+ } catch (e) { -+ this.logService.error(e); -+ } - -- if (!this.webAuthnSupported || this.webAuthn == null) { -- return; -- } -+ this.emailPromise = null; -+ } - -- this.webAuthn.init(providerData); -- } -+ authWebAuthn() { -+ const providerData = this.authService.twoFactorProvidersData.get(this.selectedProviderType); - -- private cleanupWebAuthn() { -- if (this.webAuthn != null) { -- this.webAuthn.stop(); -- this.webAuthn.cleanup(); -- } -+ if (!this.webAuthnSupported || this.webAuthn == null) { -+ return; - } - -- get authing(): boolean { -- return this.authService.authingWithPassword() || this.authService.authingWithSso() || this.authService.authingWithApiKey(); -- } -+ this.webAuthn.init(providerData); -+ } - -- get needsLock(): boolean { -- return this.authService.authingWithSso() || this.authService.authingWithApiKey(); -+ private cleanupWebAuthn() { -+ if (this.webAuthn != null) { -+ this.webAuthn.stop(); -+ this.webAuthn.cleanup(); - } -+ } -+ -+ get authing(): boolean { -+ return ( -+ this.authService.authingWithPassword() || -+ this.authService.authingWithSso() || -+ this.authService.authingWithApiKey() -+ ); -+ } -+ -+ get needsLock(): boolean { -+ return this.authService.authingWithSso() || this.authService.authingWithApiKey(); -+ } - } -diff --git a/jslib/angular/src/components/update-password.component.ts b/jslib/angular/src/components/update-password.component.ts -new file mode 100644 -index 00000000..909831c5 ---- /dev/null -+++ b/jslib/angular/src/components/update-password.component.ts -@@ -0,0 +1,130 @@ -+import { Directive } from "@angular/core"; -+import { Router } from "@angular/router"; -+ -+import { ApiService } from "jslib-common/abstractions/api.service"; -+import { CryptoService } from "jslib-common/abstractions/crypto.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { MessagingService } from "jslib-common/abstractions/messaging.service"; -+import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { PolicyService } from "jslib-common/abstractions/policy.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; -+import { UserVerificationService } from "jslib-common/abstractions/userVerification.service"; -+ -+import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component"; -+ -+import { EncString } from "jslib-common/models/domain/encString"; -+import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions"; -+import { PasswordRequest } from "jslib-common/models/request/passwordRequest"; -+ -+import { VerificationType } from "jslib-common/enums/verificationType"; -+import { Verification } from "jslib-common/types/verification"; -+ -+import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey"; -+ -+@Directive() -+export class UpdatePasswordComponent extends BaseChangePasswordComponent { -+ hint: string; -+ key: string; -+ enforcedPolicyOptions: MasterPasswordPolicyOptions; -+ showPassword: boolean = false; -+ currentMasterPassword: string; -+ -+ onSuccessfulChangePassword: () => Promise; -+ -+ constructor( -+ protected router: Router, -+ i18nService: I18nService, -+ platformUtilsService: PlatformUtilsService, -+ passwordGenerationService: PasswordGenerationService, -+ policyService: PolicyService, -+ cryptoService: CryptoService, -+ messagingService: MessagingService, -+ private apiService: ApiService, -+ stateService: StateService, -+ private userVerificationService: UserVerificationService, -+ private logService: LogService -+ ) { -+ super( -+ i18nService, -+ cryptoService, -+ messagingService, -+ passwordGenerationService, -+ platformUtilsService, -+ policyService, -+ stateService -+ ); -+ } -+ -+ togglePassword(confirmField: boolean) { -+ this.showPassword = !this.showPassword; -+ document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus(); -+ } -+ -+ async cancel() { -+ await this.stateService.setOrganizationInvitation(null); -+ await this.stateService.setLoginRedirect(null); -+ this.router.navigate(["/vault"]); -+ } -+ -+ async setupSubmitActions(): Promise { -+ if (this.currentMasterPassword == null || this.currentMasterPassword === "") { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("errorOccurred"), -+ this.i18nService.t("masterPassRequired") -+ ); -+ return false; -+ } -+ -+ const secret: Verification = { -+ type: VerificationType.MasterPassword, -+ secret: this.currentMasterPassword, -+ }; -+ try { -+ await this.userVerificationService.verifyUser(secret); -+ } catch (e) { -+ this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message); -+ return false; -+ } -+ -+ this.kdf = await this.stateService.getKdfType(); -+ this.kdfIterations = await this.stateService.getKdfIterations(); -+ return true; -+ } -+ -+ async performSubmitActions( -+ masterPasswordHash: string, -+ key: SymmetricCryptoKey, -+ encKey: [SymmetricCryptoKey, EncString] -+ ) { -+ try { -+ // Create Request -+ const request = new PasswordRequest(); -+ request.masterPasswordHash = await this.cryptoService.hashPassword( -+ this.currentMasterPassword, -+ null -+ ); -+ request.newMasterPasswordHash = masterPasswordHash; -+ request.key = encKey[1].encryptedString; -+ -+ // Update user's password -+ this.apiService.postPassword(request); -+ -+ this.platformUtilsService.showToast( -+ "success", -+ this.i18nService.t("masterPasswordChanged"), -+ this.i18nService.t("logBackIn") -+ ); -+ -+ if (this.onSuccessfulChangePassword != null) { -+ this.onSuccessfulChangePassword(); -+ } else { -+ this.messagingService.send("logout"); -+ } -+ } catch (e) { -+ this.logService.error(e); -+ } -+ } -+} -diff --git a/jslib/angular/src/components/update-temp-password.component.ts b/jslib/angular/src/components/update-temp-password.component.ts -index 29e54521..bff997e7 100644 ---- a/jslib/angular/src/components/update-temp-password.component.ts -+++ b/jslib/angular/src/components/update-temp-password.component.ts -@@ -1,109 +1,134 @@ --import { Directive } from '@angular/core'; -+import { Directive } from "@angular/core"; - --import { ApiService } from 'jslib-common/abstractions/api.service'; --import { CryptoService } from 'jslib-common/abstractions/crypto.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { LogService } from 'jslib-common/abstractions/log.service'; --import { MessagingService } from 'jslib-common/abstractions/messaging.service'; --import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { PolicyService } from 'jslib-common/abstractions/policy.service'; --import { SyncService } from 'jslib-common/abstractions/sync.service'; --import { UserService } from 'jslib-common/abstractions/user.service'; -+import { ApiService } from "jslib-common/abstractions/api.service"; -+import { CryptoService } from "jslib-common/abstractions/crypto.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { MessagingService } from "jslib-common/abstractions/messaging.service"; -+import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { PolicyService } from "jslib-common/abstractions/policy.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; -+import { SyncService } from "jslib-common/abstractions/sync.service"; - --import { ChangePasswordComponent as BaseChangePasswordComponent } from './change-password.component'; -+import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component"; - --import { EncString } from 'jslib-common/models/domain/encString'; --import { MasterPasswordPolicyOptions } from 'jslib-common/models/domain/masterPasswordPolicyOptions'; --import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey'; -+import { EncString } from "jslib-common/models/domain/encString"; -+import { MasterPasswordPolicyOptions } from "jslib-common/models/domain/masterPasswordPolicyOptions"; -+import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey"; - --import { UpdateTempPasswordRequest } from 'jslib-common/models/request/updateTempPasswordRequest'; -+import { UpdateTempPasswordRequest } from "jslib-common/models/request/updateTempPasswordRequest"; - - @Directive() - export class UpdateTempPasswordComponent extends BaseChangePasswordComponent { -- hint: string; -- key: string; -- enforcedPolicyOptions: MasterPasswordPolicyOptions; -- showPassword: boolean = false; -- -- onSuccessfulChangePassword: () => Promise; -- -- constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService, -- passwordGenerationService: PasswordGenerationService, policyService: PolicyService, -- cryptoService: CryptoService, userService: UserService, -- messagingService: MessagingService, private apiService: ApiService, -- private syncService: SyncService, private logService: LogService) { -- super(i18nService, cryptoService, messagingService, userService, passwordGenerationService, -- platformUtilsService, policyService); -+ hint: string; -+ key: string; -+ enforcedPolicyOptions: MasterPasswordPolicyOptions; -+ showPassword: boolean = false; -+ -+ onSuccessfulChangePassword: () => Promise; -+ -+ constructor( -+ i18nService: I18nService, -+ platformUtilsService: PlatformUtilsService, -+ passwordGenerationService: PasswordGenerationService, -+ policyService: PolicyService, -+ cryptoService: CryptoService, -+ messagingService: MessagingService, -+ private apiService: ApiService, -+ stateService: StateService, -+ private syncService: SyncService, -+ private logService: LogService -+ ) { -+ super( -+ i18nService, -+ cryptoService, -+ messagingService, -+ passwordGenerationService, -+ platformUtilsService, -+ policyService, -+ stateService -+ ); -+ } -+ -+ async ngOnInit() { -+ await this.syncService.fullSync(true); -+ super.ngOnInit(); -+ } -+ -+ togglePassword(confirmField: boolean) { -+ this.showPassword = !this.showPassword; -+ document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus(); -+ } -+ -+ async setupSubmitActions(): Promise { -+ this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(); -+ this.email = await this.stateService.getEmail(); -+ this.kdf = await this.stateService.getKdfType(); -+ this.kdfIterations = await this.stateService.getKdfIterations(); -+ return true; -+ } -+ -+ async submit() { -+ // Validation -+ if (!(await this.strongPassword())) { -+ return; - } - -- async ngOnInit() { -- await this.syncService.fullSync(true); -- super.ngOnInit(); -+ if (!(await this.setupSubmitActions())) { -+ return; - } - -- togglePassword(confirmField: boolean) { -- this.showPassword = !this.showPassword; -- document.getElementById(confirmField ? 'masterPasswordRetype' : 'masterPassword').focus(); -+ try { -+ // Create new key and hash new password -+ const newKey = await this.cryptoService.makeKey( -+ this.masterPassword, -+ this.email.trim().toLowerCase(), -+ this.kdf, -+ this.kdfIterations -+ ); -+ const newPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, newKey); -+ -+ // Grab user's current enc key -+ const userEncKey = await this.cryptoService.getEncKey(); -+ -+ // Create new encKey for the User -+ const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey); -+ -+ await this.performSubmitActions(newPasswordHash, newKey, newEncKey); -+ } catch (e) { -+ this.logService.error(e); - } -- -- async setupSubmitActions(): Promise { -- this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(); -- this.email = await this.userService.getEmail(); -- this.kdf = await this.userService.getKdf(); -- this.kdfIterations = await this.userService.getKdfIterations(); -- return true; -- } -- -- async submit() { -- // Validation -- if (!await this.strongPassword()) { -- return; -- } -- -- if (!await this.setupSubmitActions()) { -- return; -- } -- -- try { -- // Create new key and hash new password -- const newKey = await this.cryptoService.makeKey(this.masterPassword, this.email.trim().toLowerCase(), -- this.kdf, this.kdfIterations); -- const newPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, newKey); -- -- // Grab user's current enc key -- const userEncKey = await this.cryptoService.getEncKey(); -- -- // Create new encKey for the User -- const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey); -- -- await this.performSubmitActions(newPasswordHash, newKey, newEncKey); -- } catch (e) { -- this.logService.error(e); -- } -- } -- -- async performSubmitActions(masterPasswordHash: string, key: SymmetricCryptoKey, -- encKey: [SymmetricCryptoKey, EncString]) { -- try { -- // Create request -- const request = new UpdateTempPasswordRequest(); -- request.key = encKey[1].encryptedString; -- request.newMasterPasswordHash = masterPasswordHash; -- request.masterPasswordHint = this.hint; -- -- // Update user's password -- this.formPromise = this.apiService.putUpdateTempPassword(request); -- await this.formPromise; -- this.platformUtilsService.showToast('success', null, this.i18nService.t('updatedMasterPassword')); -- -- if (this.onSuccessfulChangePassword != null) { -- this.onSuccessfulChangePassword(); -- } else { -- this.messagingService.send('logout'); -- } -- } catch (e) { -- this.logService.error(e); -- } -+ } -+ -+ async performSubmitActions( -+ masterPasswordHash: string, -+ key: SymmetricCryptoKey, -+ encKey: [SymmetricCryptoKey, EncString] -+ ) { -+ try { -+ // Create request -+ const request = new UpdateTempPasswordRequest(); -+ request.key = encKey[1].encryptedString; -+ request.newMasterPasswordHash = masterPasswordHash; -+ request.masterPasswordHint = this.hint; -+ -+ // Update user's password -+ this.formPromise = this.apiService.putUpdateTempPassword(request); -+ await this.formPromise; -+ this.platformUtilsService.showToast( -+ "success", -+ null, -+ this.i18nService.t("updatedMasterPassword") -+ ); -+ -+ if (this.onSuccessfulChangePassword != null) { -+ this.onSuccessfulChangePassword(); -+ } else { -+ this.messagingService.send("logout"); -+ } -+ } catch (e) { -+ this.logService.error(e); - } -+ } - } -diff --git a/jslib/angular/src/components/verify-master-password.component.html b/jslib/angular/src/components/verify-master-password.component.html -index c4160e97..b90c6074 100644 ---- a/jslib/angular/src/components/verify-master-password.component.html -+++ b/jslib/angular/src/components/verify-master-password.component.html -@@ -1,25 +1,46 @@ - -- -- -- {{'confirmIdentity' | i18n}} -+ -+ -+ {{ "confirmIdentity" | i18n }} - - --
-- -- -- -- -- {{'codeSent' | i18n}} -- --
-+
-+ -+ -+ -+ -+ {{ "codeSent" | i18n }} -+ -+
- --
-- -- -- {{'confirmIdentity' | i18n}} --
-+
-+ -+ -+ {{ "confirmIdentity" | i18n }} -+
-
-diff --git a/jslib/angular/src/components/verify-master-password.component.ts b/jslib/angular/src/components/verify-master-password.component.ts -index 1975b4e2..43b64eb2 100644 ---- a/jslib/angular/src/components/verify-master-password.component.ts -+++ b/jslib/angular/src/components/verify-master-password.component.ts -@@ -1,105 +1,92 @@ --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'; -+import { animate, style, transition, trigger } from "@angular/animations"; -+import { Component, OnInit } from "@angular/core"; -+import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from "@angular/forms"; - --@Component({ -- selector: 'app-verify-master-password', -- templateUrl: 'verify-master-password.component.html', -- providers: [ -- { -- provide: NG_VALUE_ACCESSOR, -- multi: true, -- useExisting: VerifyMasterPasswordComponent, -- }, -- ], -- animations: [ -- trigger('sent', [ -- transition(':enter', [ -- style({ opacity: 0 }), -- animate('100ms', style({ opacity: 1 })), -- ]), -- ]), -- ], --}) --export class VerifyMasterPasswordComponent implements ControlValueAccessor, OnInit { -- usesKeyConnector: boolean = false; -- disableRequestOTP: boolean = false; -- sentCode: boolean = false; -- -- secret = new FormControl(''); -- -- private onChange: (value: Verification) => void; -+import { KeyConnectorService } from "jslib-common/abstractions/keyConnector.service"; -+import { UserVerificationService } from "jslib-common/abstractions/userVerification.service"; - -- constructor(private keyConnectorService: KeyConnectorService, -- private userVerificationService: UserVerificationService) { } -+import { VerificationType } from "jslib-common/enums/verificationType"; - -- async ngOnInit() { -- this.usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector(); -- this.processChanges(this.secret.value); -- -- this.secret.valueChanges.subscribe(secret => this.processChanges(secret)); -- } -- -- async requestOTP() { -- if (this.usesKeyConnector) { -- this.disableRequestOTP = true; -- try { -- await this.userVerificationService.requestOTP(); -- this.sentCode = true; -- } finally { -- this.disableRequestOTP = false; -- } -- } -- } -+import { Verification } from "jslib-common/types/verification"; - -- writeValue(obj: any): void { -- this.secret.setValue(obj); -- } -- -- registerOnChange(fn: any): void { -- this.onChange = fn; -+@Component({ -+ selector: "app-verify-master-password", -+ templateUrl: "verify-master-password.component.html", -+ providers: [ -+ { -+ provide: NG_VALUE_ACCESSOR, -+ multi: true, -+ useExisting: VerifyMasterPasswordComponent, -+ }, -+ ], -+ animations: [ -+ trigger("sent", [ -+ transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]), -+ ]), -+ ], -+}) -+export class VerifyMasterPasswordComponent implements ControlValueAccessor, OnInit { -+ usesKeyConnector: boolean = false; -+ disableRequestOTP: boolean = false; -+ sentCode: boolean = 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) => this.processChanges(secret)); -+ } -+ -+ async requestOTP() { -+ if (this.usesKeyConnector) { -+ this.disableRequestOTP = true; -+ try { -+ await this.userVerificationService.requestOTP(); -+ this.sentCode = true; -+ } finally { -+ this.disableRequestOTP = false; -+ } - } -- -- registerOnTouched(fn: any): void { -- // Not implemented -+ } -+ -+ 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(); - } -+ } - -- 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; - } - -- private processChanges(secret: string) { -- if (this.onChange == null) { -- return; -- } -- -- this.onChange({ -- type: this.usesKeyConnector ? VerificationType.OTP : VerificationType.MasterPassword, -- secret: secret, -- }); -- } -+ this.onChange({ -+ type: this.usesKeyConnector ? VerificationType.OTP : VerificationType.MasterPassword, -+ secret: secret, -+ }); -+ } - } -diff --git a/jslib/angular/src/components/view-custom-fields.component.ts b/jslib/angular/src/components/view-custom-fields.component.ts -index add22691..c955eeb8 100644 ---- a/jslib/angular/src/components/view-custom-fields.component.ts -+++ b/jslib/angular/src/components/view-custom-fields.component.ts -@@ -1,35 +1,32 @@ --import { -- Directive, -- Input, --} from '@angular/core'; -+import { Directive, Input } from "@angular/core"; - --import { EventType } from 'jslib-common/enums/eventType'; --import { FieldType } from 'jslib-common/enums/fieldType'; -+import { EventType } from "jslib-common/enums/eventType"; -+import { FieldType } from "jslib-common/enums/fieldType"; - --import { EventService } from 'jslib-common/abstractions/event.service'; -+import { EventService } from "jslib-common/abstractions/event.service"; - --import { CipherView } from 'jslib-common/models/view/cipherView'; --import { FieldView } from 'jslib-common/models/view/fieldView'; -+import { CipherView } from "jslib-common/models/view/cipherView"; -+import { FieldView } from "jslib-common/models/view/fieldView"; - - @Directive() - export class ViewCustomFieldsComponent { -- @Input() cipher: CipherView; -- @Input() promptPassword: () => Promise; -- @Input() copy: (value: string, typeI18nKey: string, aType: string) => void; -+ @Input() cipher: CipherView; -+ @Input() promptPassword: () => Promise; -+ @Input() copy: (value: string, typeI18nKey: string, aType: string) => void; - -- fieldType = FieldType; -+ fieldType = FieldType; - -- constructor(private eventService: EventService) { } -+ constructor(private eventService: EventService) {} - -- async toggleFieldValue(field: FieldView) { -- if (!await this.promptPassword()) { -- return; -- } -+ async toggleFieldValue(field: FieldView) { -+ if (!(await this.promptPassword())) { -+ return; -+ } - -- const f = (field as any); -- f.showValue = !f.showValue; -- if (f.showValue) { -- this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipher.id); -- } -+ const f = field as any; -+ f.showValue = !f.showValue; -+ if (f.showValue) { -+ this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipher.id); - } -+ } - } -diff --git a/jslib/angular/src/components/view.component.ts b/jslib/angular/src/components/view.component.ts -index 8e12634f..9e2ea818 100644 ---- a/jslib/angular/src/components/view.component.ts -+++ b/jslib/angular/src/components/view.component.ts -@@ -1,394 +1,447 @@ - import { -- ChangeDetectorRef, -- Directive, -- EventEmitter, -- Input, -- NgZone, -- OnDestroy, -- OnInit, -- Output, --} from '@angular/core'; -- --import { CipherRepromptType } from 'jslib-common/enums/cipherRepromptType'; --import { CipherType } from 'jslib-common/enums/cipherType'; --import { EventType } from 'jslib-common/enums/eventType'; --import { FieldType } from 'jslib-common/enums/fieldType'; -- --import { ApiService } from 'jslib-common/abstractions/api.service'; --import { AuditService } from 'jslib-common/abstractions/audit.service'; --import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; --import { CipherService } from 'jslib-common/abstractions/cipher.service'; --import { CryptoService } from 'jslib-common/abstractions/crypto.service'; --import { EventService } from 'jslib-common/abstractions/event.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { LogService } from 'jslib-common/abstractions/log.service'; --import { PasswordRepromptService } from 'jslib-common/abstractions/passwordReprompt.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { TokenService } from 'jslib-common/abstractions/token.service'; --import { TotpService } from 'jslib-common/abstractions/totp.service'; --import { UserService } from 'jslib-common/abstractions/user.service'; -- --import { ErrorResponse } from 'jslib-common/models/response/errorResponse'; -- --import { AttachmentView } from 'jslib-common/models/view/attachmentView'; --import { CipherView } from 'jslib-common/models/view/cipherView'; --import { LoginUriView } from 'jslib-common/models/view/loginUriView'; -- --const BroadcasterSubscriptionId = 'ViewComponent'; -+ ChangeDetectorRef, -+ Directive, -+ EventEmitter, -+ Input, -+ NgZone, -+ OnDestroy, -+ OnInit, -+ Output, -+} from "@angular/core"; -+ -+import { CipherRepromptType } from "jslib-common/enums/cipherRepromptType"; -+import { CipherType } from "jslib-common/enums/cipherType"; -+import { EventType } from "jslib-common/enums/eventType"; -+import { FieldType } from "jslib-common/enums/fieldType"; -+ -+import { ApiService } from "jslib-common/abstractions/api.service"; -+import { AuditService } from "jslib-common/abstractions/audit.service"; -+import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service"; -+import { CipherService } from "jslib-common/abstractions/cipher.service"; -+import { CryptoService } from "jslib-common/abstractions/crypto.service"; -+import { EventService } from "jslib-common/abstractions/event.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { PasswordRepromptService } from "jslib-common/abstractions/passwordReprompt.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; -+import { TokenService } from "jslib-common/abstractions/token.service"; -+import { TotpService } from "jslib-common/abstractions/totp.service"; -+ -+import { ErrorResponse } from "jslib-common/models/response/errorResponse"; -+ -+import { AttachmentView } from "jslib-common/models/view/attachmentView"; -+import { CipherView } from "jslib-common/models/view/cipherView"; -+import { LoginUriView } from "jslib-common/models/view/loginUriView"; -+ -+const BroadcasterSubscriptionId = "ViewComponent"; - - @Directive() - export class ViewComponent implements OnDestroy, OnInit { -- @Input() cipherId: string; -- @Output() onEditCipher = new EventEmitter(); -- @Output() onCloneCipher = new EventEmitter(); -- @Output() onShareCipher = new EventEmitter(); -- @Output() onDeletedCipher = new EventEmitter(); -- @Output() onRestoredCipher = new EventEmitter(); -- -- cipher: CipherView; -- showPassword: boolean; -- showCardNumber: boolean; -- showCardCode: boolean; -- canAccessPremium: boolean; -- totpCode: string; -- totpCodeFormatted: string; -- totpDash: number; -- totpSec: number; -- totpLow: boolean; -- fieldType = FieldType; -- checkPasswordPromise: Promise; -- -- private totpInterval: any; -- private previousCipherId: string; -- private passwordReprompted: boolean = false; -- -- constructor(protected cipherService: CipherService, protected totpService: TotpService, -- protected tokenService: TokenService, protected i18nService: I18nService, -- protected cryptoService: CryptoService, protected platformUtilsService: PlatformUtilsService, -- protected auditService: AuditService, protected win: Window, -- protected broadcasterService: BroadcasterService, protected ngZone: NgZone, -- protected changeDetectorRef: ChangeDetectorRef, protected userService: UserService, -- protected eventService: EventService, protected apiService: ApiService, -- protected passwordRepromptService: PasswordRepromptService, private logService: LogService) { } -- -- ngOnInit() { -- this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { -- this.ngZone.run(async () => { -- switch (message.command) { -- case 'syncCompleted': -- if (message.successfully) { -- await this.load(); -- this.changeDetectorRef.detectChanges(); -- } -- break; -- } -- }); -- }); -- } -- -- ngOnDestroy() { -- this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); -- this.cleanUp(); -- } -- -- async load() { -- this.cleanUp(); -- -- const cipher = await this.cipherService.get(this.cipherId); -- this.cipher = await cipher.decrypt(); -- this.canAccessPremium = await this.userService.canAccessPremium(); -- -- if (this.cipher.type === CipherType.Login && this.cipher.login.totp && -- (cipher.organizationUseTotp || this.canAccessPremium)) { -- await this.totpUpdateCode(); -- const interval = this.totpService.getTimeInterval(this.cipher.login.totp); -- await this.totpTick(interval); -- -- this.totpInterval = setInterval(async () => { -- await this.totpTick(interval); -- }, 1000); -- } -- -- if (this.previousCipherId !== this.cipherId) { -- this.eventService.collect(EventType.Cipher_ClientViewed, this.cipherId); -- } -- this.previousCipherId = this.cipherId; -+ @Input() cipherId: string; -+ @Output() onEditCipher = new EventEmitter(); -+ @Output() onCloneCipher = new EventEmitter(); -+ @Output() onShareCipher = new EventEmitter(); -+ @Output() onDeletedCipher = new EventEmitter(); -+ @Output() onRestoredCipher = new EventEmitter(); -+ -+ cipher: CipherView; -+ showPassword: boolean; -+ showCardNumber: boolean; -+ showCardCode: boolean; -+ canAccessPremium: boolean; -+ totpCode: string; -+ totpCodeFormatted: string; -+ totpDash: number; -+ totpSec: number; -+ totpLow: boolean; -+ fieldType = FieldType; -+ checkPasswordPromise: Promise; -+ -+ private totpInterval: any; -+ private previousCipherId: string; -+ private passwordReprompted: boolean = false; -+ -+ constructor( -+ protected cipherService: CipherService, -+ protected totpService: TotpService, -+ protected tokenService: TokenService, -+ protected i18nService: I18nService, -+ protected cryptoService: CryptoService, -+ protected platformUtilsService: PlatformUtilsService, -+ protected auditService: AuditService, -+ protected win: Window, -+ protected broadcasterService: BroadcasterService, -+ protected ngZone: NgZone, -+ protected changeDetectorRef: ChangeDetectorRef, -+ protected eventService: EventService, -+ protected apiService: ApiService, -+ protected passwordRepromptService: PasswordRepromptService, -+ private logService: LogService, -+ protected stateService: StateService -+ ) {} -+ -+ ngOnInit() { -+ this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { -+ this.ngZone.run(async () => { -+ switch (message.command) { -+ case "syncCompleted": -+ if (message.successfully) { -+ await this.load(); -+ this.changeDetectorRef.detectChanges(); -+ } -+ break; -+ } -+ }); -+ }); -+ } -+ -+ ngOnDestroy() { -+ this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); -+ this.cleanUp(); -+ } -+ -+ async load() { -+ this.cleanUp(); -+ -+ const cipher = await this.cipherService.get(this.cipherId); -+ this.cipher = await cipher.decrypt(); -+ this.canAccessPremium = await this.stateService.getCanAccessPremium(); -+ -+ if ( -+ this.cipher.type === CipherType.Login && -+ this.cipher.login.totp && -+ (cipher.organizationUseTotp || this.canAccessPremium) -+ ) { -+ await this.totpUpdateCode(); -+ const interval = this.totpService.getTimeInterval(this.cipher.login.totp); -+ await this.totpTick(interval); -+ -+ this.totpInterval = setInterval(async () => { -+ await this.totpTick(interval); -+ }, 1000); - } - -- async edit() { -- if (await this.promptPassword()) { -- this.onEditCipher.emit(this.cipher); -- return true; -- } -- -- return false; -+ if (this.previousCipherId !== this.cipherId) { -+ this.eventService.collect(EventType.Cipher_ClientViewed, this.cipherId); - } -+ this.previousCipherId = this.cipherId; -+ } - -- async clone() { -- if (await this.promptPassword()) { -- this.onCloneCipher.emit(this.cipher); -- return true; -- } -- -- return false; -+ async edit() { -+ if (await this.promptPassword()) { -+ this.onEditCipher.emit(this.cipher); -+ return true; - } - -- async share() { -- if (await this.promptPassword()) { -- this.onShareCipher.emit(this.cipher); -- return true; -- } -+ return false; -+ } - -- return false; -+ async clone() { -+ if (await this.promptPassword()) { -+ this.onCloneCipher.emit(this.cipher); -+ return true; - } - -- async delete(): Promise { -- if (!await this.promptPassword()) { -- return; -- } -+ return false; -+ } - -- const confirmed = await this.platformUtilsService.showDialog( -- this.i18nService.t(this.cipher.isDeleted ? 'permanentlyDeleteItemConfirmation' : 'deleteItemConfirmation'), -- this.i18nService.t('deleteItem'), this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); -- if (!confirmed) { -- return false; -- } -+ async share() { -+ if (await this.promptPassword()) { -+ this.onShareCipher.emit(this.cipher); -+ return true; -+ } - -- try { -- await this.deleteCipher(); -- this.platformUtilsService.showToast('success', null, -- this.i18nService.t(this.cipher.isDeleted ? 'permanentlyDeletedItem' : 'deletedItem')); -- this.onDeletedCipher.emit(this.cipher); -- } catch (e) { -- this.logService.error(e); -- } -+ return false; -+ } - -- return true; -+ async delete(): Promise { -+ if (!(await this.promptPassword())) { -+ return; - } - -- async restore(): Promise { -- if (!this.cipher.isDeleted) { -- return false; -- } -+ const confirmed = await this.platformUtilsService.showDialog( -+ this.i18nService.t( -+ this.cipher.isDeleted ? "permanentlyDeleteItemConfirmation" : "deleteItemConfirmation" -+ ), -+ this.i18nService.t("deleteItem"), -+ this.i18nService.t("yes"), -+ this.i18nService.t("no"), -+ "warning" -+ ); -+ if (!confirmed) { -+ return false; -+ } - -- const confirmed = await this.platformUtilsService.showDialog( -- this.i18nService.t('restoreItemConfirmation'), this.i18nService.t('restoreItem'), -- this.i18nService.t('yes'), this.i18nService.t('no'), 'warning'); -- if (!confirmed) { -- return false; -- } -+ try { -+ await this.deleteCipher(); -+ this.platformUtilsService.showToast( -+ "success", -+ null, -+ this.i18nService.t(this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem") -+ ); -+ this.onDeletedCipher.emit(this.cipher); -+ } catch (e) { -+ this.logService.error(e); -+ } - -- try { -- await this.restoreCipher(); -- this.platformUtilsService.showToast('success', null, this.i18nService.t('restoredItem')); -- this.onRestoredCipher.emit(this.cipher); -- } catch (e) { -- this.logService.error(e); -- } -+ return true; -+ } - -- return true; -+ async restore(): Promise { -+ if (!this.cipher.isDeleted) { -+ return false; - } - -- async togglePassword() { -- if (!await this.promptPassword()) { -- return; -- } -+ const confirmed = await this.platformUtilsService.showDialog( -+ this.i18nService.t("restoreItemConfirmation"), -+ this.i18nService.t("restoreItem"), -+ this.i18nService.t("yes"), -+ this.i18nService.t("no"), -+ "warning" -+ ); -+ if (!confirmed) { -+ return false; -+ } - -- this.showPassword = !this.showPassword; -- if (this.showPassword) { -- this.eventService.collect(EventType.Cipher_ClientToggledPasswordVisible, this.cipherId); -- } -+ try { -+ await this.restoreCipher(); -+ this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItem")); -+ this.onRestoredCipher.emit(this.cipher); -+ } catch (e) { -+ this.logService.error(e); - } - -- async toggleCardNumber() { -- if (!await this.promptPassword()) { -- return; -- } -+ return true; -+ } - -- this.showCardNumber = !this.showCardNumber; -- if (this.showCardNumber) { -- this.eventService.collect(EventType.Cipher_ClientToggledCardCodeVisible, this.cipherId); -- } -+ async togglePassword() { -+ if (!(await this.promptPassword())) { -+ return; - } - -- async toggleCardCode() { -- if (!await this.promptPassword()) { -- return; -- } -- -- this.showCardCode = !this.showCardCode; -- if (this.showCardCode) { -- this.eventService.collect(EventType.Cipher_ClientToggledCardCodeVisible, this.cipherId); -- } -+ this.showPassword = !this.showPassword; -+ if (this.showPassword) { -+ this.eventService.collect(EventType.Cipher_ClientToggledPasswordVisible, this.cipherId); - } -+ } - -- async checkPassword() { -- if (this.cipher.login == null || this.cipher.login.password == null || this.cipher.login.password === '') { -- return; -- } -+ async toggleCardNumber() { -+ if (!(await this.promptPassword())) { -+ return; -+ } - -- this.checkPasswordPromise = this.auditService.passwordLeaked(this.cipher.login.password); -- const matches = await this.checkPasswordPromise; -+ this.showCardNumber = !this.showCardNumber; -+ if (this.showCardNumber) { -+ this.eventService.collect(EventType.Cipher_ClientToggledCardCodeVisible, this.cipherId); -+ } -+ } - -- if (matches > 0) { -- this.platformUtilsService.showToast('warning', null, -- this.i18nService.t('passwordExposed', matches.toString())); -- } else { -- this.platformUtilsService.showToast('success', null, this.i18nService.t('passwordSafe')); -- } -+ async toggleCardCode() { -+ if (!(await this.promptPassword())) { -+ return; - } - -- launch(uri: LoginUriView, cipherId?: string) { -- if (!uri.canLaunch) { -- return; -- } -+ this.showCardCode = !this.showCardCode; -+ if (this.showCardCode) { -+ this.eventService.collect(EventType.Cipher_ClientToggledCardCodeVisible, this.cipherId); -+ } -+ } -+ -+ async checkPassword() { -+ if ( -+ this.cipher.login == null || -+ this.cipher.login.password == null || -+ this.cipher.login.password === "" -+ ) { -+ return; -+ } - -- if (cipherId) { -- this.cipherService.updateLastLaunchedDate(cipherId); -- } -+ this.checkPasswordPromise = this.auditService.passwordLeaked(this.cipher.login.password); -+ const matches = await this.checkPasswordPromise; -+ -+ if (matches > 0) { -+ this.platformUtilsService.showToast( -+ "warning", -+ null, -+ this.i18nService.t("passwordExposed", matches.toString()) -+ ); -+ } else { -+ this.platformUtilsService.showToast("success", null, this.i18nService.t("passwordSafe")); -+ } -+ } - -- this.platformUtilsService.launchUri(uri.launchUri); -+ launch(uri: LoginUriView, cipherId?: string) { -+ if (!uri.canLaunch) { -+ return; - } - -- async copy(value: string, typeI18nKey: string, aType: string) { -- if (value == null) { -- return; -- } -+ if (cipherId) { -+ this.cipherService.updateLastLaunchedDate(cipherId); -+ } - -- if (this.passwordRepromptService.protectedFields().includes(aType) && !await this.promptPassword()) { -- return; -- } -+ this.platformUtilsService.launchUri(uri.launchUri); -+ } - -- const copyOptions = this.win != null ? { window: this.win } : null; -- this.platformUtilsService.copyToClipboard(value, copyOptions); -- this.platformUtilsService.showToast('info', null, -- this.i18nService.t('valueCopied', this.i18nService.t(typeI18nKey))); -- -- if (typeI18nKey === 'password') { -- this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipherId); -- } else if (typeI18nKey === 'securityCode') { -- this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, this.cipherId); -- } else if (aType === 'H_Field') { -- this.eventService.collect(EventType.Cipher_ClientCopiedHiddenField, this.cipherId); -- } -+ async copy(value: string, typeI18nKey: string, aType: string) { -+ if (value == null) { -+ return; - } - -- setTextDataOnDrag(event: DragEvent, data: string) { -- event.dataTransfer.setData('text', data); -+ if ( -+ this.passwordRepromptService.protectedFields().includes(aType) && -+ !(await this.promptPassword()) -+ ) { -+ return; - } - -- async downloadAttachment(attachment: AttachmentView) { -- if (!await this.promptPassword()) { -- return; -- } -- const a = (attachment as any); -- if (a.downloading) { -- return; -- } -- -- if (this.cipher.organizationId == null && !this.canAccessPremium) { -- this.platformUtilsService.showToast('error', this.i18nService.t('premiumRequired'), -- this.i18nService.t('premiumRequiredDesc')); -- return; -- } -+ const copyOptions = this.win != null ? { window: this.win } : null; -+ this.platformUtilsService.copyToClipboard(value, copyOptions); -+ this.platformUtilsService.showToast( -+ "info", -+ null, -+ this.i18nService.t("valueCopied", this.i18nService.t(typeI18nKey)) -+ ); -+ -+ if (typeI18nKey === "password") { -+ this.eventService.collect(EventType.Cipher_ClientToggledHiddenFieldVisible, this.cipherId); -+ } else if (typeI18nKey === "securityCode") { -+ this.eventService.collect(EventType.Cipher_ClientCopiedCardCode, this.cipherId); -+ } else if (aType === "H_Field") { -+ this.eventService.collect(EventType.Cipher_ClientCopiedHiddenField, this.cipherId); -+ } -+ } - -- let url: string; -- try { -- const attachmentDownloadResponse = await this.apiService.getAttachmentData(this.cipher.id, attachment.id); -- url = attachmentDownloadResponse.url; -- } catch (e) { -- if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) { -- url = attachment.url; -- } else if (e instanceof ErrorResponse) { -- throw new Error((e as ErrorResponse).getSingleMessage()); -- } else { -- throw e; -- } -- } -+ setTextDataOnDrag(event: DragEvent, data: string) { -+ event.dataTransfer.setData("text", data); -+ } - -- a.downloading = true; -- const response = await fetch(new Request(url, { cache: 'no-store' })); -- if (response.status !== 200) { -- this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred')); -- a.downloading = false; -- return; -- } -+ async downloadAttachment(attachment: AttachmentView) { -+ if (!(await this.promptPassword())) { -+ return; -+ } -+ const a = attachment as any; -+ if (a.downloading) { -+ return; -+ } - -- try { -- const buf = await response.arrayBuffer(); -- const key = attachment.key != null ? attachment.key : -- await this.cryptoService.getOrgKey(this.cipher.organizationId); -- const decBuf = await this.cryptoService.decryptFromBytes(buf, key); -- this.platformUtilsService.saveFile(this.win, decBuf, null, attachment.fileName); -- } catch (e) { -- this.platformUtilsService.showToast('error', null, this.i18nService.t('errorOccurred')); -- } -+ if (this.cipher.organizationId == null && !this.canAccessPremium) { -+ this.platformUtilsService.showToast( -+ "error", -+ this.i18nService.t("premiumRequired"), -+ this.i18nService.t("premiumRequiredDesc") -+ ); -+ return; -+ } - -- a.downloading = false; -+ let url: string; -+ try { -+ const attachmentDownloadResponse = await this.apiService.getAttachmentData( -+ this.cipher.id, -+ attachment.id -+ ); -+ url = attachmentDownloadResponse.url; -+ } catch (e) { -+ if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) { -+ url = attachment.url; -+ } else if (e instanceof ErrorResponse) { -+ throw new Error((e as ErrorResponse).getSingleMessage()); -+ } else { -+ throw e; -+ } - } - -- protected deleteCipher() { -- return this.cipher.isDeleted ? this.cipherService.deleteWithServer(this.cipher.id) -- : this.cipherService.softDeleteWithServer(this.cipher.id); -+ a.downloading = true; -+ const response = await fetch(new Request(url, { cache: "no-store" })); -+ if (response.status !== 200) { -+ this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); -+ a.downloading = false; -+ return; - } - -- protected restoreCipher() { -- return this.cipherService.restoreWithServer(this.cipher.id); -+ try { -+ const buf = await response.arrayBuffer(); -+ const key = -+ attachment.key != null -+ ? attachment.key -+ : await this.cryptoService.getOrgKey(this.cipher.organizationId); -+ const decBuf = await this.cryptoService.decryptFromBytes(buf, key); -+ this.platformUtilsService.saveFile(this.win, decBuf, null, attachment.fileName); -+ } catch (e) { -+ this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); - } - -- protected async promptPassword() { -- if (this.cipher.reprompt === CipherRepromptType.None || this.passwordReprompted) { -- return true; -- } -+ a.downloading = false; -+ } - -- return this.passwordReprompted = await this.passwordRepromptService.showPasswordPrompt(); -- } -+ protected deleteCipher() { -+ return this.cipher.isDeleted -+ ? this.cipherService.deleteWithServer(this.cipher.id) -+ : this.cipherService.softDeleteWithServer(this.cipher.id); -+ } - -- private cleanUp() { -- this.totpCode = null; -- this.cipher = null; -- this.showPassword = false; -- this.showCardNumber = false; -- this.showCardCode = false; -- this.passwordReprompted = false; -- if (this.totpInterval) { -- clearInterval(this.totpInterval); -- } -+ protected restoreCipher() { -+ return this.cipherService.restoreWithServer(this.cipher.id); -+ } -+ -+ protected async promptPassword() { -+ if (this.cipher.reprompt === CipherRepromptType.None || this.passwordReprompted) { -+ return true; - } - -- private async totpUpdateCode() { -- if (this.cipher == null || this.cipher.type !== CipherType.Login || this.cipher.login.totp == null) { -- if (this.totpInterval) { -- clearInterval(this.totpInterval); -- } -- return; -- } -+ return (this.passwordReprompted = await this.passwordRepromptService.showPasswordPrompt()); -+ } -+ -+ private cleanUp() { -+ this.totpCode = null; -+ this.cipher = null; -+ this.showPassword = false; -+ this.showCardNumber = false; -+ this.showCardCode = false; -+ this.passwordReprompted = false; -+ if (this.totpInterval) { -+ clearInterval(this.totpInterval); -+ } -+ } -+ -+ private async totpUpdateCode() { -+ if ( -+ this.cipher == null || -+ this.cipher.type !== CipherType.Login || -+ this.cipher.login.totp == null -+ ) { -+ if (this.totpInterval) { -+ clearInterval(this.totpInterval); -+ } -+ return; -+ } - -- this.totpCode = await this.totpService.getCode(this.cipher.login.totp); -- if (this.totpCode != null) { -- if (this.totpCode.length > 4) { -- const half = Math.floor(this.totpCode.length / 2); -- this.totpCodeFormatted = this.totpCode.substring(0, half) + ' ' + this.totpCode.substring(half); -- } else { -- this.totpCodeFormatted = this.totpCode; -- } -- } else { -- this.totpCodeFormatted = null; -- if (this.totpInterval) { -- clearInterval(this.totpInterval); -- } -- } -+ this.totpCode = await this.totpService.getCode(this.cipher.login.totp); -+ if (this.totpCode != null) { -+ if (this.totpCode.length > 4) { -+ const half = Math.floor(this.totpCode.length / 2); -+ this.totpCodeFormatted = -+ this.totpCode.substring(0, half) + " " + this.totpCode.substring(half); -+ } else { -+ this.totpCodeFormatted = this.totpCode; -+ } -+ } else { -+ this.totpCodeFormatted = null; -+ if (this.totpInterval) { -+ clearInterval(this.totpInterval); -+ } - } -+ } - -- private async totpTick(intervalSeconds: number) { -- const epoch = Math.round(new Date().getTime() / 1000.0); -- const mod = epoch % intervalSeconds; -+ private async totpTick(intervalSeconds: number) { -+ const epoch = Math.round(new Date().getTime() / 1000.0); -+ const mod = epoch % intervalSeconds; - -- this.totpSec = intervalSeconds - mod; -- this.totpDash = +(Math.round((((78.6 / intervalSeconds) * mod) + 'e+2') as any) + 'e-2'); -- this.totpLow = this.totpSec <= 7; -- if (mod === 0) { -- await this.totpUpdateCode(); -- } -+ this.totpSec = intervalSeconds - mod; -+ this.totpDash = +(Math.round(((78.6 / intervalSeconds) * mod + "e+2") as any) + "e-2"); -+ this.totpLow = this.totpSec <= 7; -+ if (mod === 0) { -+ await this.totpUpdateCode(); - } -+ } - } -diff --git a/jslib/angular/src/directives/a11y-title.directive.ts b/jslib/angular/src/directives/a11y-title.directive.ts -index e8d57669..20e53a55 100644 ---- a/jslib/angular/src/directives/a11y-title.directive.ts -+++ b/jslib/angular/src/directives/a11y-title.directive.ts -@@ -1,28 +1,23 @@ --import { -- Directive, -- ElementRef, -- Input, -- Renderer2, --} from '@angular/core'; -+import { Directive, ElementRef, Input, Renderer2 } from "@angular/core"; - - @Directive({ -- selector: '[appA11yTitle]', -+ selector: "[appA11yTitle]", - }) - export class A11yTitleDirective { -- @Input() set appA11yTitle(title: string) { -- this.title = title; -- } -+ @Input() set appA11yTitle(title: string) { -+ this.title = title; -+ } - -- private title: string; -+ private title: string; - -- constructor(private el: ElementRef, private renderer: Renderer2) { } -+ constructor(private el: ElementRef, private renderer: Renderer2) {} - -- ngOnInit() { -- if (!this.el.nativeElement.hasAttribute('title')) { -- this.renderer.setAttribute(this.el.nativeElement, 'title', this.title); -- } -- if (!this.el.nativeElement.hasAttribute('aria-label')) { -- this.renderer.setAttribute(this.el.nativeElement, 'aria-label', this.title); -- } -+ ngOnInit() { -+ if (!this.el.nativeElement.hasAttribute("title")) { -+ this.renderer.setAttribute(this.el.nativeElement, "title", this.title); -+ } -+ if (!this.el.nativeElement.hasAttribute("aria-label")) { -+ this.renderer.setAttribute(this.el.nativeElement, "aria-label", this.title); - } -+ } - } -diff --git a/jslib/angular/src/directives/api-action.directive.ts b/jslib/angular/src/directives/api-action.directive.ts -index 049d4b30..64f209c9 100644 ---- a/jslib/angular/src/directives/api-action.directive.ts -+++ b/jslib/angular/src/directives/api-action.directive.ts -@@ -1,42 +1,46 @@ --import { -- Directive, -- ElementRef, -- Input, -- OnChanges, --} from '@angular/core'; --import { LogService } from 'jslib-common/abstractions/log.service'; -+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 { ErrorResponse } from "jslib-common/models/response/errorResponse"; - --import { ValidationService } from '../services/validation.service'; -+import { ValidationService } from "../services/validation.service"; - - @Directive({ -- selector: '[appApiAction]', -+ selector: "[appApiAction]", - }) - export class ApiActionDirective implements OnChanges { -- @Input() appApiAction: Promise; -+ @Input() appApiAction: Promise; - -- constructor(private el: ElementRef, private validationService: ValidationService, -- private logService: LogService) { } -+ constructor( -+ private el: ElementRef, -+ private validationService: ValidationService, -+ private logService: LogService -+ ) {} - -- ngOnChanges(changes: any) { -- if (this.appApiAction == null || this.appApiAction.then == null) { -- return; -- } -- -- this.el.nativeElement.loading = true; -- -- this.appApiAction.then((response: any) => { -- this.el.nativeElement.loading = false; -- }, (e: any) => { -- this.el.nativeElement.loading = false; -- -- if ((e instanceof ErrorResponse || e.constructor.name === 'ErrorResponse') && (e as ErrorResponse).captchaRequired) { -- this.logService.error('Captcha required error response: ' + e.getSingleMessage()); -- return; -- } -- this.logService?.error(`Received API exception: ${e}`); -- this.validationService.showError(e); -- }); -+ ngOnChanges(changes: any) { -+ if (this.appApiAction == null || this.appApiAction.then == null) { -+ return; - } -+ -+ this.el.nativeElement.loading = true; -+ -+ this.appApiAction.then( -+ (response: any) => { -+ this.el.nativeElement.loading = false; -+ }, -+ (e: any) => { -+ this.el.nativeElement.loading = false; -+ -+ if ( -+ (e instanceof ErrorResponse || e.constructor.name === "ErrorResponse") && -+ (e as ErrorResponse).captchaRequired -+ ) { -+ this.logService.error("Captcha required error response: " + e.getSingleMessage()); -+ return; -+ } -+ this.logService?.error(`Received API exception: ${e}`); -+ this.validationService.showError(e); -+ } -+ ); -+ } - } -diff --git a/jslib/angular/src/directives/autofocus.directive.ts b/jslib/angular/src/directives/autofocus.directive.ts -index 89b0cfd9..a90a8075 100644 ---- a/jslib/angular/src/directives/autofocus.directive.ts -+++ b/jslib/angular/src/directives/autofocus.directive.ts -@@ -1,33 +1,28 @@ --import { -- Directive, -- ElementRef, -- Input, -- NgZone, --} from '@angular/core'; -+import { Directive, ElementRef, Input, NgZone } from "@angular/core"; - --import { take } from 'rxjs/operators'; -+import { take } from "rxjs/operators"; - --import { Utils } from 'jslib-common/misc/utils'; -+import { Utils } from "jslib-common/misc/utils"; - - @Directive({ -- selector: '[appAutofocus]', -+ selector: "[appAutofocus]", - }) - export class AutofocusDirective { -- @Input() set appAutofocus(condition: boolean | string) { -- this.autofocus = condition === '' || condition === true; -- } -+ @Input() set appAutofocus(condition: boolean | string) { -+ this.autofocus = condition === "" || condition === true; -+ } - -- private autofocus: boolean; -+ private autofocus: boolean; - -- constructor(private el: ElementRef, private ngZone: NgZone) { } -+ constructor(private el: ElementRef, private ngZone: NgZone) {} - -- ngOnInit() { -- if (!Utils.isMobileBrowser && this.autofocus) { -- if (this.ngZone.isStable) { -- this.el.nativeElement.focus(); -- } else { -- this.ngZone.onStable.pipe(take(1)).subscribe(() => this.el.nativeElement.focus()); -- } -- } -+ ngOnInit() { -+ if (!Utils.isMobileBrowser && this.autofocus) { -+ if (this.ngZone.isStable) { -+ this.el.nativeElement.focus(); -+ } else { -+ this.ngZone.onStable.pipe(take(1)).subscribe(() => this.el.nativeElement.focus()); -+ } - } -+ } - } -diff --git a/jslib/angular/src/directives/blur-click.directive.ts b/jslib/angular/src/directives/blur-click.directive.ts -index 48555bb9..2fc1d367 100644 ---- a/jslib/angular/src/directives/blur-click.directive.ts -+++ b/jslib/angular/src/directives/blur-click.directive.ts -@@ -1,17 +1,12 @@ --import { -- Directive, -- ElementRef, -- HostListener, --} from '@angular/core'; -+import { Directive, ElementRef, HostListener } from "@angular/core"; - - @Directive({ -- selector: '[appBlurClick]', -+ selector: "[appBlurClick]", - }) - export class BlurClickDirective { -- constructor(private el: ElementRef) { -- } -+ constructor(private el: ElementRef) {} - -- @HostListener('click') onClick() { -- this.el.nativeElement.blur(); -- } -+ @HostListener("click") onClick() { -+ this.el.nativeElement.blur(); -+ } - } -diff --git a/jslib/angular/src/directives/box-row.directive.ts b/jslib/angular/src/directives/box-row.directive.ts -index a7dd0bee..049f0434 100644 ---- a/jslib/angular/src/directives/box-row.directive.ts -+++ b/jslib/angular/src/directives/box-row.directive.ts -@@ -1,51 +1,59 @@ --import { -- Directive, -- ElementRef, -- HostListener, -- OnInit, --} from '@angular/core'; -+import { Directive, ElementRef, HostListener, OnInit } from "@angular/core"; - - @Directive({ -- selector: '[appBoxRow]', -+ selector: "[appBoxRow]", - }) - export class BoxRowDirective implements OnInit { -- el: HTMLElement = null; -- formEls: Element[]; -+ el: HTMLElement = null; -+ formEls: Element[]; - -- constructor(private elRef: ElementRef) { -- this.el = elRef.nativeElement; -- } -+ constructor(private elRef: ElementRef) { -+ this.el = elRef.nativeElement; -+ } - -- ngOnInit(): void { -- this.formEls = Array.from(this.el.querySelectorAll('input:not([type="hidden"]), select, textarea')); -- this.formEls.forEach(formEl => { -- formEl.addEventListener('focus', (event: Event) => { -- this.el.classList.add('active'); -- }, false); -+ ngOnInit(): void { -+ this.formEls = Array.from( -+ this.el.querySelectorAll('input:not([type="hidden"]), select, textarea') -+ ); -+ this.formEls.forEach((formEl) => { -+ formEl.addEventListener( -+ "focus", -+ (event: Event) => { -+ this.el.classList.add("active"); -+ }, -+ false -+ ); - -- formEl.addEventListener('blur', (event: Event) => { -- this.el.classList.remove('active'); -- }, false); -- }); -- } -+ formEl.addEventListener( -+ "blur", -+ (event: Event) => { -+ this.el.classList.remove("active"); -+ }, -+ false -+ ); -+ }); -+ } - -- @HostListener('click', ['$event']) onClick(event: Event) { -- const target = event.target as HTMLElement; -- if (target !== this.el && !target.classList.contains('progress') && -- !target.classList.contains('progress-bar')) { -- return; -- } -+ @HostListener("click", ["$event"]) onClick(event: Event) { -+ const target = event.target as HTMLElement; -+ if ( -+ target !== this.el && -+ !target.classList.contains("progress") && -+ !target.classList.contains("progress-bar") -+ ) { -+ return; -+ } - -- if (this.formEls.length > 0) { -- const formEl = (this.formEls[0] as HTMLElement); -- if (formEl.tagName.toLowerCase() === 'input') { -- const inputEl = (formEl as HTMLInputElement); -- if (inputEl.type != null && inputEl.type.toLowerCase() === 'checkbox') { -- inputEl.click(); -- return; -- } -- } -- formEl.focus(); -+ if (this.formEls.length > 0) { -+ const formEl = this.formEls[0] as HTMLElement; -+ if (formEl.tagName.toLowerCase() === "input") { -+ const inputEl = formEl as HTMLInputElement; -+ if (inputEl.type != null && inputEl.type.toLowerCase() === "checkbox") { -+ inputEl.click(); -+ return; - } -+ } -+ formEl.focus(); - } -+ } - } -diff --git a/jslib/angular/src/directives/cipherListVirtualScroll.directive.ts b/jslib/angular/src/directives/cipherListVirtualScroll.directive.ts -index a0c8a6f4..7b906b53 100644 ---- a/jslib/angular/src/directives/cipherListVirtualScroll.directive.ts -+++ b/jslib/angular/src/directives/cipherListVirtualScroll.directive.ts -@@ -1,62 +1,76 @@ - import { -- CdkFixedSizeVirtualScroll, -- FixedSizeVirtualScrollStrategy, -- VIRTUAL_SCROLL_STRATEGY, --} from '@angular/cdk/scrolling'; --import { -- Directive, -- forwardRef, --} from '@angular/core'; -+ CdkFixedSizeVirtualScroll, -+ FixedSizeVirtualScrollStrategy, -+ VIRTUAL_SCROLL_STRATEGY, -+} from "@angular/cdk/scrolling"; -+import { Directive, forwardRef } from "@angular/core"; - - // Custom virtual scroll strategy for cdk-virtual-scroll - // Uses a sample list item to set the itemSize for FixedSizeVirtualScrollStrategy - // The use case is the same as FixedSizeVirtualScrollStrategy, but it avoids locking in pixel sizes in the template. - export class CipherListVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy { -- private checkItemSizeCallback: any; -- private timeout: any; -- -- constructor(itemSize: number, minBufferPx: number, maxBufferPx: number, checkItemSizeCallback: any) { -- super(itemSize, minBufferPx, maxBufferPx); -- this.checkItemSizeCallback = checkItemSizeCallback; -- } -+ private checkItemSizeCallback: any; -+ private timeout: any; - -- onContentRendered() { -- if (this.timeout != null) { -- clearTimeout(this.timeout); -- } -+ constructor( -+ itemSize: number, -+ minBufferPx: number, -+ maxBufferPx: number, -+ checkItemSizeCallback: any -+ ) { -+ super(itemSize, minBufferPx, maxBufferPx); -+ this.checkItemSizeCallback = checkItemSizeCallback; -+ } - -- this.timeout = setTimeout(this.checkItemSizeCallback, 500); -+ onContentRendered() { -+ if (this.timeout != null) { -+ clearTimeout(this.timeout); - } -+ -+ this.timeout = setTimeout(this.checkItemSizeCallback, 500); -+ } - } - - export function _cipherListVirtualScrollStrategyFactory(cipherListDir: CipherListVirtualScroll) { -- return cipherListDir._scrollStrategy; -+ return cipherListDir._scrollStrategy; - } - - @Directive({ -- selector: 'cdk-virtual-scroll-viewport[itemSize]', -- providers: [{ -- provide: VIRTUAL_SCROLL_STRATEGY, -- useFactory: _cipherListVirtualScrollStrategyFactory, -- deps: [forwardRef(() => CipherListVirtualScroll)], -- }], -+ selector: "cdk-virtual-scroll-viewport[itemSize]", -+ providers: [ -+ { -+ provide: VIRTUAL_SCROLL_STRATEGY, -+ useFactory: _cipherListVirtualScrollStrategyFactory, -+ deps: [forwardRef(() => CipherListVirtualScroll)], -+ }, -+ ], - }) - export class CipherListVirtualScroll extends CdkFixedSizeVirtualScroll { -- _scrollStrategy: CipherListVirtualScrollStrategy; -+ _scrollStrategy: CipherListVirtualScrollStrategy; - -- constructor() { -- super(); -- this._scrollStrategy = new CipherListVirtualScrollStrategy(this.itemSize, this.minBufferPx, this.maxBufferPx, -- this.checkAndUpdateItemSize); -- } -+ constructor() { -+ super(); -+ this._scrollStrategy = new CipherListVirtualScrollStrategy( -+ this.itemSize, -+ this.minBufferPx, -+ this.maxBufferPx, -+ this.checkAndUpdateItemSize -+ ); -+ } - -- checkAndUpdateItemSize = () => { -- const sampleItem = document.querySelector('cdk-virtual-scroll-viewport .virtual-scroll-item') as HTMLElement; -- const newItemSize = sampleItem?.offsetHeight; -+ checkAndUpdateItemSize = () => { -+ const sampleItem = document.querySelector( -+ "cdk-virtual-scroll-viewport .virtual-scroll-item" -+ ) as HTMLElement; -+ const newItemSize = sampleItem?.offsetHeight; - -- if (newItemSize != null && newItemSize !== this.itemSize) { -- this.itemSize = newItemSize; -- this._scrollStrategy.updateItemAndBufferSize(this.itemSize, this.minBufferPx, this.maxBufferPx); -- } -+ if (newItemSize != null && newItemSize !== this.itemSize) { -+ this.itemSize = newItemSize; -+ this._scrollStrategy.updateItemAndBufferSize( -+ this.itemSize, -+ this.minBufferPx, -+ this.maxBufferPx -+ ); - } -+ }; - } -diff --git a/jslib/angular/src/directives/fallback-src.directive.ts b/jslib/angular/src/directives/fallback-src.directive.ts -index 3e5e3381..11bce205 100644 ---- a/jslib/angular/src/directives/fallback-src.directive.ts -+++ b/jslib/angular/src/directives/fallback-src.directive.ts -@@ -1,20 +1,14 @@ --import { -- Directive, -- ElementRef, -- HostListener, -- Input, --} from '@angular/core'; -+import { Directive, ElementRef, HostListener, Input } from "@angular/core"; - - @Directive({ -- selector: '[appFallbackSrc]', -+ selector: "[appFallbackSrc]", - }) - export class FallbackSrcDirective { -- @Input('appFallbackSrc') appFallbackSrc: string; -+ @Input("appFallbackSrc") appFallbackSrc: string; - -- constructor(private el: ElementRef) { -- } -+ constructor(private el: ElementRef) {} - -- @HostListener('error') onError() { -- this.el.nativeElement.src = this.appFallbackSrc; -- } -+ @HostListener("error") onError() { -+ this.el.nativeElement.src = this.appFallbackSrc; -+ } - } -diff --git a/jslib/angular/src/directives/input-verbatim.directive.ts b/jslib/angular/src/directives/input-verbatim.directive.ts -index 9c94fbcb..3dd1975a 100644 ---- a/jslib/angular/src/directives/input-verbatim.directive.ts -+++ b/jslib/angular/src/directives/input-verbatim.directive.ts -@@ -1,37 +1,32 @@ --import { -- Directive, -- ElementRef, -- Input, -- Renderer2, --} from '@angular/core'; -+import { Directive, ElementRef, Input, Renderer2 } from "@angular/core"; - - @Directive({ -- selector: '[appInputVerbatim]', -+ selector: "[appInputVerbatim]", - }) - export class InputVerbatimDirective { -- @Input() set appInputVerbatim(condition: boolean | string) { -- this.disableComplete = condition === '' || condition === true; -- } -+ @Input() set appInputVerbatim(condition: boolean | string) { -+ this.disableComplete = condition === "" || condition === true; -+ } - -- private disableComplete: boolean; -+ private disableComplete: boolean; - -- constructor(private el: ElementRef, private renderer: Renderer2) { } -+ constructor(private el: ElementRef, private renderer: Renderer2) {} - -- ngOnInit() { -- if (this.disableComplete && !this.el.nativeElement.hasAttribute('autocomplete')) { -- this.renderer.setAttribute(this.el.nativeElement, 'autocomplete', 'off'); -- } -- if (!this.el.nativeElement.hasAttribute('autocapitalize')) { -- this.renderer.setAttribute(this.el.nativeElement, 'autocapitalize', 'none'); -- } -- if (!this.el.nativeElement.hasAttribute('autocorrect')) { -- this.renderer.setAttribute(this.el.nativeElement, 'autocorrect', 'none'); -- } -- if (!this.el.nativeElement.hasAttribute('spellcheck')) { -- this.renderer.setAttribute(this.el.nativeElement, 'spellcheck', 'false'); -- } -- if (!this.el.nativeElement.hasAttribute('inputmode')) { -- this.renderer.setAttribute(this.el.nativeElement, 'inputmode', 'verbatim'); -- } -+ ngOnInit() { -+ if (this.disableComplete && !this.el.nativeElement.hasAttribute("autocomplete")) { -+ this.renderer.setAttribute(this.el.nativeElement, "autocomplete", "off"); -+ } -+ if (!this.el.nativeElement.hasAttribute("autocapitalize")) { -+ this.renderer.setAttribute(this.el.nativeElement, "autocapitalize", "none"); -+ } -+ if (!this.el.nativeElement.hasAttribute("autocorrect")) { -+ this.renderer.setAttribute(this.el.nativeElement, "autocorrect", "none"); -+ } -+ if (!this.el.nativeElement.hasAttribute("spellcheck")) { -+ this.renderer.setAttribute(this.el.nativeElement, "spellcheck", "false"); -+ } -+ if (!this.el.nativeElement.hasAttribute("inputmode")) { -+ this.renderer.setAttribute(this.el.nativeElement, "inputmode", "verbatim"); - } -+ } - } -diff --git a/jslib/angular/src/directives/select-copy.directive.ts b/jslib/angular/src/directives/select-copy.directive.ts -index 1edc0bdd..81d4928b 100644 ---- a/jslib/angular/src/directives/select-copy.directive.ts -+++ b/jslib/angular/src/directives/select-copy.directive.ts -@@ -1,41 +1,37 @@ --import { -- Directive, -- ElementRef, -- HostListener, --} from '@angular/core'; -+import { Directive, ElementRef, HostListener } from "@angular/core"; - --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; - - @Directive({ -- selector: '[appSelectCopy]', -+ selector: "[appSelectCopy]", - }) - export class SelectCopyDirective { -- constructor(private el: ElementRef, private platformUtilsService: PlatformUtilsService) { } -+ constructor(private el: ElementRef, private platformUtilsService: PlatformUtilsService) {} - -- @HostListener('copy') onCopy() { -- if (window == null) { -- return; -- } -- let copyText = ''; -- const selection = window.getSelection(); -- for (let i = 0; i < selection.rangeCount; i++) { -- const range = selection.getRangeAt(i); -- const text = range.toString(); -+ @HostListener("copy") onCopy() { -+ if (window == null) { -+ return; -+ } -+ let copyText = ""; -+ const selection = window.getSelection(); -+ for (let i = 0; i < selection.rangeCount; i++) { -+ const range = selection.getRangeAt(i); -+ const text = range.toString(); - -- // The selection should only contain one line of text. In some cases however, the -- // selection contains newlines and space characters from the indentation of following -- // sibling nodes. To avoid copying passwords containing trailing newlines and spaces -- // that aren't part of the password, the selection has to be trimmed. -- let stringEndPos = text.length; -- const newLinePos = text.search(/(?:\r\n|\r|\n)/); -- if (newLinePos > -1) { -- const otherPart = text.substr(newLinePos).trim(); -- if (otherPart === '') { -- stringEndPos = newLinePos; -- } -- } -- copyText += text.substring(0, stringEndPos); -+ // The selection should only contain one line of text. In some cases however, the -+ // selection contains newlines and space characters from the indentation of following -+ // sibling nodes. To avoid copying passwords containing trailing newlines and spaces -+ // that aren't part of the password, the selection has to be trimmed. -+ let stringEndPos = text.length; -+ const newLinePos = text.search(/(?:\r\n|\r|\n)/); -+ if (newLinePos > -1) { -+ const otherPart = text.substr(newLinePos).trim(); -+ if (otherPart === "") { -+ stringEndPos = newLinePos; - } -- this.platformUtilsService.copyToClipboard(copyText, { window: window }); -+ } -+ copyText += text.substring(0, stringEndPos); - } -+ this.platformUtilsService.copyToClipboard(copyText, { window: window }); -+ } - } -diff --git a/jslib/angular/src/directives/stop-click.directive.ts b/jslib/angular/src/directives/stop-click.directive.ts -index 0529556b..0e88dde3 100644 ---- a/jslib/angular/src/directives/stop-click.directive.ts -+++ b/jslib/angular/src/directives/stop-click.directive.ts -@@ -1,13 +1,10 @@ --import { -- Directive, -- HostListener, --} from '@angular/core'; -+import { Directive, HostListener } from "@angular/core"; - - @Directive({ -- selector: '[appStopClick]', -+ selector: "[appStopClick]", - }) - export class StopClickDirective { -- @HostListener('click', ['$event']) onClick($event: MouseEvent) { -- $event.preventDefault(); -- } -+ @HostListener("click", ["$event"]) onClick($event: MouseEvent) { -+ $event.preventDefault(); -+ } - } -diff --git a/jslib/angular/src/directives/stop-prop.directive.ts b/jslib/angular/src/directives/stop-prop.directive.ts -index b241f628..8393e799 100644 ---- a/jslib/angular/src/directives/stop-prop.directive.ts -+++ b/jslib/angular/src/directives/stop-prop.directive.ts -@@ -1,13 +1,10 @@ --import { -- Directive, -- HostListener, --} from '@angular/core'; -+import { Directive, HostListener } from "@angular/core"; - - @Directive({ -- selector: '[appStopProp]', -+ selector: "[appStopProp]", - }) - export class StopPropDirective { -- @HostListener('click', ['$event']) onClick($event: MouseEvent) { -- $event.stopPropagation(); -- } -+ @HostListener("click", ["$event"]) onClick($event: MouseEvent) { -+ $event.stopPropagation(); -+ } - } -diff --git a/jslib/angular/src/directives/true-false-value.directive.ts b/jslib/angular/src/directives/true-false-value.directive.ts -index 6dcf628e..6b553c61 100644 ---- a/jslib/angular/src/directives/true-false-value.directive.ts -+++ b/jslib/angular/src/directives/true-false-value.directive.ts -@@ -1,54 +1,49 @@ --import { -- Directive, -- ElementRef, -- forwardRef, -- HostListener, -- Input, -- Renderer2, --} from '@angular/core'; --import { -- ControlValueAccessor, -- NgControl, -- NG_VALUE_ACCESSOR, --} from '@angular/forms'; -+import { Directive, ElementRef, forwardRef, HostListener, Input, Renderer2 } from "@angular/core"; -+import { ControlValueAccessor, NgControl, NG_VALUE_ACCESSOR } from "@angular/forms"; - - // ref: https://juristr.com/blog/2018/02/ng-true-value-directive/ - @Directive({ -- selector: 'input[type=checkbox][appTrueFalseValue]', -- providers: [ -- { -- provide: NG_VALUE_ACCESSOR, -- useExisting: forwardRef(() => TrueFalseValueDirective), -- multi: true, -- }, -- ], -+ selector: "input[type=checkbox][appTrueFalseValue]", -+ providers: [ -+ { -+ provide: NG_VALUE_ACCESSOR, -+ useExisting: forwardRef(() => TrueFalseValueDirective), -+ multi: true, -+ }, -+ ], - }) - export class TrueFalseValueDirective implements ControlValueAccessor { -- @Input() trueValue = true; -- @Input() falseValue = false; -+ @Input() trueValue = true; -+ @Input() falseValue = false; - -- constructor(private elementRef: ElementRef, private renderer: Renderer2) { } -+ constructor(private elementRef: ElementRef, private renderer: Renderer2) {} - -- @HostListener('change', ['$event']) -- onHostChange(ev: any) { -- this.propagateChange(ev.target.checked ? this.trueValue : this.falseValue); -- } -+ @HostListener("change", ["$event"]) -+ onHostChange(ev: any) { -+ this.propagateChange(ev.target.checked ? this.trueValue : this.falseValue); -+ } - -- writeValue(obj: any): void { -- if (obj === this.trueValue) { -- this.renderer.setProperty(this.elementRef.nativeElement, 'checked', true); -- } else { -- this.renderer.setProperty(this.elementRef.nativeElement, 'checked', false); -- } -+ writeValue(obj: any): void { -+ if (obj === this.trueValue) { -+ this.renderer.setProperty(this.elementRef.nativeElement, "checked", true); -+ } else { -+ this.renderer.setProperty(this.elementRef.nativeElement, "checked", false); - } -+ } - -- registerOnChange(fn: any): void { -- this.propagateChange = fn; -- } -+ registerOnChange(fn: any): void { -+ this.propagateChange = fn; -+ } - -- registerOnTouched(fn: any): void { /* nothing */ } -+ registerOnTouched(fn: any): void { -+ /* nothing */ -+ } - -- setDisabledState?(isDisabled: boolean): void { /* nothing */ } -+ setDisabledState?(isDisabled: boolean): void { -+ /* nothing */ -+ } - -- private propagateChange = (_: any) => { /* nothing */ }; -+ private propagateChange = (_: any) => { -+ /* nothing */ -+ }; - } -diff --git a/jslib/angular/src/images/cards/amex-dark.png b/jslib/angular/src/images/cards/amex-dark.png -new file mode 100644 -index 00000000..ac1b0759 -Binary files /dev/null and b/jslib/angular/src/images/cards/amex-dark.png differ -diff --git a/jslib/angular/src/images/cards/amex-light.png b/jslib/angular/src/images/cards/amex-light.png -new file mode 100644 -index 00000000..ac1b0759 -Binary files /dev/null and b/jslib/angular/src/images/cards/amex-light.png differ -diff --git a/jslib/angular/src/images/cards/diners_club-dark.png b/jslib/angular/src/images/cards/diners_club-dark.png -new file mode 100644 -index 00000000..a5d23233 -Binary files /dev/null and b/jslib/angular/src/images/cards/diners_club-dark.png differ -diff --git a/jslib/angular/src/images/cards/diners_club-light.png b/jslib/angular/src/images/cards/diners_club-light.png -new file mode 100644 -index 00000000..e2e33a24 -Binary files /dev/null and b/jslib/angular/src/images/cards/diners_club-light.png differ -diff --git a/jslib/angular/src/images/cards/discover-dark.png b/jslib/angular/src/images/cards/discover-dark.png -new file mode 100644 -index 00000000..2ed6f6f9 -Binary files /dev/null and b/jslib/angular/src/images/cards/discover-dark.png differ -diff --git a/jslib/angular/src/images/cards/discover-light.png b/jslib/angular/src/images/cards/discover-light.png -new file mode 100644 -index 00000000..755c6957 -Binary files /dev/null and b/jslib/angular/src/images/cards/discover-light.png differ -diff --git a/jslib/angular/src/images/cards/jcb-dark.png b/jslib/angular/src/images/cards/jcb-dark.png -new file mode 100644 -index 00000000..1cb484d9 -Binary files /dev/null and b/jslib/angular/src/images/cards/jcb-dark.png differ -diff --git a/jslib/angular/src/images/cards/jcb-light.png b/jslib/angular/src/images/cards/jcb-light.png -new file mode 100644 -index 00000000..2b89f5b9 -Binary files /dev/null and b/jslib/angular/src/images/cards/jcb-light.png differ -diff --git a/jslib/angular/src/images/cards/maestro-dark.png b/jslib/angular/src/images/cards/maestro-dark.png -new file mode 100644 -index 00000000..4a4879b5 -Binary files /dev/null and b/jslib/angular/src/images/cards/maestro-dark.png differ -diff --git a/jslib/angular/src/images/cards/maestro-light.png b/jslib/angular/src/images/cards/maestro-light.png -new file mode 100644 -index 00000000..2329738a -Binary files /dev/null and b/jslib/angular/src/images/cards/maestro-light.png differ -diff --git a/jslib/angular/src/images/cards/mastercard-dark.png b/jslib/angular/src/images/cards/mastercard-dark.png -new file mode 100644 -index 00000000..cd806263 -Binary files /dev/null and b/jslib/angular/src/images/cards/mastercard-dark.png differ -diff --git a/jslib/angular/src/images/cards/mastercard-light.png b/jslib/angular/src/images/cards/mastercard-light.png -new file mode 100644 -index 00000000..f87ae259 -Binary files /dev/null and b/jslib/angular/src/images/cards/mastercard-light.png differ -diff --git a/jslib/angular/src/images/cards/union_pay-dark.png b/jslib/angular/src/images/cards/union_pay-dark.png -new file mode 100644 -index 00000000..248be7c3 -Binary files /dev/null and b/jslib/angular/src/images/cards/union_pay-dark.png differ -diff --git a/jslib/angular/src/images/cards/union_pay-light.png b/jslib/angular/src/images/cards/union_pay-light.png -new file mode 100644 -index 00000000..2d75aa90 -Binary files /dev/null and b/jslib/angular/src/images/cards/union_pay-light.png differ -diff --git a/jslib/angular/src/images/cards/visa-dark.png b/jslib/angular/src/images/cards/visa-dark.png -new file mode 100644 -index 00000000..0705b68e -Binary files /dev/null and b/jslib/angular/src/images/cards/visa-dark.png differ -diff --git a/jslib/angular/src/images/cards/visa-light.png b/jslib/angular/src/images/cards/visa-light.png -new file mode 100644 -index 00000000..3dc8ae50 -Binary files /dev/null and b/jslib/angular/src/images/cards/visa-light.png differ -diff --git a/jslib/angular/src/pipes/color-password.pipe.ts b/jslib/angular/src/pipes/color-password.pipe.ts -index a6b4d8ec..f3a87d12 100644 ---- a/jslib/angular/src/pipes/color-password.pipe.ts -+++ b/jslib/angular/src/pipes/color-password.pipe.ts -@@ -1,53 +1,50 @@ --import { -- Pipe, -- PipeTransform, --} from '@angular/core'; --import { Utils } from 'jslib-common/misc/utils'; -+import { Pipe, PipeTransform } from "@angular/core"; -+import { Utils } from "jslib-common/misc/utils"; - - /* - An updated pipe that sanitizes HTML, highlights numbers and special characters (in different colors each) - and handles Unicode / Emoji characters correctly. - */ --@Pipe({ name: 'colorPassword' }) -+@Pipe({ name: "colorPassword" }) - export class ColorPasswordPipe implements PipeTransform { -- transform(password: string) { -- // Convert to an array to handle cases that stings have special characters, ie: emoji. -- const passwordArray = Array.from(password); -- let colorizedPassword = ''; -- for (let i = 0; i < passwordArray.length; i++) { -- let character = passwordArray[i]; -- let isSpecial = false; -- // Sanitize HTML first. -- switch (character) { -- case '&': -- character = '&'; -- isSpecial = true; -- break; -- case '<': -- character = '<'; -- isSpecial = true; -- break; -- case '>': -- character = '>'; -- isSpecial = true; -- break; -- case ' ': -- character = ' '; -- isSpecial = true; -- break; -- default: -- break; -- } -- let type = 'letter'; -- if (character.match(Utils.regexpEmojiPresentation)) { -- type = 'emoji'; -- } else if (isSpecial || character.match(/[^\w ]/)) { -- type = 'special'; -- } else if (character.match(/\d/)) { -- type = 'number'; -- } -- colorizedPassword += '' + character + ''; -- } -- return colorizedPassword; -+ transform(password: string) { -+ // Convert to an array to handle cases that stings have special characters, ie: emoji. -+ const passwordArray = Array.from(password); -+ let colorizedPassword = ""; -+ for (let i = 0; i < passwordArray.length; i++) { -+ let character = passwordArray[i]; -+ let isSpecial = false; -+ // Sanitize HTML first. -+ switch (character) { -+ case "&": -+ character = "&"; -+ isSpecial = true; -+ break; -+ case "<": -+ character = "<"; -+ isSpecial = true; -+ break; -+ case ">": -+ character = ">"; -+ isSpecial = true; -+ break; -+ case " ": -+ character = " "; -+ isSpecial = true; -+ break; -+ default: -+ break; -+ } -+ let type = "letter"; -+ if (character.match(Utils.regexpEmojiPresentation)) { -+ type = "emoji"; -+ } else if (isSpecial || character.match(/[^\w ]/)) { -+ type = "special"; -+ } else if (character.match(/\d/)) { -+ type = "number"; -+ } -+ colorizedPassword += '' + character + ""; - } -+ return colorizedPassword; -+ } - } -diff --git a/jslib/angular/src/pipes/i18n.pipe.ts b/jslib/angular/src/pipes/i18n.pipe.ts -index 4f3e470b..8fc09434 100644 ---- a/jslib/angular/src/pipes/i18n.pipe.ts -+++ b/jslib/angular/src/pipes/i18n.pipe.ts -@@ -1,17 +1,14 @@ --import { -- Pipe, -- PipeTransform, --} from '@angular/core'; -+import { Pipe, PipeTransform } from "@angular/core"; - --import { I18nService } from 'jslib-common/abstractions/i18n.service'; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; - - @Pipe({ -- name: 'i18n', -+ name: "i18n", - }) - export class I18nPipe implements PipeTransform { -- constructor(private i18nService: I18nService) { } -+ constructor(private i18nService: I18nService) {} - -- transform(id: string, p1?: string, p2?: string, p3?: string): string { -- return this.i18nService.t(id, p1, p2, p3); -- } -+ transform(id: string, p1?: string, p2?: string, p3?: string): string { -+ return this.i18nService.t(id, p1, p2, p3); -+ } - } -diff --git a/jslib/angular/src/pipes/search-ciphers.pipe.ts b/jslib/angular/src/pipes/search-ciphers.pipe.ts -index 92783f32..0842954c 100644 ---- a/jslib/angular/src/pipes/search-ciphers.pipe.ts -+++ b/jslib/angular/src/pipes/search-ciphers.pipe.ts -@@ -1,44 +1,41 @@ --import { -- Pipe, -- PipeTransform, --} from '@angular/core'; -+import { Pipe, PipeTransform } from "@angular/core"; - --import { CipherView } from 'jslib-common/models/view/cipherView'; -+import { CipherView } from "jslib-common/models/view/cipherView"; - - @Pipe({ -- name: 'searchCiphers', -+ name: "searchCiphers", - }) - export class SearchCiphersPipe implements PipeTransform { -- transform(ciphers: CipherView[], searchText: string, deleted: boolean = false): CipherView[] { -- if (ciphers == null || ciphers.length === 0) { -- return []; -- } -+ transform(ciphers: CipherView[], searchText: string, deleted: boolean = false): CipherView[] { -+ if (ciphers == null || ciphers.length === 0) { -+ return []; -+ } - -- if (searchText == null || searchText.length < 2) { -- return ciphers.filter(c => { -- return deleted !== c.isDeleted; -- }); -- } -+ 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; -- } -+ 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; -- }); -- } -+ return false; -+ }); -+ } - } -diff --git a/jslib/angular/src/pipes/search.pipe.ts b/jslib/angular/src/pipes/search.pipe.ts -index 6c9e140b..bf09d2de 100644 ---- a/jslib/angular/src/pipes/search.pipe.ts -+++ b/jslib/angular/src/pipes/search.pipe.ts -@@ -1,33 +1,48 @@ --import { -- Pipe, -- PipeTransform, --} from '@angular/core'; -+import { Pipe, PipeTransform } from "@angular/core"; - - @Pipe({ -- name: 'search', -+ name: "search", - }) - export class SearchPipe implements PipeTransform { -- transform(items: any[], searchText: string, prop1?: string, prop2?: string, prop3?: string): any[] { -- if (items == null || items.length === 0) { -- return []; -- } -- -- if (searchText == null || searchText.length < 2) { -- return items; -- } -+ transform( -+ items: any[], -+ searchText: string, -+ prop1?: string, -+ prop2?: string, -+ prop3?: string -+ ): any[] { -+ if (items == null || items.length === 0) { -+ return []; -+ } - -- searchText = searchText.trim().toLowerCase(); -- return items.filter(i => { -- if (prop1 != null && i[prop1] != null && i[prop1].toString().toLowerCase().indexOf(searchText) > -1) { -- return true; -- } -- if (prop2 != null && i[prop2] != null && i[prop2].toString().toLowerCase().indexOf(searchText) > -1) { -- return true; -- } -- if (prop3 != null && i[prop3] != null && i[prop3].toString().toLowerCase().indexOf(searchText) > -1) { -- return true; -- } -- return false; -- }); -+ if (searchText == null || searchText.length < 2) { -+ return items; - } -+ -+ searchText = searchText.trim().toLowerCase(); -+ return items.filter((i) => { -+ if ( -+ prop1 != null && -+ i[prop1] != null && -+ i[prop1].toString().toLowerCase().indexOf(searchText) > -1 -+ ) { -+ return true; -+ } -+ if ( -+ prop2 != null && -+ i[prop2] != null && -+ i[prop2].toString().toLowerCase().indexOf(searchText) > -1 -+ ) { -+ return true; -+ } -+ if ( -+ prop3 != null && -+ i[prop3] != null && -+ i[prop3].toString().toLowerCase().indexOf(searchText) > -1 -+ ) { -+ return true; -+ } -+ return false; -+ }); -+ } - } -diff --git a/jslib/angular/src/pipes/user-name.pipe.ts b/jslib/angular/src/pipes/user-name.pipe.ts -index 43397528..22c214e4 100644 ---- a/jslib/angular/src/pipes/user-name.pipe.ts -+++ b/jslib/angular/src/pipes/user-name.pipe.ts -@@ -1,22 +1,19 @@ --import { -- Pipe, -- PipeTransform, --} from '@angular/core'; -+import { Pipe, PipeTransform } from "@angular/core"; - - interface User { -- name?: string; -- email: string; -+ name?: string; -+ email: string; - } - - @Pipe({ -- name: 'userName', -+ name: "userName", - }) - export class UserNamePipe implements PipeTransform { -- transform(user?: User): string { -- if (user == null) { -- return null; -- } -- -- return user.name == null || user.name.trim() === '' ? user.email : user.name; -+ transform(user?: User): string { -+ if (user == null) { -+ return null; - } -+ -+ return user.name == null || user.name.trim() === "" ? user.email : user.name; -+ } - } -diff --git a/jslib/angular/src/scss/bwicons/fonts/bwi-font.svg b/jslib/angular/src/scss/bwicons/fonts/bwi-font.svg -new file mode 100644 -index 00000000..6084f2e3 ---- /dev/null -+++ b/jslib/angular/src/scss/bwicons/fonts/bwi-font.svg -@@ -0,0 +1,169 @@ -+ -+ -+ -+ -+ -+ -+{ -+ "fontFamily": "bwi-font", -+ "designer": "Bitwarden, Inc.", -+ "designerURL": "", -+ "description": "Font generated by IcoMoon.", -+ "copyright": "Bitwarden, Inc.", -+ "majorVersion": 1, -+ "minorVersion": 0, -+ "version": "Version 1.0", -+ "fontId": "bwi-font", -+ "psName": "bwi-font", -+ "subFamily": "Regular", -+ "fullName": "bwi-font" -+} -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -\ No newline at end of file -diff --git a/jslib/angular/src/scss/bwicons/fonts/bwi-font.ttf b/jslib/angular/src/scss/bwicons/fonts/bwi-font.ttf -new file mode 100644 -index 00000000..900da5ca -Binary files /dev/null and b/jslib/angular/src/scss/bwicons/fonts/bwi-font.ttf differ -diff --git a/jslib/angular/src/scss/bwicons/fonts/bwi-font.woff b/jslib/angular/src/scss/bwicons/fonts/bwi-font.woff -new file mode 100644 -index 00000000..8cde2bac -Binary files /dev/null and b/jslib/angular/src/scss/bwicons/fonts/bwi-font.woff differ -diff --git a/jslib/angular/src/scss/bwicons/fonts/bwi-font.woff2 b/jslib/angular/src/scss/bwicons/fonts/bwi-font.woff2 -new file mode 100644 -index 00000000..898cf2c3 -Binary files /dev/null and b/jslib/angular/src/scss/bwicons/fonts/bwi-font.woff2 differ -diff --git a/jslib/angular/src/scss/bwicons/styles/style.scss b/jslib/angular/src/scss/bwicons/styles/style.scss -new file mode 100644 -index 00000000..24504d88 ---- /dev/null -+++ b/jslib/angular/src/scss/bwicons/styles/style.scss -@@ -0,0 +1,255 @@ -+$icomoon-font-family: "bwi-font" !default; -+$icomoon-font-path: "~@bitwarden/jslib-angular/src/scss/bwicons/fonts/" !default; -+ -+// New font sheet? Update the font-face information below -+@font-face { -+ font-family: "#{$icomoon-font-family}"; -+ src: url($icomoon-font-path + "bwi-font.svg") format("svg"), -+ url($icomoon-font-path + "bwi-font.ttf") format("truetype"), -+ url($icomoon-font-path + "bwi-font.woff") format("woff"), -+ url($icomoon-font-path + "bwi-font.woff2") format("woff2"); -+ font-weight: normal; -+ font-style: normal; -+ font-display: block; -+} -+ -+// Base Class -+.bwi { -+ /* use !important to prevent issues with browser extensions that change fonts */ -+ font-family: "#{$icomoon-font-family}" !important; -+ speak: never; -+ font-style: normal; -+ font-weight: normal; -+ font-variant: normal; -+ text-transform: none; -+ line-height: 1; -+ display: inline-block; -+ /* Better Font Rendering */ -+ -webkit-font-smoothing: antialiased; -+ -moz-osx-font-smoothing: grayscale; -+} -+ -+// Fixed Width Icons -+.bwi-fw { -+ width: (18em / 14); -+ text-align: center; -+} -+ -+// Sizing Changes -+.bwi-sm { -+ font-size: 0.875em; -+} -+ -+.bwi-lg { -+ font-size: (4em / 3); -+ line-height: (3em / 4); -+ vertical-align: -15%; -+} -+ -+.bwi-2x { -+ font-size: 2em; -+} -+ -+.bwi-3x { -+ font-size: 3em; -+} -+ -+.bwi-4x { -+ font-size: 4em; -+} -+ -+// Spin Animations -+.bwi-spin { -+ animation: bwi-spin 2s infinite linear; -+} -+ -+@keyframes bwi-spin { -+ 0% { -+ transform: rotate(0deg); -+ } -+ 100% { -+ transform: rotate(359deg); -+ } -+} -+ -+// List Icons -+.bwi-ul { -+ padding-left: 0; -+ margin-left: (30em / 14); -+ list-style-type: none; -+ > li { -+ position: relative; -+ } -+} -+ -+.bwi-li { -+ position: absolute; -+ left: -(30em / 14); -+ width: (30em / 14); -+ top: (2em / 14); -+ text-align: center; -+ &.bwi-lg { -+ left: -(30em / 14) + (4em / 14); -+ } -+} -+ -+// Rotation -+.bwi-rotate-270 { -+ transform: rotate(270deg); -+} -+ -+// For new icons - add their glyph name and value to the map below -+$icons: ( -+ "save-changes": "\e988", -+ "browser": "\e985", -+ "mobile": "\e986", -+ "cli": "\e987", -+ "providers": "\e983", -+ "vault": "\e984", -+ "folder-closed-f": "\e982", -+ "rocket": "\e9ee", -+ "ellipsis-h": "\e9ef", -+ "ellipsis-v": "\e9f0", -+ "safari": "\e974", -+ "opera": "\e975", -+ "firefox": "\e976", -+ "edge": "\e977", -+ "chrome": "\e978", -+ "star-f": "\e979", -+ "arrow-circle-up": "\e97a", -+ "arrow-circle-right": "\e97b", -+ "arrow-circle-left": "\e97c", -+ "arrow-circle-down": "\e97d", -+ "undo": "\e97e", -+ "bolt": "\e97f", -+ "puzzle": "\e980", -+ "rss": "\e973", -+ "dbl-angle-left": "\e970", -+ "dbl-angle-right": "\e971", -+ "hamburger": "\e972", -+ "bw-folder-open-f": "\e93e", -+ "desktop": "\e96a", -+ "angle-left": "\e96b", -+ "user": "\e900", -+ "user-f": "\e901", -+ "key": "\e902", -+ "share-square": "\e903", -+ "hashtag": "\e904", -+ "clone": "\e905", -+ "list-alt": "\e906", -+ "id-card": "\e907", -+ "credit-card": "\e908", -+ "globe": "\e909", -+ "sticky-note": "\e90a", -+ "folder": "\e90b", -+ "lock": "\e90c", -+ "lock-f": "\e90d", -+ "generate": "\e90e", -+ "generate-f": "\e90f", -+ "cog": "\e910", -+ "cog-f": "\e911", -+ "check-circle": "\e912", -+ "eye": "\e913", -+ "pencil-square": "\e914", -+ "bookmark": "\e915", -+ "files": "\e916", -+ "trash": "\e917", -+ "plus": "\e918", -+ "star": "\e919", -+ "list": "\e91a", -+ "angle-right": "\e91b", -+ "external-link": "\e91c", -+ "refresh": "\e91d", -+ "search": "\e91f", -+ "filter": "\e920", -+ "plus-circle": "\e921", -+ "user-circle": "\e922", -+ "question-circle": "\e923", -+ "cogs": "\e924", -+ "minus-circle": "\e925", -+ "send": "\e926", -+ "send-f": "\e927", -+ "download": "\e928", -+ "pencil": "\e929", -+ "sign-out": "\e92a", -+ "share": "\e92b", -+ "clock": "\e92c", -+ "angle-down": "\e92d", -+ "caret-down": "\e92e", -+ "square": "\e92f", -+ "collection": "\e930", -+ "bank": "\e931", -+ "shield": "\e932", -+ "stop": "\e933", -+ "plus-square": "\e934", -+ "save": "\e935", -+ "sign-in": "\e936", -+ "spinner": "\e937", -+ "dollar": "\e939", -+ "check": "\e93a", -+ "check-square": "\e93b", -+ "minus-square": "\e93c", -+ "close": "\e93d", -+ "share-arrow": "\e96c", -+ "paperclip": "\e93f", -+ "bitcoin": "\e940", -+ "cut": "\e941", -+ "frown": "\e942", -+ "folder-open": "\e943", -+ "bug": "\e946", -+ "chain-broken": "\e947", -+ "dashboard": "\e948", -+ "envelope": "\e949", -+ "exclamation-circle": "\e94a", -+ "exclamation-triangle": "\e94b", -+ "caret-right": "\e94c", -+ "file-pdf": "\e94e", -+ "file-text": "\e94f", -+ "info-circle": "\e952", -+ "lightbulb": "\e953", -+ "link": "\e954", -+ "linux": "\e956", -+ "long-arrow-right": "\e957", -+ "money": "\e958", -+ "play": "\e959", -+ "reddit": "\e95a", -+ "refresh-tab": "\e95b", -+ "sitemap": "\e95c", -+ "sliders": "\e95d", -+ "tag": "\e95e", -+ "thumb-tack": "\e95f", -+ "thumbs-up": "\e960", -+ "unlock": "\e962", -+ "users": "\e963", -+ "wrench": "\e965", -+ "ban": "\e967", -+ "camera": "\e968", -+ "chevron-up": "\e969", -+ "eye-slash": "\e96d", -+ "file": "\e96e", -+ "paste": "\e96f", -+ "github": "\e950", -+ "facebook": "\e94d", -+ "paypal": "\e938", -+ "google": "\e951", -+ "linkedin": "\e955", -+ "discourse": "\e91e", -+ "twitter": "\e961", -+ "youtube": "\e966", -+ "windows": "\e964", -+ "apple": "\e945", -+ "android": "\e944", -+ "error": "\e981", -+); -+ -+@each $name, $glyph in $icons { -+ .bwi-#{$name}:before { -+ content: $glyph; -+ } -+} -+ -+:export { -+ @each $name, $glyph in $icons { -+ #{$name}: $glyph; -+ } -+} -diff --git a/jslib/angular/src/scss/icons.scss b/jslib/angular/src/scss/icons.scss -new file mode 100644 -index 00000000..4d2ea459 ---- /dev/null -+++ b/jslib/angular/src/scss/icons.scss -@@ -0,0 +1,44 @@ -+$card-icons-base: "~@bitwarden/jslib-angular/src/images/cards/"; -+$card-icons: ( -+ "visa": $card-icons-base + "visa-light.png", -+ "amex": $card-icons-base + "amex-light.png", -+ "diners-club": $card-icons-base + "diners_club-light.png", -+ "discover": $card-icons-base + "discover-light.png", -+ "jcb": $card-icons-base + "jcb-light.png", -+ "maestro": $card-icons-base + "maestro-light.png", -+ "mastercard": $card-icons-base + "mastercard-light.png", -+ "union-pay": $card-icons-base + "union_pay-light.png", -+); -+ -+$card-icons-dark: ( -+ "visa": $card-icons-base + "visa-dark.png", -+ "amex": $card-icons-base + "amex-dark.png", -+ "diners-club": $card-icons-base + "diners_club-dark.png", -+ "discover": $card-icons-base + "discover-dark.png", -+ "jcb": $card-icons-base + "jcb-dark.png", -+ "maestro": $card-icons-base + "maestro-dark.png", -+ "mastercard": $card-icons-base + "mastercard-dark.png", -+ "union-pay": $card-icons-base + "union_pay-dark.png", -+); -+ -+.credit-card-icon { -+ display: block; // Resolves the parent container being slighly to big -+ height: 19px; -+ width: 24px; -+ background-size: contain; -+ background-repeat: no-repeat; -+} -+ -+@each $name, $url in $card-icons { -+ .card-#{$name} { -+ background-image: url("#{$url}"); -+ } -+} -+ -+@each $theme in $dark-icon-themes { -+ @each $name, $url in $card-icons-dark { -+ .#{$theme} .card-#{$name} { -+ background-image: url("#{$url}"); -+ } -+ } -+} -diff --git a/jslib/angular/src/scss/webfonts.css b/jslib/angular/src/scss/webfonts.css -index c8a72d4b..fe9bb7e1 100644 ---- a/jslib/angular/src/scss/webfonts.css -+++ b/jslib/angular/src/scss/webfonts.css -@@ -1,90 +1,89 @@ - @font-face { -- font-family: 'Open Sans'; -- font-style: italic; -- font-weight: 300; -- font-display: auto; -- src: url(webfonts/Open_Sans-italic-300.woff) format('woff'); -- unicode-range: U+0-10FFFF; -+ font-family: "Open Sans"; -+ font-style: italic; -+ font-weight: 300; -+ font-display: auto; -+ src: url(webfonts/Open_Sans-italic-300.woff) format("woff"); -+ unicode-range: U+0-10FFFF; - } - - @font-face { -- font-family: 'Open Sans'; -- font-style: italic; -- font-weight: 400; -- font-display: auto; -- src: url(webfonts/Open_Sans-italic-400.woff) format('woff'); -- unicode-range: U+0-10FFFF; -+ font-family: "Open Sans"; -+ font-style: italic; -+ font-weight: 400; -+ font-display: auto; -+ src: url(webfonts/Open_Sans-italic-400.woff) format("woff"); -+ unicode-range: U+0-10FFFF; - } - - @font-face { -- font-family: 'Open Sans'; -- font-style: italic; -- font-weight: 600; -- font-display: auto; -- src: url(webfonts/Open_Sans-italic-600.woff) format('woff'); -- unicode-range: U+0-10FFFF; -+ font-family: "Open Sans"; -+ font-style: italic; -+ font-weight: 600; -+ font-display: auto; -+ src: url(webfonts/Open_Sans-italic-600.woff) format("woff"); -+ unicode-range: U+0-10FFFF; - } - - @font-face { -- font-family: 'Open Sans'; -- font-style: italic; -- font-weight: 700; -- font-display: auto; -- src: url(webfonts/Open_Sans-italic-700.woff) format('woff'); -- unicode-range: U+0-10FFFF; -+ font-family: "Open Sans"; -+ font-style: italic; -+ font-weight: 700; -+ font-display: auto; -+ src: url(webfonts/Open_Sans-italic-700.woff) format("woff"); -+ unicode-range: U+0-10FFFF; - } - - @font-face { -- font-family: 'Open Sans'; -- font-style: italic; -- font-weight: 800; -- font-display: auto; -- src: url(webfonts/Open_Sans-italic-800.woff) format('woff'); -- unicode-range: U+0-10FFFF; -+ font-family: "Open Sans"; -+ font-style: italic; -+ font-weight: 800; -+ font-display: auto; -+ src: url(webfonts/Open_Sans-italic-800.woff) format("woff"); -+ unicode-range: U+0-10FFFF; - } - - @font-face { -- font-family: 'Open Sans'; -- font-style: normal; -- font-weight: 300; -- font-display: auto; -- src: url(webfonts/Open_Sans-normal-300.woff) format('woff'); -- unicode-range: U+0-10FFFF; -+ font-family: "Open Sans"; -+ font-style: normal; -+ font-weight: 300; -+ font-display: auto; -+ src: url(webfonts/Open_Sans-normal-300.woff) format("woff"); -+ unicode-range: U+0-10FFFF; - } - - @font-face { -- font-family: 'Open Sans'; -- font-style: normal; -- font-weight: 400; -- font-display: auto; -- src: url(webfonts/Open_Sans-normal-400.woff) format('woff'); -- unicode-range: U+0-10FFFF; -+ font-family: "Open Sans"; -+ font-style: normal; -+ font-weight: 400; -+ font-display: auto; -+ src: url(webfonts/Open_Sans-normal-400.woff) format("woff"); -+ unicode-range: U+0-10FFFF; - } - - @font-face { -- font-family: 'Open Sans'; -- font-style: normal; -- font-weight: 600; -- font-display: auto; -- src: url(webfonts/Open_Sans-normal-600.woff) format('woff'); -- unicode-range: U+0-10FFFF; -+ font-family: "Open Sans"; -+ font-style: normal; -+ font-weight: 600; -+ font-display: auto; -+ src: url(webfonts/Open_Sans-normal-600.woff) format("woff"); -+ unicode-range: U+0-10FFFF; - } - - @font-face { -- font-family: 'Open Sans'; -- font-style: normal; -- font-weight: 700; -- font-display: auto; -- src: url(webfonts/Open_Sans-normal-700.woff) format('woff'); -- unicode-range: U+0-10FFFF; -+ font-family: "Open Sans"; -+ font-style: normal; -+ font-weight: 700; -+ font-display: auto; -+ src: url(webfonts/Open_Sans-normal-700.woff) format("woff"); -+ unicode-range: U+0-10FFFF; - } - - @font-face { -- font-family: 'Open Sans'; -- font-style: normal; -- font-weight: 800; -- font-display: auto; -- src: url(webfonts/Open_Sans-normal-800.woff) format('woff'); -- unicode-range: U+0-10FFFF; -+ font-family: "Open Sans"; -+ font-style: normal; -+ font-weight: 800; -+ font-display: auto; -+ src: url(webfonts/Open_Sans-normal-800.woff) format("woff"); -+ unicode-range: U+0-10FFFF; - } -- -diff --git a/jslib/angular/src/services/auth-guard.service.ts b/jslib/angular/src/services/auth-guard.service.ts -index 56561577..8c06e438 100644 ---- a/jslib/angular/src/services/auth-guard.service.ts -+++ b/jslib/angular/src/services/auth-guard.service.ts -@@ -1,42 +1,45 @@ --import { Injectable } from '@angular/core'; --import { -- ActivatedRouteSnapshot, -- CanActivate, -- Router, -- RouterStateSnapshot, --} from '@angular/router'; -+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 { UserService } from 'jslib-common/abstractions/user.service'; --import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; -+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 userService: UserService, -- private router: Router, private messagingService: MessagingService, private keyConnectorService: KeyConnectorService) { } -+ 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.userService.isAuthenticated(); -- 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; -- } -+ async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) { -+ const isAuthed = await this.stateService.getIsAuthenticated(); -+ if (!isAuthed) { -+ this.messagingService.send("authBlocked"); -+ return false; -+ } - -- if (!routerState.url.includes('remove-password') && await this.keyConnectorService.getConvertAccountRequired()) { -- this.router.navigate(['/remove-password']); -- 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; -+ } - -- return true; -+ if ( -+ !routerState.url.includes("remove-password") && -+ (await this.keyConnectorService.getConvertAccountRequired()) -+ ) { -+ this.router.navigate(["/remove-password"]); -+ return false; - } -+ -+ return true; -+ } - } -diff --git a/jslib/angular/src/services/broadcaster.service.ts b/jslib/angular/src/services/broadcaster.service.ts -index 6a9bdf9b..36218405 100644 ---- a/jslib/angular/src/services/broadcaster.service.ts -+++ b/jslib/angular/src/services/broadcaster.service.ts -@@ -1,7 +1,6 @@ --import { Injectable } from '@angular/core'; -+import { Injectable } from "@angular/core"; - --import { BroadcasterService as BaseBroadcasterService } from 'jslib-common/services/broadcaster.service'; -+import { BroadcasterService as BaseBroadcasterService } from "jslib-common/services/broadcaster.service"; - - @Injectable() --export class BroadcasterService extends BaseBroadcasterService { --} -+export class BroadcasterService extends BaseBroadcasterService {} -diff --git a/jslib/angular/src/services/jslib-services.module.ts b/jslib/angular/src/services/jslib-services.module.ts -new file mode 100644 -index 00000000..a08bce24 ---- /dev/null -+++ b/jslib/angular/src/services/jslib-services.module.ts -@@ -0,0 +1,478 @@ -+import { Injector, LOCALE_ID, NgModule } from "@angular/core"; -+ -+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 { UserVerificationService } from "jslib-common/services/userVerification.service"; -+import { VaultTimeoutService } from "jslib-common/services/vaultTimeout.service"; -+import { WebCryptoFunctionService } from "jslib-common/services/webCryptoFunction.service"; -+ -+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 { UserVerificationService as UserVerificationServiceAbstraction } from "jslib-common/abstractions/userVerification.service"; -+import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "jslib-common/abstractions/vaultTimeout.service"; -+ -+import { AuthGuardService } from "./auth-guard.service"; -+import { BroadcasterService } from "./broadcaster.service"; -+import { LockGuardService } from "./lock-guard.service"; -+import { ModalService } from "./modal.service"; -+import { PasswordRepromptService } from "./passwordReprompt.service"; -+import { UnauthGuardService } from "./unauth-guard.service"; -+import { ValidationService } from "./validation.service"; -+ -+import { Account } from "jslib-common/models/domain/account"; -+import { GlobalState } from "jslib-common/models/domain/globalState"; -+ -+import { GlobalStateFactory } from "jslib-common/factories/globalStateFactory"; -+import { StateFactory } from "jslib-common/factories/stateFactory"; -+ -+@NgModule({ -+ declarations: [], -+ providers: [ -+ { provide: "WINDOW", useValue: window }, -+ { -+ provide: LOCALE_ID, -+ useFactory: (i18nService: I18nServiceAbstraction) => i18nService.translationLocale, -+ deps: [I18nServiceAbstraction], -+ }, -+ ValidationService, -+ AuthGuardService, -+ UnauthGuardService, -+ LockGuardService, -+ ModalService, -+ { -+ provide: AppIdServiceAbstraction, -+ useClass: AppIdService, -+ deps: [StorageServiceAbstraction], -+ }, -+ { -+ provide: AuditServiceAbstraction, -+ useClass: AuditService, -+ deps: [CryptoFunctionServiceAbstraction, ApiServiceAbstraction], -+ }, -+ { -+ provide: AuthServiceAbstraction, -+ useClass: AuthService, -+ deps: [ -+ CryptoServiceAbstraction, -+ ApiServiceAbstraction, -+ TokenServiceAbstraction, -+ AppIdServiceAbstraction, -+ I18nServiceAbstraction, -+ PlatformUtilsServiceAbstraction, -+ MessagingServiceAbstraction, -+ VaultTimeoutServiceAbstraction, -+ LogService, -+ CryptoFunctionServiceAbstraction, -+ KeyConnectorServiceAbstraction, -+ EnvironmentServiceAbstraction, -+ StateServiceAbstraction, -+ ], -+ }, -+ { -+ provide: CipherServiceAbstraction, -+ useFactory: ( -+ cryptoService: CryptoServiceAbstraction, -+ settingsService: SettingsServiceAbstraction, -+ apiService: ApiServiceAbstraction, -+ fileUploadService: FileUploadServiceAbstraction, -+ i18nService: I18nServiceAbstraction, -+ injector: Injector, -+ logService: LogService, -+ stateService: StateServiceAbstraction -+ ) => -+ new CipherService( -+ cryptoService, -+ settingsService, -+ apiService, -+ fileUploadService, -+ i18nService, -+ () => injector.get(SearchServiceAbstraction), -+ logService, -+ stateService -+ ), -+ deps: [ -+ CryptoServiceAbstraction, -+ SettingsServiceAbstraction, -+ ApiServiceAbstraction, -+ FileUploadServiceAbstraction, -+ I18nServiceAbstraction, -+ Injector, // TODO: Get rid of this circular dependency! -+ LogService, -+ StateServiceAbstraction, -+ ], -+ }, -+ { -+ provide: FolderServiceAbstraction, -+ useClass: FolderService, -+ deps: [ -+ CryptoServiceAbstraction, -+ ApiServiceAbstraction, -+ I18nServiceAbstraction, -+ CipherServiceAbstraction, -+ StateServiceAbstraction, -+ ], -+ }, -+ { provide: LogService, useFactory: () => new ConsoleLogService(false) }, -+ { -+ provide: CollectionServiceAbstraction, -+ useClass: CollectionService, -+ deps: [CryptoServiceAbstraction, I18nServiceAbstraction, StateServiceAbstraction], -+ }, -+ { -+ provide: EnvironmentServiceAbstraction, -+ useClass: EnvironmentService, -+ deps: [StateServiceAbstraction], -+ }, -+ { -+ provide: TotpServiceAbstraction, -+ useClass: TotpService, -+ deps: [CryptoFunctionServiceAbstraction, LogService, StateServiceAbstraction], -+ }, -+ { provide: TokenServiceAbstraction, useClass: TokenService, deps: [StateServiceAbstraction] }, -+ { -+ provide: CryptoServiceAbstraction, -+ useClass: CryptoService, -+ deps: [ -+ CryptoFunctionServiceAbstraction, -+ PlatformUtilsServiceAbstraction, -+ LogService, -+ StateServiceAbstraction, -+ ], -+ }, -+ { -+ provide: PasswordGenerationServiceAbstraction, -+ useClass: PasswordGenerationService, -+ deps: [CryptoServiceAbstraction, PolicyServiceAbstraction, StateServiceAbstraction], -+ }, -+ { -+ provide: ApiServiceAbstraction, -+ useFactory: ( -+ tokenService: TokenServiceAbstraction, -+ platformUtilsService: PlatformUtilsServiceAbstraction, -+ environmentService: EnvironmentServiceAbstraction, -+ messagingService: MessagingServiceAbstraction -+ ) => -+ new ApiService( -+ tokenService, -+ platformUtilsService, -+ environmentService, -+ async (expired: boolean) => messagingService.send("logout", { expired: expired }) -+ ), -+ deps: [ -+ TokenServiceAbstraction, -+ PlatformUtilsServiceAbstraction, -+ EnvironmentServiceAbstraction, -+ MessagingServiceAbstraction, -+ ], -+ }, -+ { -+ 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 () => messagingService.send("logout", { expired: false }) -+ ), -+ deps: [ -+ CipherServiceAbstraction, -+ FolderServiceAbstraction, -+ CollectionServiceAbstraction, -+ CryptoServiceAbstraction, -+ PlatformUtilsServiceAbstraction, -+ MessagingServiceAbstraction, -+ SearchServiceAbstraction, -+ TokenServiceAbstraction, -+ PolicyServiceAbstraction, -+ KeyConnectorServiceAbstraction, -+ StateServiceAbstraction, -+ ], -+ }, -+ { -+ provide: StateServiceAbstraction, -+ useFactory: ( -+ storageService: StorageServiceAbstraction, -+ secureStorageService: StorageServiceAbstraction, -+ logService: LogService, -+ stateMigrationService: StateMigrationServiceAbstraction -+ ) => -+ new StateService( -+ storageService, -+ secureStorageService, -+ logService, -+ stateMigrationService, -+ new StateFactory(GlobalState, Account) -+ ), -+ deps: [ -+ StorageServiceAbstraction, -+ "SECURE_STORAGE", -+ LogService, -+ StateMigrationServiceAbstraction, -+ ], -+ }, -+ { -+ provide: StateMigrationServiceAbstraction, -+ useFactory: ( -+ storageService: StorageServiceAbstraction, -+ secureStorageService: StorageServiceAbstraction -+ ) => -+ new StateMigrationService( -+ storageService, -+ secureStorageService, -+ new GlobalStateFactory(GlobalState) -+ ), -+ 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", PlatformUtilsServiceAbstraction], -+ }, -+ { -+ 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, -+ ], -+ }, -+ { -+ provide: UserVerificationServiceAbstraction, -+ useClass: UserVerificationService, -+ deps: [CryptoServiceAbstraction, I18nServiceAbstraction, ApiServiceAbstraction], -+ }, -+ { provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService }, -+ { -+ provide: OrganizationServiceAbstraction, -+ useClass: OrganizationService, -+ deps: [StateServiceAbstraction], -+ }, -+ { -+ provide: ProviderServiceAbstraction, -+ useClass: ProviderService, -+ deps: [StateServiceAbstraction], -+ }, -+ ], -+}) -+export class JslibServicesModule {} -diff --git a/jslib/angular/src/services/lock-guard.service.ts b/jslib/angular/src/services/lock-guard.service.ts -index 400eedc5..992c6e5d 100644 ---- a/jslib/angular/src/services/lock-guard.service.ts -+++ b/jslib/angular/src/services/lock-guard.service.ts -@@ -1,32 +1,29 @@ --import { Injectable } from '@angular/core'; --import { -- CanActivate, -- Router, --} from '@angular/router'; -+import { Injectable } from "@angular/core"; -+import { CanActivate, Router } from "@angular/router"; - --import { UserService } from 'jslib-common/abstractions/user.service'; --import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; -+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 -+ ) {} - -- protected homepage = 'vault'; -- constructor(private vaultTimeoutService: VaultTimeoutService, private userService: UserService, -- private router: Router) { } -+ async canActivate() { -+ if (await this.vaultTimeoutService.isLocked()) { -+ return true; -+ } - -- async canActivate() { -- const isAuthed = await this.userService.isAuthenticated(); -- if (isAuthed) { -- const locked = await this.vaultTimeoutService.isLocked(); -- if (locked) { -- return true; -- } else { -- this.router.navigate([this.homepage]); -- return false; -- } -- } -+ const redirectUrl = (await this.stateService.getIsAuthenticated()) -+ ? [this.homepage] -+ : [this.loginpage]; - -- this.router.navigate(['']); -- return false; -- } -+ this.router.navigate(redirectUrl); -+ return false; -+ } - } -diff --git a/jslib/angular/src/services/modal.service.ts b/jslib/angular/src/services/modal.service.ts -index 9674cd1a..f487c579 100644 ---- a/jslib/angular/src/services/modal.service.ts -+++ b/jslib/angular/src/services/modal.service.ts -@@ -1,164 +1,179 @@ - import { -- ApplicationRef, -- ComponentFactory, -- ComponentFactoryResolver, -- ComponentRef, -- EmbeddedViewRef, -- Injectable, -- Injector, -- Type, -- ViewContainerRef --} from '@angular/core'; --import { first } from 'rxjs/operators'; -- --import { DynamicModalComponent } from '../components/modal/dynamic-modal.component'; --import { ModalInjector } from '../components/modal/modal-injector'; --import { ModalRef } from '../components/modal/modal.ref'; -+ ApplicationRef, -+ ComponentFactory, -+ ComponentFactoryResolver, -+ ComponentRef, -+ EmbeddedViewRef, -+ Injectable, -+ Injector, -+ Type, -+ ViewContainerRef, -+} from "@angular/core"; -+import { first } from "rxjs/operators"; -+ -+import { DynamicModalComponent } from "../components/modal/dynamic-modal.component"; -+import { ModalInjector } from "../components/modal/modal-injector"; -+import { ModalRef } from "../components/modal/modal.ref"; - - export class ModalConfig { -- data?: D; -- allowMultipleModals: boolean = false; -+ data?: D; -+ allowMultipleModals: boolean = false; - } - - @Injectable() - export class ModalService { -- protected modalList: ComponentRef[] = []; -- -- // Lazy loaded modules are not available in componentFactoryResolver, -- // therefore modules needs to manually initialize their resolvers. -- private factoryResolvers: Map, ComponentFactoryResolver> = new Map(); -- -- constructor(private componentFactoryResolver: ComponentFactoryResolver, private applicationRef: ApplicationRef, -- private injector: Injector) { -- document.addEventListener('keyup', event => { -- if (event.key === 'Escape' && this.modalCount > 0) { -- this.topModal.instance.close(); -- } -- }); -- } -- -- get modalCount() { -- return this.modalList.length; -+ protected modalList: ComponentRef[] = []; -+ -+ // Lazy loaded modules are not available in componentFactoryResolver, -+ // therefore modules needs to manually initialize their resolvers. -+ private factoryResolvers: Map, ComponentFactoryResolver> = new Map(); -+ -+ constructor( -+ private componentFactoryResolver: ComponentFactoryResolver, -+ private applicationRef: ApplicationRef, -+ private injector: Injector -+ ) { -+ document.addEventListener("keyup", (event) => { -+ if (event.key === "Escape" && this.modalCount > 0) { -+ this.topModal.instance.close(); -+ } -+ }); -+ } -+ -+ get modalCount() { -+ return this.modalList.length; -+ } -+ -+ private get topModal() { -+ return this.modalList[this.modalCount - 1]; -+ } -+ -+ async openViewRef( -+ componentType: Type, -+ viewContainerRef: ViewContainerRef, -+ setComponentParameters: (component: T) => void = null -+ ): Promise<[ModalRef, T]> { -+ const [modalRef, modalComponentRef] = this.openInternal(componentType, null, false); -+ modalComponentRef.instance.setComponentParameters = setComponentParameters; -+ -+ viewContainerRef.insert(modalComponentRef.hostView); -+ -+ await modalRef.onCreated.pipe(first()).toPromise(); -+ -+ return [modalRef, modalComponentRef.instance.componentRef.instance]; -+ } -+ -+ open(componentType: Type, config?: ModalConfig) { -+ if (!(config?.allowMultipleModals ?? false) && this.modalCount > 0) { -+ return; - } - -- private get topModal() { -- return this.modalList[this.modalCount - 1]; -- } -- -- async openViewRef(componentType: Type, viewContainerRef: ViewContainerRef, -- setComponentParameters: (component: T) => void = null): Promise<[ModalRef, T]> { -- -- const [modalRef, modalComponentRef] = this.openInternal(componentType, null, false); -- modalComponentRef.instance.setComponentParameters = setComponentParameters; -- -- viewContainerRef.insert(modalComponentRef.hostView); -+ const [modalRef, _] = this.openInternal(componentType, config, true); - -- await modalRef.onCreated.pipe(first()).toPromise(); -+ return modalRef; -+ } - -- return [modalRef, modalComponentRef.instance.componentRef.instance]; -- } -- -- open(componentType: Type, config?: ModalConfig) { -- if (!(config?.allowMultipleModals ?? false) && this.modalCount > 0) { -- return; -- } -+ registerComponentFactoryResolver( -+ componentType: Type, -+ componentFactoryResolver: ComponentFactoryResolver -+ ): void { -+ this.factoryResolvers.set(componentType, componentFactoryResolver); -+ } - -- const [modalRef, _] = this.openInternal(componentType, config, true); -- -- return modalRef; -+ resolveComponentFactory(componentType: Type): ComponentFactory { -+ if (this.factoryResolvers.has(componentType)) { -+ return this.factoryResolvers.get(componentType).resolveComponentFactory(componentType); - } - -- registerComponentFactoryResolver(componentType: Type, componentFactoryResolver: ComponentFactoryResolver): void { -- this.factoryResolvers.set(componentType, componentFactoryResolver); -+ return this.componentFactoryResolver.resolveComponentFactory(componentType); -+ } -+ -+ protected openInternal( -+ componentType: Type, -+ config?: ModalConfig, -+ attachToDom?: boolean -+ ): [ModalRef, ComponentRef] { -+ const [modalRef, componentRef] = this.createModalComponent(config); -+ componentRef.instance.childComponentType = componentType; -+ -+ if (attachToDom) { -+ this.applicationRef.attachView(componentRef.hostView); -+ const domElem = (componentRef.hostView as EmbeddedViewRef).rootNodes[0] as HTMLElement; -+ document.body.appendChild(domElem); - } - -- resolveComponentFactory(componentType: Type): ComponentFactory { -- if (this.factoryResolvers.has(componentType)) { -- return this.factoryResolvers.get(componentType).resolveComponentFactory(componentType); -- } -+ modalRef.onClosed.pipe(first()).subscribe(() => { -+ if (attachToDom) { -+ this.applicationRef.detachView(componentRef.hostView); -+ } -+ componentRef.destroy(); - -- return this.componentFactoryResolver.resolveComponentFactory(componentType); -- } -+ this.modalList.pop(); -+ if (this.modalCount > 0) { -+ this.topModal.instance.getFocus(); -+ } -+ }); - -- protected openInternal(componentType: Type, config?: ModalConfig, attachToDom?: boolean): -- [ModalRef, ComponentRef] { -+ this.setupHandlers(modalRef); - -- const [modalRef, componentRef] = this.createModalComponent(config); -- componentRef.instance.childComponentType = componentType; -+ this.modalList.push(componentRef); - -- if (attachToDom) { -- this.applicationRef.attachView(componentRef.hostView); -- const domElem = (componentRef.hostView as EmbeddedViewRef).rootNodes[0] as HTMLElement; -- document.body.appendChild(domElem); -- } -+ return [modalRef, componentRef]; -+ } - -- modalRef.onClosed.pipe(first()).subscribe(() => { -- if (attachToDom) { -- this.applicationRef.detachView(componentRef.hostView); -- } -- componentRef.destroy(); -+ protected setupHandlers(modalRef: ModalRef) { -+ let backdrop: HTMLElement = null; - -- this.modalList.pop(); -- if (this.modalCount > 0) { -- this.topModal.instance.getFocus(); -- } -- }); -- -- this.setupHandlers(modalRef); -+ // Add backdrop, setup [data-dismiss] handler. -+ modalRef.onCreated.pipe(first()).subscribe((el) => { -+ document.body.classList.add("modal-open"); - -- this.modalList.push(componentRef); -+ const modalEl: HTMLElement = el.querySelector(".modal"); -+ const dialogEl = modalEl.querySelector(".modal-dialog") as HTMLElement; - -- return [modalRef, componentRef]; -- } -+ backdrop = document.createElement("div"); -+ backdrop.className = "modal-backdrop fade"; -+ backdrop.style.zIndex = `${this.modalCount}040`; -+ modalEl.prepend(backdrop); - -- protected setupHandlers(modalRef: ModalRef) { -- let backdrop: HTMLElement = null; -+ dialogEl.addEventListener("click", (e: Event) => { -+ e.stopPropagation(); -+ }); -+ dialogEl.style.zIndex = `${this.modalCount}050`; - -- // Add backdrop, setup [data-dismiss] handler. -- modalRef.onCreated.pipe(first()).subscribe(el => { -- document.body.classList.add('modal-open'); -- -- const modalEl: HTMLElement = el.querySelector('.modal'); -- const dialogEl = modalEl.querySelector('.modal-dialog') as HTMLElement; -- -- backdrop = document.createElement('div'); -- backdrop.className = 'modal-backdrop fade'; -- backdrop.style.zIndex = `${this.modalCount}040`; -- modalEl.prepend(backdrop); -- -- dialogEl.addEventListener('click', (e: Event) => { -- e.stopPropagation(); -- }); -- dialogEl.style.zIndex = `${this.modalCount}050`; -- -- const modals = Array.from(el.querySelectorAll('.modal-backdrop, .modal *[data-dismiss="modal"]')); -- for (const closeElement of modals) { -- closeElement.addEventListener('click', event => { -- modalRef.close(); -- }); -- } -- }); -- -- // onClose is used in Web to hook into bootstrap. On other projects we pipe it directly to closed. -- modalRef.onClose.pipe(first()).subscribe(() => { -- modalRef.closed(); -- -- if (this.modalCount === 0) { -- document.body.classList.remove('modal-open'); -- } -+ const modals = Array.from( -+ el.querySelectorAll('.modal-backdrop, .modal *[data-dismiss="modal"]') -+ ); -+ for (const closeElement of modals) { -+ closeElement.addEventListener("click", (event) => { -+ modalRef.close(); - }); -- } -- -- protected createModalComponent(config: ModalConfig): [ModalRef, ComponentRef] { -- const modalRef = new ModalRef(); -- -- const map = new WeakMap(); -- map.set(ModalConfig, config); -- map.set(ModalRef, modalRef); -- -- const componentFactory = this.componentFactoryResolver.resolveComponentFactory(DynamicModalComponent); -- const componentRef = componentFactory.create(new ModalInjector(this.injector, map)); -- -- return [modalRef, componentRef]; -- } -+ } -+ }); -+ -+ // onClose is used in Web to hook into bootstrap. On other projects we pipe it directly to closed. -+ modalRef.onClose.pipe(first()).subscribe(() => { -+ modalRef.closed(); -+ -+ if (this.modalCount === 0) { -+ document.body.classList.remove("modal-open"); -+ } -+ }); -+ } -+ -+ protected createModalComponent( -+ config: ModalConfig -+ ): [ModalRef, ComponentRef] { -+ const modalRef = new ModalRef(); -+ -+ const map = new WeakMap(); -+ map.set(ModalConfig, config); -+ map.set(ModalRef, modalRef); -+ -+ const componentFactory = -+ this.componentFactoryResolver.resolveComponentFactory(DynamicModalComponent); -+ const componentRef = componentFactory.create(new ModalInjector(this.injector, map)); -+ -+ return [modalRef, componentRef]; -+ } - } -diff --git a/jslib/angular/src/services/passwordReprompt.service.ts b/jslib/angular/src/services/passwordReprompt.service.ts -index 03e04531..58ae5190 100644 ---- a/jslib/angular/src/services/passwordReprompt.service.ts -+++ b/jslib/angular/src/services/passwordReprompt.service.ts -@@ -1,37 +1,40 @@ --import { Injectable } from '@angular/core'; -+import { Injectable } from "@angular/core"; - --import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; --import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from 'jslib-common/abstractions/passwordReprompt.service'; -+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'; -+import { PasswordRepromptComponent } from "../components/password-reprompt.component"; -+import { ModalService } from "./modal.service"; - - @Injectable() - export class PasswordRepromptService implements PasswordRepromptServiceAbstraction { -- protected component = PasswordRepromptComponent; -+ protected component = PasswordRepromptComponent; - -- constructor(private modalService: ModalService, private keyConnectorService: KeyConnectorService) { } -+ 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; -- } -+ protectedFields() { -+ return ["TOTP", "Password", "H_Field", "Card Number", "Security Code"]; -+ } - -- const ref = this.modalService.open(this.component, {allowMultipleModals: true}); -+ async showPasswordPrompt() { -+ if (!(await this.enabled())) { -+ return true; -+ } - -- if (ref == null) { -- return false; -- } -+ const ref = this.modalService.open(this.component, { allowMultipleModals: true }); - -- const result = await ref.onClosedPromise(); -- return result === true; -+ if (ref == null) { -+ return false; - } - -- async enabled() { -- return !await this.keyConnectorService.getUsesKeyConnector(); -- } -+ const result = await ref.onClosedPromise(); -+ return result === true; -+ } -+ -+ async enabled() { -+ return !(await this.keyConnectorService.getUsesKeyConnector()); -+ } - } -diff --git a/jslib/angular/src/services/unauth-guard.service.ts b/jslib/angular/src/services/unauth-guard.service.ts -index 786241b8..ee2da045 100644 ---- a/jslib/angular/src/services/unauth-guard.service.ts -+++ b/jslib/angular/src/services/unauth-guard.service.ts -@@ -1,32 +1,29 @@ --import { Injectable } from '@angular/core'; --import { -- ActivatedRouteSnapshot, -- CanActivate, -- Router, --} from '@angular/router'; -+import { Injectable } from "@angular/core"; -+import { CanActivate, Router } from "@angular/router"; - --import { UserService } from 'jslib-common/abstractions/user.service'; --import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; -+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 -+ ) {} - -- protected homepage = 'vault'; -- constructor(private vaultTimeoutService: VaultTimeoutService, private userService: UserService, -- private router: Router) { } -- -- async canActivate() { -- const isAuthed = await this.userService.isAuthenticated(); -- if (isAuthed) { -- const locked = await this.vaultTimeoutService.isLocked(); -- if (locked) { -- this.router.navigate(['lock']); -- } else { -- this.router.navigate([this.homepage]); -- } -- return false; -- } -- -- return true; -+ 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; -+ } - } -diff --git a/jslib/angular/src/services/validation.service.ts b/jslib/angular/src/services/validation.service.ts -index 1fa38119..9cb04b38 100644 ---- a/jslib/angular/src/services/validation.service.ts -+++ b/jslib/angular/src/services/validation.service.ts -@@ -1,36 +1,39 @@ --import { Injectable } from '@angular/core'; -+import { Injectable } from "@angular/core"; - --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -+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 { ErrorResponse } from "jslib-common/models/response/errorResponse"; - - @Injectable() - export class ValidationService { -- constructor(private i18nService: I18nService, private platformUtilsService: PlatformUtilsService) { } -+ constructor( -+ private i18nService: I18nService, -+ private platformUtilsService: PlatformUtilsService -+ ) {} - -- showError(data: any): string[] { -- const defaultErrorMessage = this.i18nService.t('unexpectedError'); -- let errors: string[] = []; -+ showError(data: any): string[] { -+ const defaultErrorMessage = this.i18nService.t("unexpectedError"); -+ let errors: string[] = []; - -- if (data != null && typeof data === 'string') { -- errors.push(data); -- } else if (data == null || typeof data !== 'object') { -- errors.push(defaultErrorMessage); -- } else if (data.validationErrors != null) { -- errors = errors.concat((data as ErrorResponse).getAllMessages()); -- } else { -- errors.push(data.message ? data.message : defaultErrorMessage); -- } -- -- if (errors.length === 1) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), errors[0]); -- } else if (errors.length > 1) { -- this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), errors, { -- timeout: 5000 * errors.length, -- }); -- } -+ if (data != null && typeof data === "string") { -+ errors.push(data); -+ } else if (data == null || typeof data !== "object") { -+ errors.push(defaultErrorMessage); -+ } else if (data.validationErrors != null) { -+ errors = errors.concat((data as ErrorResponse).getAllMessages()); -+ } else { -+ errors.push(data.message ? data.message : defaultErrorMessage); -+ } - -- return errors; -+ if (errors.length === 1) { -+ this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), errors[0]); -+ } else if (errors.length > 1) { -+ this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), errors, { -+ timeout: 5000 * errors.length, -+ }); - } -+ -+ return errors; -+ } - } -diff --git a/jslib/angular/tsconfig.json b/jslib/angular/tsconfig.json -index 3d41e86c..07c6a21d 100644 ---- a/jslib/angular/tsconfig.json -+++ b/jslib/angular/tsconfig.json -@@ -1,30 +1,10 @@ - { -+ "extends": "../shared/tsconfig", - "compilerOptions": { -- "pretty": true, -- "moduleResolution": "node", -- "noImplicitAny": true, -- "target": "ES6", -- "module": "commonjs", -- "lib": ["es5", "es6", "es7", "dom"], -- "sourceMap": true, -- "declaration": true, -- "allowSyntheticDefaultImports": true, -- "experimentalDecorators": true, -- "emitDecoratorMetadata": true, -- "declarationDir": "dist/types", -- "outDir": "dist", - "paths": { -- "jslib-common/*": [ -- "../common/src/*" -- ] -+ "jslib-common/*": ["../common/src/*"] - } - }, -- "include": [ -- "src", -- "spec" -- ], -- "exclude": [ -- "node_modules", -- "dist" -- ] -+ "include": ["src", "spec"], -+ "exclude": ["node_modules", "dist"] - } -diff --git a/jslib/common/package-lock.json b/jslib/common/package-lock.json -index f19395f3..b97b5406 100644 ---- a/jslib/common/package-lock.json -+++ b/jslib/common/package-lock.json -@@ -16,19 +16,19 @@ - "lunr": "^2.3.9", - "node-forge": "^0.10.0", - "papaparse": "^5.3.0", -- "rxjs": "6.6.7", -+ "rxjs": "^7.4.0", - "tldjs": "^2.3.1", - "zxcvbn": "^4.4.2" - }, - "devDependencies": { - "@types/lunr": "^2.3.3", -- "@types/node": "^14.17.1", -+ "@types/node": "^16.11.12", - "@types/node-forge": "^0.9.7", - "@types/papaparse": "^5.2.5", - "@types/tldjs": "^2.3.0", - "@types/zxcvbn": "^4.4.1", - "rimraf": "^3.0.2", -- "typescript": "4.1.5" -+ "typescript": "4.3.5" - } - }, - "node_modules/@microsoft/signalr": { -@@ -59,9 +59,9 @@ - "dev": true - }, - "node_modules/@types/node": { -- "version": "14.17.19", -- "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.19.tgz", -- "integrity": "sha512-jjYI6NkyfXykucU6ELEoT64QyKOdvaA6enOqKtP4xUsGY0X0ZUZz29fUmrTRo+7v7c6TgDu82q3GHHaCEkqZwA==", -+ "version": "16.11.12", -+ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", -+ "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==", - "dev": true - }, - "node_modules/@types/node-forge": { -@@ -74,9 +74,9 @@ - } - }, - "node_modules/@types/papaparse": { -- "version": "5.2.6", -- "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.2.6.tgz", -- "integrity": "sha512-xGKSd0UTn58N1h0+zf8mW863Rv8BvXcGibEgKFtBIXZlcDXAmX/T4RdDO2mwmrmOypUDt5vRgo2v32a78JdqUA==", -+ "version": "5.3.1", -+ "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.1.tgz", -+ "integrity": "sha512-1lbngk9wty2kCyQB42LjqSa12SEop3t9wcEC7/xYr3ujTSTmv7HWKjKYXly0GkMfQ42PRb2lFPFEibDOiMXS0g==", - "dev": true, - "dependencies": { - "@types/node": "*" -@@ -267,9 +267,9 @@ - } - }, - "node_modules/node-fetch": { -- "version": "2.6.5", -- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", -- "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", -+ "version": "2.6.6", -+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", -+ "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, -@@ -376,14 +376,11 @@ - } - }, - "node_modules/rxjs": { -- "version": "6.6.7", -- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", -- "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", -+ "version": "7.4.0", -+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", -+ "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", - "dependencies": { -- "tslib": "^1.9.0" -- }, -- "engines": { -- "npm": ">=2.0.0" -+ "tslib": "~2.1.0" - } - }, - "node_modules/safe-buffer": { -@@ -456,14 +453,14 @@ - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" - }, - "node_modules/tslib": { -- "version": "1.14.1", -- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", -- "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" -+ "version": "2.1.0", -+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", -+ "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" - }, - "node_modules/typescript": { -- "version": "4.1.5", -- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz", -- "integrity": "sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==", -+ "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", -@@ -550,9 +547,9 @@ - "dev": true - }, - "@types/node": { -- "version": "14.17.19", -- "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.19.tgz", -- "integrity": "sha512-jjYI6NkyfXykucU6ELEoT64QyKOdvaA6enOqKtP4xUsGY0X0ZUZz29fUmrTRo+7v7c6TgDu82q3GHHaCEkqZwA==", -+ "version": "16.11.12", -+ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", -+ "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==", - "dev": true - }, - "@types/node-forge": { -@@ -565,9 +562,9 @@ - } - }, - "@types/papaparse": { -- "version": "5.2.6", -- "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.2.6.tgz", -- "integrity": "sha512-xGKSd0UTn58N1h0+zf8mW863Rv8BvXcGibEgKFtBIXZlcDXAmX/T4RdDO2mwmrmOypUDt5vRgo2v32a78JdqUA==", -+ "version": "5.3.1", -+ "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.1.tgz", -+ "integrity": "sha512-1lbngk9wty2kCyQB42LjqSa12SEop3t9wcEC7/xYr3ujTSTmv7HWKjKYXly0GkMfQ42PRb2lFPFEibDOiMXS0g==", - "dev": true, - "requires": { - "@types/node": "*" -@@ -737,9 +734,9 @@ - } - }, - "node-fetch": { -- "version": "2.6.5", -- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", -- "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", -+ "version": "2.6.6", -+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", -+ "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", - "requires": { - "whatwg-url": "^5.0.0" - } -@@ -833,11 +830,11 @@ - } - }, - "rxjs": { -- "version": "6.6.7", -- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", -- "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", -+ "version": "7.4.0", -+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", -+ "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", - "requires": { -- "tslib": "^1.9.0" -+ "tslib": "~2.1.0" - } - }, - "safe-buffer": { -@@ -890,14 +887,14 @@ - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" - }, - "tslib": { -- "version": "1.14.1", -- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", -- "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" -+ "version": "2.1.0", -+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", -+ "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" - }, - "typescript": { -- "version": "4.1.5", -- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz", -- "integrity": "sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==", -+ "version": "4.3.5", -+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", -+ "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", - "dev": true - }, - "url-parse": { -diff --git a/jslib/common/package.json b/jslib/common/package.json -index 3e1e8044..95b2f817 100644 ---- a/jslib/common/package.json -+++ b/jslib/common/package.json -@@ -21,13 +21,13 @@ - }, - "devDependencies": { - "@types/lunr": "^2.3.3", -- "@types/node": "^14.17.1", -+ "@types/node": "^16.11.12", - "@types/node-forge": "^0.9.7", - "@types/papaparse": "^5.2.5", - "@types/tldjs": "^2.3.0", - "@types/zxcvbn": "^4.4.1", - "rimraf": "^3.0.2", -- "typescript": "4.1.5" -+ "typescript": "4.3.5" - }, - "dependencies": { - "@microsoft/signalr": "5.0.10", -@@ -37,7 +37,7 @@ - "lunr": "^2.3.9", - "node-forge": "^0.10.0", - "papaparse": "^5.3.0", -- "rxjs": "6.6.7", -+ "rxjs": "^7.4.0", - "tldjs": "^2.3.1", - "zxcvbn": "^4.4.2" - } -diff --git a/jslib/common/src/abstractions/api.service.ts b/jslib/common/src/abstractions/api.service.ts -index 1c6aa0ef..f0b157bc 100644 ---- a/jslib/common/src/abstractions/api.service.ts -+++ b/jslib/common/src/abstractions/api.service.ts -@@ -1,468 +1,685 @@ --import { PolicyType } from '../enums/policyType'; --import { SetKeyConnectorKeyRequest } from '../models/request/account/setKeyConnectorKeyRequest'; --import { VerifyOTPRequest } from '../models/request/account/verifyOTPRequest'; -+import { PolicyType } from "../enums/policyType"; -+import { SetKeyConnectorKeyRequest } from "../models/request/account/setKeyConnectorKeyRequest"; -+import { VerifyOTPRequest } from "../models/request/account/verifyOTPRequest"; - --import { AttachmentRequest } from '../models/request/attachmentRequest'; -+import { AttachmentRequest } from "../models/request/attachmentRequest"; - --import { BitPayInvoiceRequest } from '../models/request/bitPayInvoiceRequest'; --import { CipherBulkDeleteRequest } from '../models/request/cipherBulkDeleteRequest'; --import { CipherBulkMoveRequest } from '../models/request/cipherBulkMoveRequest'; --import { CipherBulkRestoreRequest } from '../models/request/cipherBulkRestoreRequest'; --import { CipherBulkShareRequest } from '../models/request/cipherBulkShareRequest'; --import { CipherCollectionsRequest } from '../models/request/cipherCollectionsRequest'; --import { CipherCreateRequest } from '../models/request/cipherCreateRequest'; --import { CipherRequest } from '../models/request/cipherRequest'; --import { CipherShareRequest } from '../models/request/cipherShareRequest'; --import { CollectionRequest } from '../models/request/collectionRequest'; --import { DeleteRecoverRequest } from '../models/request/deleteRecoverRequest'; --import { EmailRequest } from '../models/request/emailRequest'; --import { EmailTokenRequest } from '../models/request/emailTokenRequest'; --import { EmergencyAccessAcceptRequest } from '../models/request/emergencyAccessAcceptRequest'; --import { EmergencyAccessConfirmRequest } from '../models/request/emergencyAccessConfirmRequest'; --import { EmergencyAccessInviteRequest } from '../models/request/emergencyAccessInviteRequest'; --import { EmergencyAccessPasswordRequest } from '../models/request/emergencyAccessPasswordRequest'; --import { EmergencyAccessUpdateRequest } from '../models/request/emergencyAccessUpdateRequest'; --import { EventRequest } from '../models/request/eventRequest'; --import { FolderRequest } from '../models/request/folderRequest'; --import { GroupRequest } from '../models/request/groupRequest'; --import { IapCheckRequest } from '../models/request/iapCheckRequest'; --import { ImportCiphersRequest } from '../models/request/importCiphersRequest'; --import { ImportDirectoryRequest } from '../models/request/importDirectoryRequest'; --import { ImportOrganizationCiphersRequest } from '../models/request/importOrganizationCiphersRequest'; --import { KdfRequest } from '../models/request/kdfRequest'; --import { KeyConnectorUserKeyRequest } from '../models/request/keyConnectorUserKeyRequest'; --import { KeysRequest } from '../models/request/keysRequest'; --import { OrganizationSponsorshipCreateRequest } from '../models/request/organization/organizationSponsorshipCreateRequest'; --import { OrganizationSponsorshipRedeemRequest } from '../models/request/organization/organizationSponsorshipRedeemRequest'; --import { OrganizationSsoRequest } from '../models/request/organization/organizationSsoRequest'; --import { OrganizationCreateRequest } from '../models/request/organizationCreateRequest'; --import { OrganizationImportRequest } from '../models/request/organizationImportRequest'; --import { OrganizationKeysRequest } from '../models/request/organizationKeysRequest'; --import { OrganizationSubscriptionUpdateRequest } from '../models/request/organizationSubscriptionUpdateRequest'; --import { OrganizationTaxInfoUpdateRequest } from '../models/request/organizationTaxInfoUpdateRequest'; --import { OrganizationUpdateRequest } from '../models/request/organizationUpdateRequest'; --import { OrganizationUpgradeRequest } from '../models/request/organizationUpgradeRequest'; --import { OrganizationUserAcceptRequest } from '../models/request/organizationUserAcceptRequest'; --import { OrganizationUserBulkConfirmRequest } from '../models/request/organizationUserBulkConfirmRequest'; --import { OrganizationUserBulkRequest } from '../models/request/organizationUserBulkRequest'; --import { OrganizationUserConfirmRequest } from '../models/request/organizationUserConfirmRequest'; --import { OrganizationUserInviteRequest } from '../models/request/organizationUserInviteRequest'; --import { OrganizationUserResetPasswordEnrollmentRequest } from '../models/request/organizationUserResetPasswordEnrollmentRequest'; --import { OrganizationUserResetPasswordRequest } from '../models/request/organizationUserResetPasswordRequest'; --import { OrganizationUserUpdateGroupsRequest } from '../models/request/organizationUserUpdateGroupsRequest'; --import { OrganizationUserUpdateRequest } from '../models/request/organizationUserUpdateRequest'; --import { PasswordHintRequest } from '../models/request/passwordHintRequest'; --import { PasswordRequest } from '../models/request/passwordRequest'; --import { PaymentRequest } from '../models/request/paymentRequest'; --import { PolicyRequest } from '../models/request/policyRequest'; --import { PreloginRequest } from '../models/request/preloginRequest'; --import { ProviderAddOrganizationRequest } from '../models/request/provider/providerAddOrganizationRequest'; --import { ProviderOrganizationCreateRequest } from '../models/request/provider/providerOrganizationCreateRequest'; --import { ProviderSetupRequest } from '../models/request/provider/providerSetupRequest'; --import { ProviderUpdateRequest } from '../models/request/provider/providerUpdateRequest'; --import { ProviderUserAcceptRequest } from '../models/request/provider/providerUserAcceptRequest'; --import { ProviderUserBulkConfirmRequest } from '../models/request/provider/providerUserBulkConfirmRequest'; --import { ProviderUserBulkRequest } from '../models/request/provider/providerUserBulkRequest'; --import { ProviderUserConfirmRequest } from '../models/request/provider/providerUserConfirmRequest'; --import { ProviderUserInviteRequest } from '../models/request/provider/providerUserInviteRequest'; --import { ProviderUserUpdateRequest } from '../models/request/provider/providerUserUpdateRequest'; --import { RegisterRequest } from '../models/request/registerRequest'; --import { SeatRequest } from '../models/request/seatRequest'; --import { SecretVerificationRequest } from '../models/request/secretVerificationRequest'; --import { SelectionReadOnlyRequest } from '../models/request/selectionReadOnlyRequest'; --import { SendAccessRequest } from '../models/request/sendAccessRequest'; --import { SendRequest } from '../models/request/sendRequest'; --import { SetPasswordRequest } from '../models/request/setPasswordRequest'; --import { StorageRequest } from '../models/request/storageRequest'; --import { TaxInfoUpdateRequest } from '../models/request/taxInfoUpdateRequest'; --import { TokenRequest } from '../models/request/tokenRequest'; --import { TwoFactorEmailRequest } from '../models/request/twoFactorEmailRequest'; --import { TwoFactorProviderRequest } from '../models/request/twoFactorProviderRequest'; --import { TwoFactorRecoveryRequest } from '../models/request/twoFactorRecoveryRequest'; --import { UpdateDomainsRequest } from '../models/request/updateDomainsRequest'; --import { UpdateKeyRequest } from '../models/request/updateKeyRequest'; --import { UpdateProfileRequest } from '../models/request/updateProfileRequest'; --import { UpdateTempPasswordRequest } from '../models/request/updateTempPasswordRequest'; --import { UpdateTwoFactorAuthenticatorRequest } from '../models/request/updateTwoFactorAuthenticatorRequest'; --import { UpdateTwoFactorDuoRequest } from '../models/request/updateTwoFactorDuoRequest'; --import { UpdateTwoFactorEmailRequest } from '../models/request/updateTwoFactorEmailRequest'; --import { UpdateTwoFactorWebAuthnDeleteRequest } from '../models/request/updateTwoFactorWebAuthnDeleteRequest'; --import { UpdateTwoFactorWebAuthnRequest } from '../models/request/updateTwoFactorWebAuthnRequest'; --import { UpdateTwoFactorYubioOtpRequest } from '../models/request/updateTwoFactorYubioOtpRequest'; --import { VerifyBankRequest } from '../models/request/verifyBankRequest'; --import { VerifyDeleteRecoverRequest } from '../models/request/verifyDeleteRecoverRequest'; --import { VerifyEmailRequest } from '../models/request/verifyEmailRequest'; -+import { BitPayInvoiceRequest } from "../models/request/bitPayInvoiceRequest"; -+import { CipherBulkDeleteRequest } from "../models/request/cipherBulkDeleteRequest"; -+import { CipherBulkMoveRequest } from "../models/request/cipherBulkMoveRequest"; -+import { CipherBulkRestoreRequest } from "../models/request/cipherBulkRestoreRequest"; -+import { CipherBulkShareRequest } from "../models/request/cipherBulkShareRequest"; -+import { CipherCollectionsRequest } from "../models/request/cipherCollectionsRequest"; -+import { CipherCreateRequest } from "../models/request/cipherCreateRequest"; -+import { CipherRequest } from "../models/request/cipherRequest"; -+import { CipherShareRequest } from "../models/request/cipherShareRequest"; -+import { CollectionRequest } from "../models/request/collectionRequest"; -+import { DeleteRecoverRequest } from "../models/request/deleteRecoverRequest"; -+import { EmailRequest } from "../models/request/emailRequest"; -+import { EmailTokenRequest } from "../models/request/emailTokenRequest"; -+import { EmergencyAccessAcceptRequest } from "../models/request/emergencyAccessAcceptRequest"; -+import { EmergencyAccessConfirmRequest } from "../models/request/emergencyAccessConfirmRequest"; -+import { EmergencyAccessInviteRequest } from "../models/request/emergencyAccessInviteRequest"; -+import { EmergencyAccessPasswordRequest } from "../models/request/emergencyAccessPasswordRequest"; -+import { EmergencyAccessUpdateRequest } from "../models/request/emergencyAccessUpdateRequest"; -+import { EventRequest } from "../models/request/eventRequest"; -+import { FolderRequest } from "../models/request/folderRequest"; -+import { GroupRequest } from "../models/request/groupRequest"; -+import { IapCheckRequest } from "../models/request/iapCheckRequest"; -+import { ImportCiphersRequest } from "../models/request/importCiphersRequest"; -+import { ImportDirectoryRequest } from "../models/request/importDirectoryRequest"; -+import { ImportOrganizationCiphersRequest } from "../models/request/importOrganizationCiphersRequest"; -+import { KdfRequest } from "../models/request/kdfRequest"; -+import { KeyConnectorUserKeyRequest } from "../models/request/keyConnectorUserKeyRequest"; -+import { KeysRequest } from "../models/request/keysRequest"; -+import { OrganizationSponsorshipCreateRequest } from "../models/request/organization/organizationSponsorshipCreateRequest"; -+import { OrganizationSponsorshipRedeemRequest } from "../models/request/organization/organizationSponsorshipRedeemRequest"; -+import { OrganizationSsoRequest } from "../models/request/organization/organizationSsoRequest"; -+import { OrganizationCreateRequest } from "../models/request/organizationCreateRequest"; -+import { OrganizationImportRequest } from "../models/request/organizationImportRequest"; -+import { OrganizationKeysRequest } from "../models/request/organizationKeysRequest"; -+import { OrganizationSsoUpdateRequest } from '../models/request/organizationSsoUpdateRequest'; -+import { OrganizationSubscriptionUpdateRequest } from "../models/request/organizationSubscriptionUpdateRequest"; -+import { OrganizationTaxInfoUpdateRequest } from "../models/request/organizationTaxInfoUpdateRequest"; -+import { OrganizationUpdateRequest } from "../models/request/organizationUpdateRequest"; -+import { OrganizationUpgradeRequest } from "../models/request/organizationUpgradeRequest"; -+import { OrganizationUserAcceptRequest } from "../models/request/organizationUserAcceptRequest"; -+import { OrganizationUserBulkConfirmRequest } from "../models/request/organizationUserBulkConfirmRequest"; -+import { OrganizationUserBulkRequest } from "../models/request/organizationUserBulkRequest"; -+import { OrganizationUserConfirmRequest } from "../models/request/organizationUserConfirmRequest"; -+import { OrganizationUserInviteRequest } from "../models/request/organizationUserInviteRequest"; -+import { OrganizationUserResetPasswordEnrollmentRequest } from "../models/request/organizationUserResetPasswordEnrollmentRequest"; -+import { OrganizationUserResetPasswordRequest } from "../models/request/organizationUserResetPasswordRequest"; -+import { OrganizationUserUpdateGroupsRequest } from "../models/request/organizationUserUpdateGroupsRequest"; -+import { OrganizationUserUpdateRequest } from "../models/request/organizationUserUpdateRequest"; -+import { PasswordHintRequest } from "../models/request/passwordHintRequest"; -+import { PasswordRequest } from "../models/request/passwordRequest"; -+import { PaymentRequest } from "../models/request/paymentRequest"; -+import { PolicyRequest } from "../models/request/policyRequest"; -+import { PreloginRequest } from "../models/request/preloginRequest"; -+import { ProviderAddOrganizationRequest } from "../models/request/provider/providerAddOrganizationRequest"; -+import { ProviderOrganizationCreateRequest } from "../models/request/provider/providerOrganizationCreateRequest"; -+import { ProviderSetupRequest } from "../models/request/provider/providerSetupRequest"; -+import { ProviderUpdateRequest } from "../models/request/provider/providerUpdateRequest"; -+import { ProviderUserAcceptRequest } from "../models/request/provider/providerUserAcceptRequest"; -+import { ProviderUserBulkConfirmRequest } from "../models/request/provider/providerUserBulkConfirmRequest"; -+import { ProviderUserBulkRequest } from "../models/request/provider/providerUserBulkRequest"; -+import { ProviderUserConfirmRequest } from "../models/request/provider/providerUserConfirmRequest"; -+import { ProviderUserInviteRequest } from "../models/request/provider/providerUserInviteRequest"; -+import { ProviderUserUpdateRequest } from "../models/request/provider/providerUserUpdateRequest"; -+import { RegisterRequest } from "../models/request/registerRequest"; -+import { SeatRequest } from "../models/request/seatRequest"; -+import { SecretVerificationRequest } from "../models/request/secretVerificationRequest"; -+import { SelectionReadOnlyRequest } from "../models/request/selectionReadOnlyRequest"; -+import { SendAccessRequest } from "../models/request/sendAccessRequest"; -+import { SendRequest } from "../models/request/sendRequest"; -+import { SetPasswordRequest } from "../models/request/setPasswordRequest"; -+import { StorageRequest } from "../models/request/storageRequest"; -+import { TaxInfoUpdateRequest } from "../models/request/taxInfoUpdateRequest"; -+import { TokenRequest } from "../models/request/tokenRequest"; -+import { TwoFactorEmailRequest } from "../models/request/twoFactorEmailRequest"; -+import { TwoFactorProviderRequest } from "../models/request/twoFactorProviderRequest"; -+import { TwoFactorRecoveryRequest } from "../models/request/twoFactorRecoveryRequest"; -+import { UpdateDomainsRequest } from "../models/request/updateDomainsRequest"; -+import { UpdateKeyRequest } from "../models/request/updateKeyRequest"; -+import { UpdateProfileRequest } from "../models/request/updateProfileRequest"; -+import { UpdateTempPasswordRequest } from "../models/request/updateTempPasswordRequest"; -+import { UpdateTwoFactorAuthenticatorRequest } from "../models/request/updateTwoFactorAuthenticatorRequest"; -+import { UpdateTwoFactorDuoRequest } from "../models/request/updateTwoFactorDuoRequest"; -+import { UpdateTwoFactorEmailRequest } from "../models/request/updateTwoFactorEmailRequest"; -+import { UpdateTwoFactorWebAuthnDeleteRequest } from "../models/request/updateTwoFactorWebAuthnDeleteRequest"; -+import { UpdateTwoFactorWebAuthnRequest } from "../models/request/updateTwoFactorWebAuthnRequest"; -+import { UpdateTwoFactorYubioOtpRequest } from "../models/request/updateTwoFactorYubioOtpRequest"; -+import { VerifyBankRequest } from "../models/request/verifyBankRequest"; -+import { VerifyDeleteRecoverRequest } from "../models/request/verifyDeleteRecoverRequest"; -+import { VerifyEmailRequest } from "../models/request/verifyEmailRequest"; - --import { ApiKeyResponse } from '../models/response/apiKeyResponse'; --import { AttachmentResponse } from '../models/response/attachmentResponse'; --import { AttachmentUploadDataResponse } from '../models/response/attachmentUploadDataResponse'; --import { BillingResponse } from '../models/response/billingResponse'; --import { BreachAccountResponse } from '../models/response/breachAccountResponse'; --import { CipherResponse } from '../models/response/cipherResponse'; -+import { ApiKeyResponse } from "../models/response/apiKeyResponse"; -+import { AttachmentResponse } from "../models/response/attachmentResponse"; -+import { AttachmentUploadDataResponse } from "../models/response/attachmentUploadDataResponse"; -+import { BillingResponse } from "../models/response/billingResponse"; -+import { BreachAccountResponse } from "../models/response/breachAccountResponse"; -+import { CipherResponse } from "../models/response/cipherResponse"; - import { -- CollectionGroupDetailsResponse, -- CollectionResponse, --} from '../models/response/collectionResponse'; --import { DomainsResponse } from '../models/response/domainsResponse'; -+ CollectionGroupDetailsResponse, -+ CollectionResponse, -+} from "../models/response/collectionResponse"; -+import { DomainsResponse } from "../models/response/domainsResponse"; - import { -- EmergencyAccessGranteeDetailsResponse, -- EmergencyAccessGrantorDetailsResponse, -- EmergencyAccessTakeoverResponse, -- EmergencyAccessViewResponse --} from '../models/response/emergencyAccessResponse'; --import { EventResponse } from '../models/response/eventResponse'; --import { FolderResponse } from '../models/response/folderResponse'; -+ EmergencyAccessGranteeDetailsResponse, -+ EmergencyAccessGrantorDetailsResponse, -+ EmergencyAccessTakeoverResponse, -+ EmergencyAccessViewResponse, -+} from "../models/response/emergencyAccessResponse"; -+import { EventResponse } from "../models/response/eventResponse"; -+import { FolderResponse } from "../models/response/folderResponse"; -+import { GroupDetailsResponse, GroupResponse } from "../models/response/groupResponse"; -+import { IdentityCaptchaResponse } from "../models/response/identityCaptchaResponse"; -+import { IdentityTokenResponse } from "../models/response/identityTokenResponse"; -+import { IdentityTwoFactorResponse } from "../models/response/identityTwoFactorResponse"; -+import { KeyConnectorUserKeyResponse } from "../models/response/keyConnectorUserKeyResponse"; -+import { ListResponse } from "../models/response/listResponse"; -+import { OrganizationSsoResponse } from "../models/response/organization/organizationSsoResponse"; -+import { OrganizationAutoEnrollStatusResponse } from "../models/response/organizationAutoEnrollStatusResponse"; -+import { OrganizationKeysResponse } from "../models/response/organizationKeysResponse"; -+import { OrganizationResponse } from "../models/response/organizationResponse"; -+import { OrganizationSubscriptionResponse } from "../models/response/organizationSubscriptionResponse"; -+import { OrganizationUserBulkPublicKeyResponse } from "../models/response/organizationUserBulkPublicKeyResponse"; -+import { OrganizationUserBulkResponse } from "../models/response/organizationUserBulkResponse"; - import { -- GroupDetailsResponse, -- GroupResponse, --} from '../models/response/groupResponse'; --import { IdentityCaptchaResponse } from '../models/response/identityCaptchaResponse'; --import { IdentityTokenResponse } from '../models/response/identityTokenResponse'; --import { IdentityTwoFactorResponse } from '../models/response/identityTwoFactorResponse'; --import { KeyConnectorUserKeyResponse } from '../models/response/keyConnectorUserKeyResponse'; --import { ListResponse } from '../models/response/listResponse'; --import { OrganizationSsoResponse } from '../models/response/organization/organizationSsoResponse'; --import { OrganizationAutoEnrollStatusResponse } from '../models/response/organizationAutoEnrollStatusResponse'; --import { OrganizationKeysResponse } from '../models/response/organizationKeysResponse'; --import { OrganizationResponse } from '../models/response/organizationResponse'; --import { OrganizationSubscriptionResponse } from '../models/response/organizationSubscriptionResponse'; --import { OrganizationUserBulkPublicKeyResponse } from '../models/response/organizationUserBulkPublicKeyResponse'; --import { OrganizationUserBulkResponse } from '../models/response/organizationUserBulkResponse'; -+ OrganizationUserDetailsResponse, -+ OrganizationUserResetPasswordDetailsReponse, -+ OrganizationUserUserDetailsResponse, -+} from "../models/response/organizationUserResponse"; -+import { PaymentResponse } from "../models/response/paymentResponse"; -+import { PlanResponse } from "../models/response/planResponse"; -+import { PolicyResponse } from "../models/response/policyResponse"; -+import { PreloginResponse } from "../models/response/preloginResponse"; -+import { ProfileResponse } from "../models/response/profileResponse"; - import { -- OrganizationUserDetailsResponse, -- OrganizationUserResetPasswordDetailsReponse, -- OrganizationUserUserDetailsResponse, --} from '../models/response/organizationUserResponse'; --import { PaymentResponse } from '../models/response/paymentResponse'; --import { PlanResponse } from '../models/response/planResponse'; --import { PolicyResponse } from '../models/response/policyResponse'; --import { PreloginResponse } from '../models/response/preloginResponse'; --import { ProfileResponse } from '../models/response/profileResponse'; --import { ProviderOrganizationOrganizationDetailsResponse, ProviderOrganizationResponse } from '../models/response/provider/providerOrganizationResponse'; --import { ProviderResponse } from '../models/response/provider/providerResponse'; --import { ProviderUserBulkPublicKeyResponse } from '../models/response/provider/providerUserBulkPublicKeyResponse'; --import { ProviderUserBulkResponse } from '../models/response/provider/providerUserBulkResponse'; --import { ProviderUserResponse, ProviderUserUserDetailsResponse } from '../models/response/provider/providerUserResponse'; --import { SelectionReadOnlyResponse } from '../models/response/selectionReadOnlyResponse'; --import { SendAccessResponse } from '../models/response/sendAccessResponse'; --import { SendFileDownloadDataResponse } from '../models/response/sendFileDownloadDataResponse'; --import { SendFileUploadDataResponse } from '../models/response/sendFileUploadDataResponse'; --import { SendResponse } from '../models/response/sendResponse'; --import { SubscriptionResponse } from '../models/response/subscriptionResponse'; --import { SyncResponse } from '../models/response/syncResponse'; --import { TaxInfoResponse } from '../models/response/taxInfoResponse'; --import { TaxRateResponse } from '../models/response/taxRateResponse'; --import { TwoFactorAuthenticatorResponse } from '../models/response/twoFactorAuthenticatorResponse'; --import { TwoFactorDuoResponse } from '../models/response/twoFactorDuoResponse'; --import { TwoFactorEmailResponse } from '../models/response/twoFactorEmailResponse'; --import { TwoFactorProviderResponse } from '../models/response/twoFactorProviderResponse'; --import { TwoFactorRecoverResponse } from '../models/response/twoFactorRescoverResponse'; --import { ChallengeResponse, TwoFactorWebAuthnResponse } from '../models/response/twoFactorWebAuthnResponse'; --import { TwoFactorYubiKeyResponse } from '../models/response/twoFactorYubiKeyResponse'; --import { UserKeyResponse } from '../models/response/userKeyResponse'; -+ ProviderOrganizationOrganizationDetailsResponse, -+ ProviderOrganizationResponse, -+} from "../models/response/provider/providerOrganizationResponse"; -+import { ProviderResponse } from "../models/response/provider/providerResponse"; -+import { ProviderUserBulkPublicKeyResponse } from "../models/response/provider/providerUserBulkPublicKeyResponse"; -+import { ProviderUserBulkResponse } from "../models/response/provider/providerUserBulkResponse"; -+import { -+ ProviderUserResponse, -+ ProviderUserUserDetailsResponse, -+} from "../models/response/provider/providerUserResponse"; -+import { SelectionReadOnlyResponse } from "../models/response/selectionReadOnlyResponse"; -+import { SendAccessResponse } from "../models/response/sendAccessResponse"; -+import { SendFileDownloadDataResponse } from "../models/response/sendFileDownloadDataResponse"; -+import { SendFileUploadDataResponse } from "../models/response/sendFileUploadDataResponse"; -+import { SendResponse } from "../models/response/sendResponse"; -+import { SsoConfigResponse } from '../models/response/ssoConfigResponse'; -+import { SubscriptionResponse } from "../models/response/subscriptionResponse"; -+import { SyncResponse } from "../models/response/syncResponse"; -+import { TaxInfoResponse } from "../models/response/taxInfoResponse"; -+import { TaxRateResponse } from "../models/response/taxRateResponse"; -+import { TwoFactorAuthenticatorResponse } from "../models/response/twoFactorAuthenticatorResponse"; -+import { TwoFactorDuoResponse } from "../models/response/twoFactorDuoResponse"; -+import { TwoFactorEmailResponse } from "../models/response/twoFactorEmailResponse"; -+import { TwoFactorProviderResponse } from "../models/response/twoFactorProviderResponse"; -+import { TwoFactorRecoverResponse } from "../models/response/twoFactorRescoverResponse"; -+import { -+ ChallengeResponse, -+ TwoFactorWebAuthnResponse, -+} from "../models/response/twoFactorWebAuthnResponse"; -+import { TwoFactorYubiKeyResponse } from "../models/response/twoFactorYubiKeyResponse"; -+import { UserKeyResponse } from "../models/response/userKeyResponse"; - --import { SendAccessView } from '../models/view/sendAccessView'; -+import { SendAccessView } from "../models/view/sendAccessView"; - - export abstract class ApiService { -- postIdentityToken: (request: TokenRequest) => Promise; -- refreshIdentityToken: () => Promise; -+ postIdentityToken: ( -+ request: TokenRequest -+ ) => Promise; -+ refreshIdentityToken: () => Promise; - -- getProfile: () => Promise; -- getUserBilling: () => Promise; -- getUserSubscription: () => Promise; -- getTaxInfo: () => Promise; -- putProfile: (request: UpdateProfileRequest) => Promise; -- putTaxInfo: (request: TaxInfoUpdateRequest) => Promise; -- postPrelogin: (request: PreloginRequest) => Promise; -- postEmailToken: (request: EmailTokenRequest) => Promise; -- postEmail: (request: EmailRequest) => Promise; -- postPassword: (request: PasswordRequest) => Promise; -- setPassword: (request: SetPasswordRequest) => Promise; -- postSetKeyConnectorKey: (request: SetKeyConnectorKeyRequest) => Promise; -- postSecurityStamp: (request: SecretVerificationRequest) => Promise; -- deleteAccount: (request: SecretVerificationRequest) => Promise; -- getAccountRevisionDate: () => Promise; -- postPasswordHint: (request: PasswordHintRequest) => Promise; -- postRegister: (request: RegisterRequest) => Promise; -- postPremium: (data: FormData) => Promise; -- postIapCheck: (request: IapCheckRequest) => Promise; -- postReinstatePremium: () => Promise; -- postCancelPremium: () => Promise; -- postAccountStorage: (request: StorageRequest) => Promise; -- postAccountPayment: (request: PaymentRequest) => Promise; -- postAccountLicense: (data: FormData) => Promise; -- postAccountKey: (request: UpdateKeyRequest) => Promise; -- postAccountKeys: (request: KeysRequest) => Promise; -- postAccountVerifyEmail: () => Promise; -- postAccountVerifyEmailToken: (request: VerifyEmailRequest) => Promise; -- postAccountVerifyPassword: (request: SecretVerificationRequest) => Promise; -- postAccountRecoverDelete: (request: DeleteRecoverRequest) => Promise; -- postAccountRecoverDeleteToken: (request: VerifyDeleteRecoverRequest) => Promise; -- postAccountKdf: (request: KdfRequest) => Promise; -- postUserApiKey: (id: string, request: SecretVerificationRequest) => Promise; -- postUserRotateApiKey: (id: string, request: SecretVerificationRequest) => Promise; -- putUpdateTempPassword: (request: UpdateTempPasswordRequest) => Promise; -- postAccountRequestOTP: () => Promise; -- postAccountVerifyOTP: (request: VerifyOTPRequest) => Promise; -- postConvertToKeyConnector: () => Promise; -+ getProfile: () => Promise; -+ getUserBilling: () => Promise; -+ getUserSubscription: () => Promise; -+ getTaxInfo: () => Promise; -+ putProfile: (request: UpdateProfileRequest) => Promise; -+ putTaxInfo: (request: TaxInfoUpdateRequest) => Promise; -+ postPrelogin: (request: PreloginRequest) => Promise; -+ postEmailToken: (request: EmailTokenRequest) => Promise; -+ postEmail: (request: EmailRequest) => Promise; -+ postPassword: (request: PasswordRequest) => Promise; -+ setPassword: (request: SetPasswordRequest) => Promise; -+ postSetKeyConnectorKey: (request: SetKeyConnectorKeyRequest) => Promise; -+ postSecurityStamp: (request: SecretVerificationRequest) => Promise; -+ deleteAccount: (request: SecretVerificationRequest) => Promise; -+ getAccountRevisionDate: () => Promise; -+ postPasswordHint: (request: PasswordHintRequest) => Promise; -+ postRegister: (request: RegisterRequest) => Promise; -+ postPremium: (data: FormData) => Promise; -+ postIapCheck: (request: IapCheckRequest) => Promise; -+ postReinstatePremium: () => Promise; -+ postCancelPremium: () => Promise; -+ postAccountStorage: (request: StorageRequest) => Promise; -+ postAccountPayment: (request: PaymentRequest) => Promise; -+ postAccountLicense: (data: FormData) => Promise; -+ postAccountKey: (request: UpdateKeyRequest) => Promise; -+ postAccountKeys: (request: KeysRequest) => Promise; -+ postAccountVerifyEmail: () => Promise; -+ postAccountVerifyEmailToken: (request: VerifyEmailRequest) => Promise; -+ postAccountVerifyPassword: (request: SecretVerificationRequest) => Promise; -+ postAccountRecoverDelete: (request: DeleteRecoverRequest) => Promise; -+ postAccountRecoverDeleteToken: (request: VerifyDeleteRecoverRequest) => Promise; -+ postAccountKdf: (request: KdfRequest) => Promise; -+ postUserApiKey: (id: string, request: SecretVerificationRequest) => Promise; -+ postUserRotateApiKey: (id: string, request: SecretVerificationRequest) => Promise; -+ putUpdateTempPassword: (request: UpdateTempPasswordRequest) => Promise; -+ postAccountRequestOTP: () => Promise; -+ postAccountVerifyOTP: (request: VerifyOTPRequest) => Promise; -+ postConvertToKeyConnector: () => Promise; - -- getFolder: (id: string) => Promise; -- postFolder: (request: FolderRequest) => Promise; -- putFolder: (id: string, request: FolderRequest) => Promise; -- deleteFolder: (id: string) => Promise; -+ getFolder: (id: string) => Promise; -+ postFolder: (request: FolderRequest) => Promise; -+ putFolder: (id: string, request: FolderRequest) => Promise; -+ deleteFolder: (id: string) => Promise; - -- getSend: (id: string) => Promise; -- postSendAccess: (id: string, request: SendAccessRequest, apiUrl?: string) => Promise; -- getSends: () => Promise>; -- postSend: (request: SendRequest) => Promise; -- postFileTypeSend: (request: SendRequest) => Promise; -- postSendFile: (sendId: string, fileId: string, data: FormData) => Promise; -- /** -- * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. -- * This method still exists for backward compatibility with old server versions. -- */ -- postSendFileLegacy: (data: FormData) => Promise; -- putSend: (id: string, request: SendRequest) => Promise; -- putSendRemovePassword: (id: string) => Promise; -- deleteSend: (id: string) => Promise; -- getSendFileDownloadData: (send: SendAccessView, request: SendAccessRequest, apiUrl?: string) => Promise; -- renewSendFileUploadUrl: (sendId: string, fileId: string) => Promise; -+ getSend: (id: string) => Promise; -+ postSendAccess: ( -+ id: string, -+ request: SendAccessRequest, -+ apiUrl?: string -+ ) => Promise; -+ getSends: () => Promise>; -+ postSend: (request: SendRequest) => Promise; -+ postFileTypeSend: (request: SendRequest) => Promise; -+ postSendFile: (sendId: string, fileId: string, data: FormData) => Promise; -+ /** -+ * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. -+ * This method still exists for backward compatibility with old server versions. -+ */ -+ postSendFileLegacy: (data: FormData) => Promise; -+ putSend: (id: string, request: SendRequest) => Promise; -+ putSendRemovePassword: (id: string) => Promise; -+ deleteSend: (id: string) => Promise; -+ getSendFileDownloadData: ( -+ send: SendAccessView, -+ request: SendAccessRequest, -+ apiUrl?: string -+ ) => Promise; -+ renewSendFileUploadUrl: (sendId: string, fileId: string) => Promise; - -- getCipher: (id: string) => Promise; -- getCipherAdmin: (id: string) => Promise; -- getAttachmentData: (cipherId: string, attachmentId: string, emergencyAccessId?: string) => Promise; -- getCiphersOrganization: (organizationId: string) => Promise>; -- postCipher: (request: CipherRequest) => Promise; -- postCipherCreate: (request: CipherCreateRequest) => Promise; -- postCipherAdmin: (request: CipherCreateRequest) => Promise; -- putCipher: (id: string, request: CipherRequest) => Promise; -- putCipherAdmin: (id: string, request: CipherRequest) => Promise; -- deleteCipher: (id: string) => Promise; -- deleteCipherAdmin: (id: string) => Promise; -- deleteManyCiphers: (request: CipherBulkDeleteRequest) => Promise; -- deleteManyCiphersAdmin: (request: CipherBulkDeleteRequest) => Promise; -- putMoveCiphers: (request: CipherBulkMoveRequest) => Promise; -- putShareCipher: (id: string, request: CipherShareRequest) => Promise; -- putShareCiphers: (request: CipherBulkShareRequest) => Promise; -- putCipherCollections: (id: string, request: CipherCollectionsRequest) => Promise; -- putCipherCollectionsAdmin: (id: string, request: CipherCollectionsRequest) => Promise; -- postPurgeCiphers: (request: SecretVerificationRequest, organizationId?: string) => Promise; -- postImportCiphers: (request: ImportCiphersRequest) => Promise; -- postImportOrganizationCiphers: (organizationId: string, request: ImportOrganizationCiphersRequest) => Promise; -- putDeleteCipher: (id: string) => Promise; -- putDeleteCipherAdmin: (id: string) => Promise; -- putDeleteManyCiphers: (request: CipherBulkDeleteRequest) => Promise; -- putDeleteManyCiphersAdmin: (request: CipherBulkDeleteRequest) => Promise; -- putRestoreCipher: (id: string) => Promise; -- putRestoreCipherAdmin: (id: string) => Promise; -- putRestoreManyCiphers: (request: CipherBulkRestoreRequest) => Promise>; -+ getCipher: (id: string) => Promise; -+ getCipherAdmin: (id: string) => Promise; -+ getAttachmentData: ( -+ cipherId: string, -+ attachmentId: string, -+ emergencyAccessId?: string -+ ) => Promise; -+ getCiphersOrganization: (organizationId: string) => Promise>; -+ postCipher: (request: CipherRequest) => Promise; -+ postCipherCreate: (request: CipherCreateRequest) => Promise; -+ postCipherAdmin: (request: CipherCreateRequest) => Promise; -+ putCipher: (id: string, request: CipherRequest) => Promise; -+ putCipherAdmin: (id: string, request: CipherRequest) => Promise; -+ deleteCipher: (id: string) => Promise; -+ deleteCipherAdmin: (id: string) => Promise; -+ deleteManyCiphers: (request: CipherBulkDeleteRequest) => Promise; -+ deleteManyCiphersAdmin: (request: CipherBulkDeleteRequest) => Promise; -+ putMoveCiphers: (request: CipherBulkMoveRequest) => Promise; -+ putShareCipher: (id: string, request: CipherShareRequest) => Promise; -+ putShareCiphers: (request: CipherBulkShareRequest) => Promise; -+ putCipherCollections: (id: string, request: CipherCollectionsRequest) => Promise; -+ putCipherCollectionsAdmin: (id: string, request: CipherCollectionsRequest) => Promise; -+ postPurgeCiphers: (request: SecretVerificationRequest, organizationId?: string) => Promise; -+ postImportCiphers: (request: ImportCiphersRequest) => Promise; -+ postImportOrganizationCiphers: ( -+ organizationId: string, -+ request: ImportOrganizationCiphersRequest -+ ) => Promise; -+ putDeleteCipher: (id: string) => Promise; -+ putDeleteCipherAdmin: (id: string) => Promise; -+ putDeleteManyCiphers: (request: CipherBulkDeleteRequest) => Promise; -+ putDeleteManyCiphersAdmin: (request: CipherBulkDeleteRequest) => Promise; -+ putRestoreCipher: (id: string) => Promise; -+ putRestoreCipherAdmin: (id: string) => Promise; -+ putRestoreManyCiphers: ( -+ request: CipherBulkRestoreRequest -+ ) => Promise>; - -- /** -- * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. -- * This method still exists for backward compatibility with old server versions. -- */ -- postCipherAttachmentLegacy: (id: string, data: FormData) => Promise; -- /** -- * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. -- * This method still exists for backward compatibility with old server versions. -- */ -- postCipherAttachmentAdminLegacy: (id: string, data: FormData) => Promise; -- postCipherAttachment: (id: string, request: AttachmentRequest) => Promise; -- deleteCipherAttachment: (id: string, attachmentId: string) => Promise; -- deleteCipherAttachmentAdmin: (id: string, attachmentId: string) => Promise; -- postShareCipherAttachment: (id: string, attachmentId: string, data: FormData, -- organizationId: string) => Promise; -- renewAttachmentUploadUrl: (id: string, attachmentId: string) => Promise; -- postAttachmentFile: (id: string, attachmentId: string, data: FormData) => Promise; -+ /** -+ * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. -+ * This method still exists for backward compatibility with old server versions. -+ */ -+ postCipherAttachmentLegacy: (id: string, data: FormData) => Promise; -+ /** -+ * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. -+ * This method still exists for backward compatibility with old server versions. -+ */ -+ postCipherAttachmentAdminLegacy: (id: string, data: FormData) => Promise; -+ postCipherAttachment: ( -+ id: string, -+ request: AttachmentRequest -+ ) => Promise; -+ deleteCipherAttachment: (id: string, attachmentId: string) => Promise; -+ deleteCipherAttachmentAdmin: (id: string, attachmentId: string) => Promise; -+ postShareCipherAttachment: ( -+ id: string, -+ attachmentId: string, -+ data: FormData, -+ organizationId: string -+ ) => Promise; -+ renewAttachmentUploadUrl: ( -+ id: string, -+ attachmentId: string -+ ) => Promise; -+ postAttachmentFile: (id: string, attachmentId: string, data: FormData) => Promise; - -- getCollectionDetails: (organizationId: string, id: string) => Promise; -- getUserCollections: () => Promise>; -- getCollections: (organizationId: string) => Promise>; -- getCollectionUsers: (organizationId: string, id: string) => Promise; -- postCollection: (organizationId: string, request: CollectionRequest) => Promise; -- putCollectionUsers: (organizationId: string, id: string, request: SelectionReadOnlyRequest[]) => Promise; -- putCollection: (organizationId: string, id: string, request: CollectionRequest) => Promise; -- deleteCollection: (organizationId: string, id: string) => Promise; -- deleteCollectionUser: (organizationId: string, id: string, organizationUserId: string) => Promise; -+ getCollectionDetails: ( -+ organizationId: string, -+ id: string -+ ) => Promise; -+ getUserCollections: () => Promise>; -+ getCollections: (organizationId: string) => Promise>; -+ getCollectionUsers: (organizationId: string, id: string) => Promise; -+ postCollection: ( -+ organizationId: string, -+ request: CollectionRequest -+ ) => Promise; -+ putCollectionUsers: ( -+ organizationId: string, -+ id: string, -+ request: SelectionReadOnlyRequest[] -+ ) => Promise; -+ putCollection: ( -+ organizationId: string, -+ id: string, -+ request: CollectionRequest -+ ) => Promise; -+ deleteCollection: (organizationId: string, id: string) => Promise; -+ deleteCollectionUser: ( -+ organizationId: string, -+ id: string, -+ organizationUserId: string -+ ) => Promise; - -- getGroupDetails: (organizationId: string, id: string) => Promise; -- getGroups: (organizationId: string) => Promise>; -- getGroupUsers: (organizationId: string, id: string) => Promise; -- postGroup: (organizationId: string, request: GroupRequest) => Promise; -- putGroup: (organizationId: string, id: string, request: GroupRequest) => Promise; -- putGroupUsers: (organizationId: string, id: string, request: string[]) => Promise; -- deleteGroup: (organizationId: string, id: string) => Promise; -- deleteGroupUser: (organizationId: string, id: string, organizationUserId: string) => Promise; -+ getGroupDetails: (organizationId: string, id: string) => Promise; -+ getGroups: (organizationId: string) => Promise>; -+ getGroupUsers: (organizationId: string, id: string) => Promise; -+ postGroup: (organizationId: string, request: GroupRequest) => Promise; -+ putGroup: (organizationId: string, id: string, request: GroupRequest) => Promise; -+ putGroupUsers: (organizationId: string, id: string, request: string[]) => Promise; -+ deleteGroup: (organizationId: string, id: string) => Promise; -+ deleteGroupUser: (organizationId: string, id: string, organizationUserId: string) => Promise; - -- getPolicy: (organizationId: string, type: PolicyType) => Promise; -- getPolicies: (organizationId: string) => Promise>; -- getPoliciesByToken: (organizationId: string, token: string, email: string, organizationUserId: string) => -- Promise>; -- putPolicy: (organizationId: string, type: PolicyType, request: PolicyRequest) => Promise; -+ getPolicy: (organizationId: string, type: PolicyType) => Promise; -+ getPolicies: (organizationId: string) => Promise>; -+ getPoliciesByToken: ( -+ organizationId: string, -+ token: string, -+ email: string, -+ organizationUserId: string -+ ) => Promise>; -+ getPoliciesByInvitedUser: ( -+ organizationId: string, -+ userId: string -+ ) => Promise>; -+ putPolicy: ( -+ organizationId: string, -+ type: PolicyType, -+ request: PolicyRequest -+ ) => Promise; - -- getOrganizationUser: (organizationId: string, id: string) => Promise; -- getOrganizationUserGroups: (organizationId: string, id: string) => Promise; -- getOrganizationUsers: (organizationId: string) => Promise>; -- getOrganizationUserResetPasswordDetails: (organizationId: string, id: string) -- => Promise; -- postOrganizationUserInvite: (organizationId: string, request: OrganizationUserInviteRequest) => Promise; -- postOrganizationUserReinvite: (organizationId: string, id: string) => Promise; -- postManyOrganizationUserReinvite: (organizationId: string, request: OrganizationUserBulkRequest) => Promise>; -- postOrganizationUserAccept: (organizationId: string, id: string, -- request: OrganizationUserAcceptRequest) => Promise; -- postOrganizationUserConfirm: (organizationId: string, id: string, -- request: OrganizationUserConfirmRequest) => Promise; -- postOrganizationUsersPublicKey: (organizationId: string, request: OrganizationUserBulkRequest) => -- Promise>; -- postOrganizationUserBulkConfirm: (organizationId: string, request: OrganizationUserBulkConfirmRequest) => Promise>; -+ getOrganizationUser: ( -+ organizationId: string, -+ id: string -+ ) => Promise; -+ getOrganizationUserGroups: (organizationId: string, id: string) => Promise; -+ getOrganizationUsers: ( -+ organizationId: string -+ ) => Promise>; -+ getOrganizationUserResetPasswordDetails: ( -+ organizationId: string, -+ id: string -+ ) => Promise; -+ postOrganizationUserInvite: ( -+ organizationId: string, -+ request: OrganizationUserInviteRequest -+ ) => Promise; -+ postOrganizationUserReinvite: (organizationId: string, id: string) => Promise; -+ postManyOrganizationUserReinvite: ( -+ organizationId: string, -+ request: OrganizationUserBulkRequest -+ ) => Promise>; -+ postOrganizationUserAccept: ( -+ organizationId: string, -+ id: string, -+ request: OrganizationUserAcceptRequest -+ ) => Promise; -+ postOrganizationUserConfirm: ( -+ organizationId: string, -+ id: string, -+ request: OrganizationUserConfirmRequest -+ ) => Promise; -+ postOrganizationUsersPublicKey: ( -+ organizationId: string, -+ request: OrganizationUserBulkRequest -+ ) => Promise>; -+ postOrganizationUserBulkConfirm: ( -+ organizationId: string, -+ request: OrganizationUserBulkConfirmRequest -+ ) => Promise>; - -- putOrganizationUser: (organizationId: string, id: string, request: OrganizationUserUpdateRequest) => Promise; -- putOrganizationUserGroups: (organizationId: string, id: string, -- request: OrganizationUserUpdateGroupsRequest) => Promise; -- putOrganizationUserResetPasswordEnrollment: (organizationId: string, userId: string, -- request: OrganizationUserResetPasswordEnrollmentRequest) => Promise; -- putOrganizationUserResetPassword: (organizationId: string, id: string, -- request: OrganizationUserResetPasswordRequest) => Promise; -- deleteOrganizationUser: (organizationId: string, id: string) => Promise; -- deleteManyOrganizationUsers: (organizationId: string, request: OrganizationUserBulkRequest) => Promise>; -+ putOrganizationUser: ( -+ organizationId: string, -+ id: string, -+ request: OrganizationUserUpdateRequest -+ ) => Promise; -+ getSsoConfig: (id: string) => Promise; -+ putOrganizationSso: (id: string, request: OrganizationSsoUpdateRequest) => Promise; -+ putOrganizationUserGroups: ( -+ organizationId: string, -+ id: string, -+ request: OrganizationUserUpdateGroupsRequest -+ ) => Promise; -+ putOrganizationUserResetPasswordEnrollment: ( -+ organizationId: string, -+ userId: string, -+ request: OrganizationUserResetPasswordEnrollmentRequest -+ ) => Promise; -+ putOrganizationUserResetPassword: ( -+ organizationId: string, -+ id: string, -+ request: OrganizationUserResetPasswordRequest -+ ) => Promise; -+ deleteOrganizationUser: (organizationId: string, id: string) => Promise; -+ deleteManyOrganizationUsers: ( -+ organizationId: string, -+ request: OrganizationUserBulkRequest -+ ) => Promise>; - -- getSync: () => Promise; -- postImportDirectory: (organizationId: string, request: ImportDirectoryRequest) => Promise; -- postPublicImportDirectory: (request: OrganizationImportRequest) => Promise; -+ getSync: () => Promise; -+ postImportDirectory: (organizationId: string, request: ImportDirectoryRequest) => Promise; -+ postPublicImportDirectory: (request: OrganizationImportRequest) => Promise; - -- getSettingsDomains: () => Promise; -- putSettingsDomains: (request: UpdateDomainsRequest) => Promise; -+ getSettingsDomains: () => Promise; -+ putSettingsDomains: (request: UpdateDomainsRequest) => Promise; - -- getTwoFactorProviders: () => Promise>; -- getTwoFactorOrganizationProviders: (organizationId: string) => Promise>; -- getTwoFactorAuthenticator: (request: SecretVerificationRequest) => Promise; -- getTwoFactorEmail: (request: SecretVerificationRequest) => Promise; -- getTwoFactorDuo: (request: SecretVerificationRequest) => Promise; -- getTwoFactorOrganizationDuo: (organizationId: string, -- request: SecretVerificationRequest) => Promise; -- getTwoFactorYubiKey: (request: SecretVerificationRequest) => Promise; -- getTwoFactorWebAuthn: (request: SecretVerificationRequest) => Promise; -- getTwoFactorWebAuthnChallenge: (request: SecretVerificationRequest) => Promise; -- getTwoFactorRecover: (request: SecretVerificationRequest) => Promise; -- putTwoFactorAuthenticator: ( -- request: UpdateTwoFactorAuthenticatorRequest) => Promise; -- putTwoFactorEmail: (request: UpdateTwoFactorEmailRequest) => Promise; -- putTwoFactorDuo: (request: UpdateTwoFactorDuoRequest) => Promise; -- putTwoFactorOrganizationDuo: (organizationId: string, -- request: UpdateTwoFactorDuoRequest) => Promise; -- putTwoFactorYubiKey: (request: UpdateTwoFactorYubioOtpRequest) => Promise; -- putTwoFactorWebAuthn: (request: UpdateTwoFactorWebAuthnRequest) => Promise; -- deleteTwoFactorWebAuthn: (request: UpdateTwoFactorWebAuthnDeleteRequest) => Promise; -- putTwoFactorDisable: (request: TwoFactorProviderRequest) => Promise; -- putTwoFactorOrganizationDisable: (organizationId: string, -- request: TwoFactorProviderRequest) => Promise; -- postTwoFactorRecover: (request: TwoFactorRecoveryRequest) => Promise; -- postTwoFactorEmailSetup: (request: TwoFactorEmailRequest) => Promise; -- postTwoFactorEmail: (request: TwoFactorEmailRequest) => Promise; -+ getTwoFactorProviders: () => Promise>; -+ getTwoFactorOrganizationProviders: ( -+ organizationId: string -+ ) => Promise>; -+ getTwoFactorAuthenticator: ( -+ request: SecretVerificationRequest -+ ) => Promise; -+ getTwoFactorEmail: (request: SecretVerificationRequest) => Promise; -+ getTwoFactorDuo: (request: SecretVerificationRequest) => Promise; -+ getTwoFactorOrganizationDuo: ( -+ organizationId: string, -+ request: SecretVerificationRequest -+ ) => Promise; -+ getTwoFactorYubiKey: (request: SecretVerificationRequest) => Promise; -+ getTwoFactorWebAuthn: (request: SecretVerificationRequest) => Promise; -+ getTwoFactorWebAuthnChallenge: (request: SecretVerificationRequest) => Promise; -+ getTwoFactorRecover: (request: SecretVerificationRequest) => Promise; -+ putTwoFactorAuthenticator: ( -+ request: UpdateTwoFactorAuthenticatorRequest -+ ) => Promise; -+ putTwoFactorEmail: (request: UpdateTwoFactorEmailRequest) => Promise; -+ putTwoFactorDuo: (request: UpdateTwoFactorDuoRequest) => Promise; -+ putTwoFactorOrganizationDuo: ( -+ organizationId: string, -+ request: UpdateTwoFactorDuoRequest -+ ) => Promise; -+ putTwoFactorYubiKey: ( -+ request: UpdateTwoFactorYubioOtpRequest -+ ) => Promise; -+ putTwoFactorWebAuthn: ( -+ request: UpdateTwoFactorWebAuthnRequest -+ ) => Promise; -+ deleteTwoFactorWebAuthn: ( -+ request: UpdateTwoFactorWebAuthnDeleteRequest -+ ) => Promise; -+ putTwoFactorDisable: (request: TwoFactorProviderRequest) => Promise; -+ putTwoFactorOrganizationDisable: ( -+ organizationId: string, -+ request: TwoFactorProviderRequest -+ ) => Promise; -+ postTwoFactorRecover: (request: TwoFactorRecoveryRequest) => Promise; -+ postTwoFactorEmailSetup: (request: TwoFactorEmailRequest) => Promise; -+ postTwoFactorEmail: (request: TwoFactorEmailRequest) => Promise; - -- getEmergencyAccessTrusted: () => Promise>; -- getEmergencyAccessGranted: () => Promise>; -- getEmergencyAccess: (id: string) => Promise; -- getEmergencyGrantorPolicies: (id: string) => Promise>; -- putEmergencyAccess: (id: string, request: EmergencyAccessUpdateRequest) => Promise; -- deleteEmergencyAccess: (id: string) => Promise; -- postEmergencyAccessInvite: (request: EmergencyAccessInviteRequest) => Promise; -- postEmergencyAccessReinvite: (id: string) => Promise; -- postEmergencyAccessAccept: (id: string, request: EmergencyAccessAcceptRequest) => Promise; -- postEmergencyAccessConfirm: (id: string, request: EmergencyAccessConfirmRequest) => Promise; -- postEmergencyAccessInitiate: (id: string) => Promise; -- postEmergencyAccessApprove: (id: string) => Promise; -- postEmergencyAccessReject: (id: string) => Promise; -- postEmergencyAccessTakeover: (id: string) => Promise; -- postEmergencyAccessPassword: (id: string, request: EmergencyAccessPasswordRequest) => Promise; -- postEmergencyAccessView: (id: string) => Promise; -+ getEmergencyAccessTrusted: () => Promise>; -+ getEmergencyAccessGranted: () => Promise>; -+ getEmergencyAccess: (id: string) => Promise; -+ getEmergencyGrantorPolicies: (id: string) => Promise>; -+ putEmergencyAccess: (id: string, request: EmergencyAccessUpdateRequest) => Promise; -+ deleteEmergencyAccess: (id: string) => Promise; -+ postEmergencyAccessInvite: (request: EmergencyAccessInviteRequest) => Promise; -+ postEmergencyAccessReinvite: (id: string) => Promise; -+ postEmergencyAccessAccept: (id: string, request: EmergencyAccessAcceptRequest) => Promise; -+ postEmergencyAccessConfirm: (id: string, request: EmergencyAccessConfirmRequest) => Promise; -+ postEmergencyAccessInitiate: (id: string) => Promise; -+ postEmergencyAccessApprove: (id: string) => Promise; -+ postEmergencyAccessReject: (id: string) => Promise; -+ postEmergencyAccessTakeover: (id: string) => Promise; -+ postEmergencyAccessPassword: ( -+ id: string, -+ request: EmergencyAccessPasswordRequest -+ ) => Promise; -+ postEmergencyAccessView: (id: string) => Promise; - -- getOrganization: (id: string) => Promise; -- getOrganizationBilling: (id: string) => Promise; -- getOrganizationSubscription: (id: string) => Promise; -- getOrganizationLicense: (id: string, installationId: string) => Promise; -- getOrganizationTaxInfo: (id: string) => Promise; -- getOrganizationAutoEnrollStatus: (identifier: string) => Promise; -- getOrganizationSso: (id: string) => Promise; -- postOrganization: (request: OrganizationCreateRequest) => Promise; -- putOrganization: (id: string, request: OrganizationUpdateRequest) => Promise; -- putOrganizationTaxInfo: (id: string, request: OrganizationTaxInfoUpdateRequest) => Promise; -- postLeaveOrganization: (id: string) => Promise; -- postOrganizationLicense: (data: FormData) => Promise; -- postOrganizationLicenseUpdate: (id: string, data: FormData) => Promise; -- postOrganizationApiKey: (id: string, request: SecretVerificationRequest) => Promise; -- postOrganizationRotateApiKey: (id: string, request: SecretVerificationRequest) => Promise; -- postOrganizationSso: (id: string, request: OrganizationSsoRequest) => Promise; -- postOrganizationUpgrade: (id: string, request: OrganizationUpgradeRequest) => Promise; -- postOrganizationUpdateSubscription: (id: string, request: OrganizationSubscriptionUpdateRequest) => Promise; -- postOrganizationSeat: (id: string, request: SeatRequest) => Promise; -- postOrganizationStorage: (id: string, request: StorageRequest) => Promise; -- postOrganizationPayment: (id: string, request: PaymentRequest) => Promise; -- postOrganizationVerifyBank: (id: string, request: VerifyBankRequest) => Promise; -- postOrganizationCancel: (id: string) => Promise; -- postOrganizationReinstate: (id: string) => Promise; -- deleteOrganization: (id: string, request: SecretVerificationRequest) => Promise; -- getPlans: () => Promise>; -- getTaxRates: () => Promise>; -- getOrganizationKeys: (id: string) => Promise; -- postOrganizationKeys: (id: string, request: OrganizationKeysRequest) => Promise; -+ getOrganization: (id: string) => Promise; -+ getOrganizationBilling: (id: string) => Promise; -+ getOrganizationSubscription: (id: string) => Promise; -+ getOrganizationLicense: (id: string, installationId: string) => Promise; -+ getOrganizationTaxInfo: (id: string) => Promise; -+ getOrganizationAutoEnrollStatus: ( -+ identifier: string -+ ) => Promise; -+ getOrganizationSso: (id: string) => Promise; -+ postOrganization: (request: OrganizationCreateRequest) => Promise; -+ putOrganization: ( -+ id: string, -+ request: OrganizationUpdateRequest -+ ) => Promise; -+ putOrganizationTaxInfo: (id: string, request: OrganizationTaxInfoUpdateRequest) => Promise; -+ postLeaveOrganization: (id: string) => Promise; -+ postOrganizationLicense: (data: FormData) => Promise; -+ postOrganizationLicenseUpdate: (id: string, data: FormData) => Promise; -+ postOrganizationApiKey: ( -+ id: string, -+ request: SecretVerificationRequest -+ ) => Promise; -+ postOrganizationRotateApiKey: ( -+ id: string, -+ request: SecretVerificationRequest -+ ) => Promise; -+ postOrganizationSso: ( -+ id: string, -+ request: OrganizationSsoRequest -+ ) => Promise; -+ postOrganizationUpgrade: ( -+ id: string, -+ request: OrganizationUpgradeRequest -+ ) => Promise; -+ postOrganizationUpdateSubscription: ( -+ id: string, -+ request: OrganizationSubscriptionUpdateRequest -+ ) => Promise; -+ postOrganizationSeat: (id: string, request: SeatRequest) => Promise; -+ postOrganizationStorage: (id: string, request: StorageRequest) => Promise; -+ postOrganizationPayment: (id: string, request: PaymentRequest) => Promise; -+ postOrganizationVerifyBank: (id: string, request: VerifyBankRequest) => Promise; -+ postOrganizationCancel: (id: string) => Promise; -+ postOrganizationReinstate: (id: string) => Promise; -+ deleteOrganization: (id: string, request: SecretVerificationRequest) => Promise; -+ getPlans: () => Promise>; -+ getTaxRates: () => Promise>; -+ getOrganizationKeys: (id: string) => Promise; -+ postOrganizationKeys: ( -+ id: string, -+ request: OrganizationKeysRequest -+ ) => Promise; - -- postProviderSetup: (id: string, request: ProviderSetupRequest) => Promise; -- getProvider: (id: string) => Promise; -- putProvider: (id: string, request: ProviderUpdateRequest) => Promise; -+ postProviderSetup: (id: string, request: ProviderSetupRequest) => Promise; -+ getProvider: (id: string) => Promise; -+ putProvider: (id: string, request: ProviderUpdateRequest) => Promise; - -- getProviderUsers: (providerId: string) => Promise>; -- getProviderUser: (providerId: string, id: string) => Promise; -- postProviderUserInvite: (providerId: string, request: ProviderUserInviteRequest) => Promise; -- postProviderUserReinvite: (providerId: string, id: string) => Promise; -- postManyProviderUserReinvite: (providerId: string, request: ProviderUserBulkRequest) => Promise>; -- postProviderUserAccept: (providerId: string, id: string, request: ProviderUserAcceptRequest) => Promise; -- postProviderUserConfirm: (providerId: string, id: string, request: ProviderUserConfirmRequest) => Promise; -- postProviderUsersPublicKey: (providerId: string, request: ProviderUserBulkRequest) => -- Promise>; -- postProviderUserBulkConfirm: (providerId: string, request: ProviderUserBulkConfirmRequest) => Promise>; -- putProviderUser: (providerId: string, id: string, request: ProviderUserUpdateRequest) => Promise; -- deleteProviderUser: (organizationId: string, id: string) => Promise; -- deleteManyProviderUsers: (providerId: string, request: ProviderUserBulkRequest) => Promise>; -- getProviderClients: (providerId: string) => Promise>; -- postProviderAddOrganization: (providerId: string, request: ProviderAddOrganizationRequest) => Promise; -- postProviderCreateOrganization: (providerId: string, request: ProviderOrganizationCreateRequest) => Promise; -- deleteProviderOrganization: (providerId: string, organizationId: string) => Promise; -+ getProviderUsers: (providerId: string) => Promise>; -+ getProviderUser: (providerId: string, id: string) => Promise; -+ postProviderUserInvite: (providerId: string, request: ProviderUserInviteRequest) => Promise; -+ postProviderUserReinvite: (providerId: string, id: string) => Promise; -+ postManyProviderUserReinvite: ( -+ providerId: string, -+ request: ProviderUserBulkRequest -+ ) => Promise>; -+ postProviderUserAccept: ( -+ providerId: string, -+ id: string, -+ request: ProviderUserAcceptRequest -+ ) => Promise; -+ postProviderUserConfirm: ( -+ providerId: string, -+ id: string, -+ request: ProviderUserConfirmRequest -+ ) => Promise; -+ postProviderUsersPublicKey: ( -+ providerId: string, -+ request: ProviderUserBulkRequest -+ ) => Promise>; -+ postProviderUserBulkConfirm: ( -+ providerId: string, -+ request: ProviderUserBulkConfirmRequest -+ ) => Promise>; -+ putProviderUser: ( -+ providerId: string, -+ id: string, -+ request: ProviderUserUpdateRequest -+ ) => Promise; -+ deleteProviderUser: (organizationId: string, id: string) => Promise; -+ deleteManyProviderUsers: ( -+ providerId: string, -+ request: ProviderUserBulkRequest -+ ) => Promise>; -+ getProviderClients: ( -+ providerId: string -+ ) => Promise>; -+ postProviderAddOrganization: ( -+ providerId: string, -+ request: ProviderAddOrganizationRequest -+ ) => Promise; -+ postProviderCreateOrganization: ( -+ providerId: string, -+ request: ProviderOrganizationCreateRequest -+ ) => Promise; -+ deleteProviderOrganization: (providerId: string, organizationId: string) => Promise; - -- getEvents: (start: string, end: string, token: string) => Promise>; -- getEventsCipher: (id: string, start: string, end: string, token: string) => Promise>; -- getEventsOrganization: (id: string, start: string, end: string, -- token: string) => Promise>; -- getEventsOrganizationUser: (organizationId: string, id: string, -- start: string, end: string, token: string) => Promise>; -- getEventsProvider: (id: string, start: string, end: string, token: string) => Promise>; -- getEventsProviderUser: (providerId: string, id: string, start: string, end: string, token: string) => Promise>; -- postEventsCollect: (request: EventRequest[]) => Promise; -+ getEvents: (start: string, end: string, token: string) => Promise>; -+ getEventsCipher: ( -+ id: string, -+ start: string, -+ end: string, -+ token: string -+ ) => Promise>; -+ getEventsOrganization: ( -+ id: string, -+ start: string, -+ end: string, -+ token: string -+ ) => Promise>; -+ getEventsOrganizationUser: ( -+ organizationId: string, -+ id: string, -+ start: string, -+ end: string, -+ token: string -+ ) => Promise>; -+ getEventsProvider: ( -+ id: string, -+ start: string, -+ end: string, -+ token: string -+ ) => Promise>; -+ getEventsProviderUser: ( -+ providerId: string, -+ id: string, -+ start: string, -+ end: string, -+ token: string -+ ) => Promise>; -+ postEventsCollect: (request: EventRequest[]) => Promise; - -- deleteSsoUser: (organizationId: string) => Promise; -- getSsoUserIdentifier: () => Promise; -+ deleteSsoUser: (organizationId: string) => Promise; -+ getSsoUserIdentifier: () => Promise; - -- getUserPublicKey: (id: string) => Promise; -+ getUserPublicKey: (id: string) => Promise; - -- getHibpBreach: (username: string) => Promise; -+ getHibpBreach: (username: string) => Promise; - -- postBitPayInvoice: (request: BitPayInvoiceRequest) => Promise; -- postSetupPayment: () => Promise; -+ postBitPayInvoice: (request: BitPayInvoiceRequest) => Promise; -+ postSetupPayment: () => Promise; - -- getActiveBearerToken: () => Promise; -- fetch: (request: Request) => Promise; -- nativeFetch: (request: Request) => Promise; -+ getActiveBearerToken: () => Promise; -+ fetch: (request: Request) => Promise; -+ nativeFetch: (request: Request) => Promise; - -- preValidateSso: (identifier: string) => Promise; -+ preValidateSso: (identifier: string) => Promise; - -- postCreateSponsorship: (sponsorshipOrgId: string, request: OrganizationSponsorshipCreateRequest) => Promise; -- deleteRevokeSponsorship: (sponsoringOrganizationId: string) => Promise; -- deleteRemoveSponsorship: (sponsoringOrgId: string) => Promise; -- postPreValidateSponsorshipToken: (sponsorshipToken: string) => Promise; -- postRedeemSponsorship: (sponsorshipToken: string, request: OrganizationSponsorshipRedeemRequest) => Promise; -- postResendSponsorshipOffer: (sponsoringOrgId: string) => Promise; -+ postCreateSponsorship: ( -+ sponsorshipOrgId: string, -+ request: OrganizationSponsorshipCreateRequest -+ ) => Promise; -+ deleteRevokeSponsorship: (sponsoringOrganizationId: string) => Promise; -+ deleteRemoveSponsorship: (sponsoringOrgId: string) => Promise; -+ postPreValidateSponsorshipToken: (sponsorshipToken: string) => Promise; -+ postRedeemSponsorship: ( -+ sponsorshipToken: string, -+ request: OrganizationSponsorshipRedeemRequest -+ ) => Promise; -+ postResendSponsorshipOffer: (sponsoringOrgId: string) => Promise; - -- getUserKeyFromKeyConnector: (keyConnectorUrl: string) => Promise; -- postUserKeyToKeyConnector: (keyConnectorUrl: string, request: KeyConnectorUserKeyRequest) => Promise; -- getKeyConnectorAlive: (keyConnectorUrl: string) => Promise; -+ getUserKeyFromKeyConnector: (keyConnectorUrl: string) => Promise; -+ postUserKeyToKeyConnector: ( -+ keyConnectorUrl: string, -+ request: KeyConnectorUserKeyRequest -+ ) => Promise; -+ getKeyConnectorAlive: (keyConnectorUrl: string) => Promise; - } -diff --git a/jslib/common/src/abstractions/apiKey.service.ts b/jslib/common/src/abstractions/apiKey.service.ts -deleted file mode 100644 -index bc4b8a44..00000000 ---- a/jslib/common/src/abstractions/apiKey.service.ts -+++ /dev/null -@@ -1,9 +0,0 @@ --export abstract class ApiKeyService { -- setInformation: (clientId: string, clientSecret: string) => Promise; -- clear: () => Promise; -- getClientId: () => Promise; -- getClientSecret: () => Promise; -- getEntityType: () => Promise; -- getEntityId: () => Promise; -- isAuthenticated: () => Promise; --} -diff --git a/jslib/common/src/abstractions/appId.service.ts b/jslib/common/src/abstractions/appId.service.ts -index d087478a..99bc6c9e 100644 ---- a/jslib/common/src/abstractions/appId.service.ts -+++ b/jslib/common/src/abstractions/appId.service.ts -@@ -1,4 +1,4 @@ - export abstract class AppIdService { -- getAppId: () => Promise; -- getAnonymousAppId: () => Promise; -+ getAppId: () => Promise; -+ getAnonymousAppId: () => Promise; - } -diff --git a/jslib/common/src/abstractions/audit.service.ts b/jslib/common/src/abstractions/audit.service.ts -index b8c40bc2..6b6b81f6 100644 ---- a/jslib/common/src/abstractions/audit.service.ts -+++ b/jslib/common/src/abstractions/audit.service.ts -@@ -1,6 +1,6 @@ --import { BreachAccountResponse } from '../models/response/breachAccountResponse'; -+import { BreachAccountResponse } from "../models/response/breachAccountResponse"; - - export abstract class AuditService { -- passwordLeaked: (password: string) => Promise; -- breachedAccounts: (username: string) => Promise; -+ passwordLeaked: (password: string) => Promise; -+ breachedAccounts: (username: string) => Promise; - } -diff --git a/jslib/common/src/abstractions/auth.service.ts b/jslib/common/src/abstractions/auth.service.ts -index 58f72fc7..10dff3a8 100644 ---- a/jslib/common/src/abstractions/auth.service.ts -+++ b/jslib/common/src/abstractions/auth.service.ts -@@ -1,35 +1,60 @@ --import { TwoFactorProviderType } from '../enums/twoFactorProviderType'; -+import { TwoFactorProviderType } from "../enums/twoFactorProviderType"; - --import { AuthResult } from '../models/domain/authResult'; --import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; -+import { AuthResult } from "../models/domain/authResult"; -+import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; - - export abstract class AuthService { -- email: string; -- masterPasswordHash: string; -- code: string; -- codeVerifier: string; -- ssoRedirectUrl: string; -- clientId: string; -- clientSecret: string; -- twoFactorProvidersData: Map; -- selectedTwoFactorProviderType: TwoFactorProviderType; -+ email: string; -+ masterPasswordHash: string; -+ code: string; -+ codeVerifier: string; -+ ssoRedirectUrl: string; -+ clientId: string; -+ clientSecret: string; -+ twoFactorProvidersData: Map; -+ selectedTwoFactorProviderType: TwoFactorProviderType; - -- logIn: (email: string, masterPassword: string, captchaToken?: string) => Promise; -- logInSso: (code: string, codeVerifier: string, redirectUrl: string, orgId: string) => Promise; -- logInApiKey: (clientId: string, clientSecret: string) => Promise; -- logInTwoFactor: (twoFactorProvider: TwoFactorProviderType, twoFactorToken: string, -- remember?: boolean) => Promise; -- logInComplete: (email: string, masterPassword: string, twoFactorProvider: TwoFactorProviderType, -- twoFactorToken: string, remember?: boolean, captchaToken?: string) => Promise; -- logInSsoComplete: (code: string, codeVerifier: string, redirectUrl: string, -- twoFactorProvider: TwoFactorProviderType, twoFactorToken: string, remember?: boolean) => Promise; -- logInApiKeyComplete: (clientId: string, clientSecret: string, twoFactorProvider: TwoFactorProviderType, -- twoFactorToken: string, remember?: boolean) => Promise; -- logOut: (callback: Function) => void; -- getSupportedTwoFactorProviders: (win: Window) => any[]; -- getDefaultTwoFactorProvider: (webAuthnSupported: boolean) => TwoFactorProviderType; -- makePreloginKey: (masterPassword: string, email: string) => Promise; -- authingWithApiKey: () => boolean; -- authingWithSso: () => boolean; -- authingWithPassword: () => boolean; -+ logIn: (email: string, masterPassword: string, captchaToken?: string) => Promise; -+ logInSso: ( -+ code: string, -+ codeVerifier: string, -+ redirectUrl: string, -+ orgId: string -+ ) => Promise; -+ logInApiKey: (clientId: string, clientSecret: string) => Promise; -+ logInTwoFactor: ( -+ twoFactorProvider: TwoFactorProviderType, -+ twoFactorToken: string, -+ remember?: boolean -+ ) => Promise; -+ logInComplete: ( -+ email: string, -+ masterPassword: string, -+ twoFactorProvider: TwoFactorProviderType, -+ twoFactorToken: string, -+ remember?: boolean, -+ captchaToken?: string -+ ) => Promise; -+ logInSsoComplete: ( -+ code: string, -+ codeVerifier: string, -+ redirectUrl: string, -+ twoFactorProvider: TwoFactorProviderType, -+ twoFactorToken: string, -+ remember?: boolean -+ ) => Promise; -+ logInApiKeyComplete: ( -+ clientId: string, -+ clientSecret: string, -+ twoFactorProvider: TwoFactorProviderType, -+ twoFactorToken: string, -+ remember?: boolean -+ ) => Promise; -+ logOut: (callback: Function) => void; -+ getSupportedTwoFactorProviders: (win: Window) => any[]; -+ getDefaultTwoFactorProvider: (webAuthnSupported: boolean) => TwoFactorProviderType; -+ makePreloginKey: (masterPassword: string, email: string) => Promise; -+ authingWithApiKey: () => boolean; -+ authingWithSso: () => boolean; -+ authingWithPassword: () => boolean; - } -diff --git a/jslib/common/src/abstractions/biometric.main.ts b/jslib/common/src/abstractions/biometric.main.ts -index 65a4eb5c..a8557c40 100644 ---- a/jslib/common/src/abstractions/biometric.main.ts -+++ b/jslib/common/src/abstractions/biometric.main.ts -@@ -1,6 +1,6 @@ - export abstract class BiometricMain { -- isError: boolean; -- init: () => Promise; -- supportsBiometric: () => Promise; -- authenticateBiometric: () => Promise; -+ isError: boolean; -+ init: () => Promise; -+ supportsBiometric: () => Promise; -+ authenticateBiometric: () => Promise; - } -diff --git a/jslib/common/src/abstractions/broadcaster.service.ts b/jslib/common/src/abstractions/broadcaster.service.ts -index 9d0c8de9..1b9d0899 100644 ---- a/jslib/common/src/abstractions/broadcaster.service.ts -+++ b/jslib/common/src/abstractions/broadcaster.service.ts -@@ -1,5 +1,5 @@ - export abstract class BroadcasterService { -- send: (message: any, id?: string) => void; -- subscribe: (id: string, messageCallback: (message: any) => any) => void; -- unsubscribe: (id: string) => void; -+ send: (message: any, id?: string) => void; -+ subscribe: (id: string, messageCallback: (message: any) => any) => void; -+ unsubscribe: (id: string) => void; - } -diff --git a/jslib/common/src/abstractions/cipher.service.ts b/jslib/common/src/abstractions/cipher.service.ts -index 941c3826..c1c1437a 100644 ---- a/jslib/common/src/abstractions/cipher.service.ts -+++ b/jslib/common/src/abstractions/cipher.service.ts -@@ -1,59 +1,82 @@ --import { CipherType } from '../enums/cipherType'; --import { UriMatchType } from '../enums/uriMatchType'; -+import { CipherType } from "../enums/cipherType"; -+import { UriMatchType } from "../enums/uriMatchType"; - --import { CipherData } from '../models/data/cipherData'; -+import { CipherData } from "../models/data/cipherData"; - --import { Cipher } from '../models/domain/cipher'; --import { Field } from '../models/domain/field'; --import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; -+import { Cipher } from "../models/domain/cipher"; -+import { Field } from "../models/domain/field"; -+import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; - --import { CipherView } from '../models/view/cipherView'; --import { FieldView } from '../models/view/fieldView'; -+import { CipherView } from "../models/view/cipherView"; -+import { FieldView } from "../models/view/fieldView"; - - export abstract class CipherService { -- decryptedCipherCache: CipherView[]; -- -- clearCache: () => void; -- encrypt: (model: CipherView, key?: SymmetricCryptoKey, originalCipher?: Cipher) => Promise; -- encryptFields: (fieldsModel: FieldView[], key: SymmetricCryptoKey) => Promise; -- encryptField: (fieldModel: FieldView, key: SymmetricCryptoKey) => Promise; -- get: (id: string) => Promise; -- getAll: () => Promise; -- getAllDecrypted: () => Promise; -- getAllDecryptedForGrouping: (groupingId: string, folder?: boolean) => Promise; -- getAllDecryptedForUrl: (url: string, includeOtherTypes?: CipherType[], -- defaultMatch?: UriMatchType) => Promise; -- getAllFromApiForOrganization: (organizationId: string) => Promise; -- getLastUsedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise; -- getLastLaunchedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise; -- getNextCipherForUrl: (url: string) => Promise; -- updateLastUsedIndexForUrl: (url: string) => void; -- updateLastUsedDate: (id: string) => Promise; -- updateLastLaunchedDate: (id: string) => Promise; -- saveNeverDomain: (domain: string) => Promise; -- saveWithServer: (cipher: Cipher) => Promise; -- shareWithServer: (cipher: CipherView, organizationId: string, collectionIds: string[]) => Promise; -- shareManyWithServer: (ciphers: CipherView[], organizationId: string, collectionIds: string[]) => Promise; -- saveAttachmentWithServer: (cipher: Cipher, unencryptedFile: any, admin?: boolean) => Promise; -- saveAttachmentRawWithServer: (cipher: Cipher, filename: string, data: ArrayBuffer, -- admin?: boolean) => Promise; -- saveCollectionsWithServer: (cipher: Cipher) => Promise; -- upsert: (cipher: CipherData | CipherData[]) => Promise; -- replace: (ciphers: { [id: string]: CipherData; }) => Promise; -- clear: (userId: string) => Promise; -- moveManyWithServer: (ids: string[], folderId: string) => Promise; -- delete: (id: string | string[]) => Promise; -- deleteWithServer: (id: string) => Promise; -- deleteManyWithServer: (ids: string[]) => Promise; -- deleteAttachment: (id: string, attachmentId: string) => Promise; -- deleteAttachmentWithServer: (id: string, attachmentId: string) => Promise; -- sortCiphersByLastUsed: (a: any, b: any) => number; -- sortCiphersByLastUsedThenName: (a: any, b: any) => number; -- getLocaleSortingFunction: () => (a: CipherView, b: CipherView) => number; -- softDelete: (id: string | string[]) => Promise; -- softDeleteWithServer: (id: string) => Promise; -- softDeleteManyWithServer: (ids: string[]) => Promise; -- restore: (cipher: { id: string, revisionDate: string; } | { id: string, revisionDate: string; }[]) => Promise; -- restoreWithServer: (id: string) => Promise; -- restoreManyWithServer: (ids: string[]) => Promise; -+ clearCache: (userId?: string) => Promise; -+ encrypt: ( -+ model: CipherView, -+ key?: SymmetricCryptoKey, -+ originalCipher?: Cipher -+ ) => Promise; -+ encryptFields: (fieldsModel: FieldView[], key: SymmetricCryptoKey) => Promise; -+ encryptField: (fieldModel: FieldView, key: SymmetricCryptoKey) => Promise; -+ get: (id: string) => Promise; -+ getAll: () => Promise; -+ getAllDecrypted: () => Promise; -+ getAllDecryptedForGrouping: (groupingId: string, folder?: boolean) => Promise; -+ getAllDecryptedForUrl: ( -+ url: string, -+ includeOtherTypes?: CipherType[], -+ defaultMatch?: UriMatchType -+ ) => Promise; -+ getAllFromApiForOrganization: (organizationId: string) => Promise; -+ getLastUsedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise; -+ getLastLaunchedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise; -+ getNextCipherForUrl: (url: string) => Promise; -+ updateLastUsedIndexForUrl: (url: string) => void; -+ updateLastUsedDate: (id: string) => Promise; -+ updateLastLaunchedDate: (id: string) => Promise; -+ saveNeverDomain: (domain: string) => Promise; -+ saveWithServer: (cipher: Cipher) => Promise; -+ shareWithServer: ( -+ cipher: CipherView, -+ organizationId: string, -+ collectionIds: string[] -+ ) => Promise; -+ shareManyWithServer: ( -+ ciphers: CipherView[], -+ organizationId: string, -+ collectionIds: string[] -+ ) => Promise; -+ saveAttachmentWithServer: ( -+ cipher: Cipher, -+ unencryptedFile: any, -+ admin?: boolean -+ ) => Promise; -+ saveAttachmentRawWithServer: ( -+ cipher: Cipher, -+ filename: string, -+ data: ArrayBuffer, -+ admin?: boolean -+ ) => Promise; -+ saveCollectionsWithServer: (cipher: Cipher) => Promise; -+ upsert: (cipher: CipherData | CipherData[]) => Promise; -+ replace: (ciphers: { [id: string]: CipherData }) => Promise; -+ clear: (userId: string) => Promise; -+ moveManyWithServer: (ids: string[], folderId: string) => Promise; -+ delete: (id: string | string[]) => Promise; -+ deleteWithServer: (id: string) => Promise; -+ deleteManyWithServer: (ids: string[]) => Promise; -+ deleteAttachment: (id: string, attachmentId: string) => Promise; -+ deleteAttachmentWithServer: (id: string, attachmentId: string) => Promise; -+ sortCiphersByLastUsed: (a: any, b: any) => number; -+ sortCiphersByLastUsedThenName: (a: any, b: any) => number; -+ getLocaleSortingFunction: () => (a: CipherView, b: CipherView) => number; -+ softDelete: (id: string | string[]) => Promise; -+ softDeleteWithServer: (id: string) => Promise; -+ softDeleteManyWithServer: (ids: string[]) => Promise; -+ restore: ( -+ cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[] -+ ) => Promise; -+ restoreWithServer: (id: string) => Promise; -+ restoreManyWithServer: (ids: string[]) => Promise; - } -diff --git a/jslib/common/src/abstractions/collection.service.ts b/jslib/common/src/abstractions/collection.service.ts -index 09045537..f5880e47 100644 ---- a/jslib/common/src/abstractions/collection.service.ts -+++ b/jslib/common/src/abstractions/collection.service.ts -@@ -1,23 +1,21 @@ --import { CollectionData } from '../models/data/collectionData'; -+import { CollectionData } from "../models/data/collectionData"; - --import { Collection } from '../models/domain/collection'; --import { TreeNode } from '../models/domain/treeNode'; -+import { Collection } from "../models/domain/collection"; -+import { TreeNode } from "../models/domain/treeNode"; - --import { CollectionView } from '../models/view/collectionView'; -+import { CollectionView } from "../models/view/collectionView"; - - export abstract class CollectionService { -- decryptedCollectionCache: CollectionView[]; -- -- clearCache: () => void; -- encrypt: (model: CollectionView) => Promise; -- decryptMany: (collections: Collection[]) => Promise; -- get: (id: string) => Promise; -- getAll: () => Promise; -- getAllDecrypted: () => Promise; -- getAllNested: (collections?: CollectionView[]) => Promise[]>; -- getNested: (id: string) => Promise>; -- upsert: (collection: CollectionData | CollectionData[]) => Promise; -- replace: (collections: { [id: string]: CollectionData; }) => Promise; -- clear: (userId: string) => Promise; -- delete: (id: string | string[]) => Promise; -+ clearCache: (userId?: string) => Promise; -+ encrypt: (model: CollectionView) => Promise; -+ decryptMany: (collections: Collection[]) => Promise; -+ get: (id: string) => Promise; -+ getAll: () => Promise; -+ getAllDecrypted: () => Promise; -+ getAllNested: (collections?: CollectionView[]) => Promise[]>; -+ getNested: (id: string) => Promise>; -+ upsert: (collection: CollectionData | CollectionData[]) => Promise; -+ replace: (collections: { [id: string]: CollectionData }) => Promise; -+ clear: (userId: string) => Promise; -+ delete: (id: string | string[]) => Promise; - } -diff --git a/jslib/common/src/abstractions/crypto.service.ts b/jslib/common/src/abstractions/crypto.service.ts -index 841e61ab..3397d0d5 100644 ---- a/jslib/common/src/abstractions/crypto.service.ts -+++ b/jslib/common/src/abstractions/crypto.service.ts -@@ -1,64 +1,88 @@ --import { EncArrayBuffer } from '../models/domain/encArrayBuffer'; --import { EncString } from '../models/domain/encString'; --import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; -+import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; -+import { EncString } from "../models/domain/encString"; -+import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; - --import { ProfileOrganizationResponse } from '../models/response/profileOrganizationResponse'; --import { ProfileProviderOrganizationResponse } from '../models/response/profileProviderOrganizationResponse'; --import { ProfileProviderResponse } from '../models/response/profileProviderResponse'; -+import { ProfileOrganizationResponse } from "../models/response/profileOrganizationResponse"; -+import { ProfileProviderOrganizationResponse } from "../models/response/profileProviderOrganizationResponse"; -+import { ProfileProviderResponse } from "../models/response/profileProviderResponse"; - --import { HashPurpose } from '../enums/hashPurpose'; --import { KdfType } from '../enums/kdfType'; -- --import { KeySuffixOptions } from './storage.service'; -+import { HashPurpose } from "../enums/hashPurpose"; -+import { KdfType } from "../enums/kdfType"; -+import { KeySuffixOptions } from "../enums/keySuffixOptions"; - - export abstract class CryptoService { -- setKey: (key: SymmetricCryptoKey) => Promise; -- setKeyHash: (keyHash: string) => Promise<{}>; -- setEncKey: (encKey: string) => Promise<{}>; -- setEncPrivateKey: (encPrivateKey: string) => Promise<{}>; -- setOrgKeys: (orgs: ProfileOrganizationResponse[], providerOrgs: ProfileProviderOrganizationResponse[]) => Promise<{}>; -- setProviderKeys: (orgs: ProfileProviderResponse[]) => Promise<{}>; -- getKey: (keySuffix?: KeySuffixOptions) => Promise; -- getKeyFromStorage: (keySuffix: KeySuffixOptions) => Promise; -- getKeyHash: () => Promise; -- compareAndUpdateKeyHash: (masterPassword: string, key: SymmetricCryptoKey) => Promise; -- getEncKey: (key?: SymmetricCryptoKey) => Promise; -- getPublicKey: () => Promise; -- getPrivateKey: () => Promise; -- getFingerprint: (userId: string, publicKey?: ArrayBuffer) => Promise; -- getOrgKeys: () => Promise>; -- getOrgKey: (orgId: string) => Promise; -- getProviderKey: (providerId: string) => Promise; -- hasKey: () => Promise; -- hasKeyInMemory: () => boolean; -- hasKeyStored: (keySuffix?: KeySuffixOptions) => Promise; -- hasEncKey: () => Promise; -- clearKey: (clearSecretStorage?: boolean) => Promise; -- clearKeyHash: () => Promise; -- clearEncKey: (memoryOnly?: boolean) => Promise; -- clearKeyPair: (memoryOnly?: boolean) => Promise; -- clearOrgKeys: (memoryOnly?: boolean) => Promise; -- clearProviderKeys: (memoryOnly?: boolean) => Promise; -- clearPinProtectedKey: () => Promise; -- clearKeys: () => Promise; -- toggleKey: () => Promise; -- makeKey: (password: string, salt: string, kdf: KdfType, kdfIterations: number) => Promise; -- makeKeyFromPin: (pin: string, salt: string, kdf: KdfType, kdfIterations: number, -- protectedKeyCs?: EncString) => Promise; -- makeShareKey: () => Promise<[EncString, SymmetricCryptoKey]>; -- makeKeyPair: (key?: SymmetricCryptoKey) => Promise<[string, EncString]>; -- makePinKey: (pin: string, salt: string, kdf: KdfType, kdfIterations: number) => Promise; -- makeSendKey: (keyMaterial: ArrayBuffer) => Promise; -- hashPassword: (password: string, key: SymmetricCryptoKey, hashPurpose?: HashPurpose) => Promise; -- makeEncKey: (key: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, EncString]>; -- remakeEncKey: (key: SymmetricCryptoKey, encKey?: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, EncString]>; -- encrypt: (plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey) => Promise; -- encryptToBytes: (plainValue: ArrayBuffer, key?: SymmetricCryptoKey) => Promise; -- rsaEncrypt: (data: ArrayBuffer, publicKey?: ArrayBuffer) => Promise; -- rsaDecrypt: (encValue: string, privateKeyValue?: ArrayBuffer) => Promise; -- decryptToBytes: (encString: EncString, key?: SymmetricCryptoKey) => Promise; -- decryptToUtf8: (encString: EncString, key?: SymmetricCryptoKey) => Promise; -- decryptFromBytes: (encBuf: ArrayBuffer, key: SymmetricCryptoKey) => Promise; -- randomNumber: (min: number, max: number) => Promise; -- validateKey: (key: SymmetricCryptoKey) => Promise; -+ setKey: (key: SymmetricCryptoKey) => Promise; -+ setKeyHash: (keyHash: string) => Promise; -+ setEncKey: (encKey: string) => Promise; -+ setEncPrivateKey: (encPrivateKey: string) => Promise; -+ setOrgKeys: ( -+ orgs: ProfileOrganizationResponse[], -+ providerOrgs: ProfileProviderOrganizationResponse[] -+ ) => Promise; -+ setProviderKeys: (orgs: ProfileProviderResponse[]) => Promise; -+ getKey: (keySuffix?: KeySuffixOptions, userId?: string) => Promise; -+ getKeyFromStorage: (keySuffix: KeySuffixOptions, userId?: string) => Promise; -+ getKeyHash: () => Promise; -+ compareAndUpdateKeyHash: (masterPassword: string, key: SymmetricCryptoKey) => Promise; -+ getEncKey: (key?: SymmetricCryptoKey) => Promise; -+ getPublicKey: () => Promise; -+ getPrivateKey: () => Promise; -+ getFingerprint: (userId: string, publicKey?: ArrayBuffer) => Promise; -+ getOrgKeys: () => Promise>; -+ getOrgKey: (orgId: string) => Promise; -+ getProviderKey: (providerId: string) => Promise; -+ hasKey: () => Promise; -+ hasKeyInMemory: (userId?: string) => Promise; -+ hasKeyStored: (keySuffix?: KeySuffixOptions, userId?: string) => Promise; -+ hasEncKey: () => Promise; -+ clearKey: (clearSecretStorage?: boolean, userId?: string) => Promise; -+ clearKeyHash: () => Promise; -+ clearEncKey: (memoryOnly?: boolean, userId?: string) => Promise; -+ clearKeyPair: (memoryOnly?: boolean, userId?: string) => Promise; -+ clearOrgKeys: (memoryOnly?: boolean, userId?: string) => Promise; -+ clearProviderKeys: (memoryOnly?: boolean) => Promise; -+ clearPinProtectedKey: () => Promise; -+ clearKeys: (userId?: string) => Promise; -+ toggleKey: () => Promise; -+ makeKey: ( -+ password: string, -+ salt: string, -+ kdf: KdfType, -+ kdfIterations: number -+ ) => Promise; -+ makeKeyFromPin: ( -+ pin: string, -+ salt: string, -+ kdf: KdfType, -+ kdfIterations: number, -+ protectedKeyCs?: EncString -+ ) => Promise; -+ makeShareKey: () => Promise<[EncString, SymmetricCryptoKey]>; -+ makeKeyPair: (key?: SymmetricCryptoKey) => Promise<[string, EncString]>; -+ makePinKey: ( -+ pin: string, -+ salt: string, -+ kdf: KdfType, -+ kdfIterations: number -+ ) => Promise; -+ makeSendKey: (keyMaterial: ArrayBuffer) => Promise; -+ hashPassword: ( -+ password: string, -+ key: SymmetricCryptoKey, -+ hashPurpose?: HashPurpose -+ ) => Promise; -+ makeEncKey: (key: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, EncString]>; -+ remakeEncKey: ( -+ key: SymmetricCryptoKey, -+ encKey?: SymmetricCryptoKey -+ ) => Promise<[SymmetricCryptoKey, EncString]>; -+ encrypt: (plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey) => Promise; -+ encryptToBytes: (plainValue: ArrayBuffer, key?: SymmetricCryptoKey) => Promise; -+ rsaEncrypt: (data: ArrayBuffer, publicKey?: ArrayBuffer) => Promise; -+ rsaDecrypt: (encValue: string, privateKeyValue?: ArrayBuffer) => Promise; -+ decryptToBytes: (encString: EncString, key?: SymmetricCryptoKey) => Promise; -+ decryptToUtf8: (encString: EncString, key?: SymmetricCryptoKey) => Promise; -+ decryptFromBytes: (encBuf: ArrayBuffer, key: SymmetricCryptoKey) => Promise; -+ randomNumber: (min: number, max: number) => Promise; -+ validateKey: (key: SymmetricCryptoKey) => Promise; - } -diff --git a/jslib/common/src/abstractions/cryptoFunction.service.ts b/jslib/common/src/abstractions/cryptoFunction.service.ts -index 7573050c..21ed33cb 100644 ---- a/jslib/common/src/abstractions/cryptoFunction.service.ts -+++ b/jslib/common/src/abstractions/cryptoFunction.service.ts -@@ -1,27 +1,62 @@ --import { DecryptParameters } from '../models/domain/decryptParameters'; --import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; -+import { DecryptParameters } from "../models/domain/decryptParameters"; -+import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; - - export abstract class CryptoFunctionService { -- pbkdf2: (password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512', -- iterations: number) => Promise; -- hkdf: (ikm: ArrayBuffer, salt: string | ArrayBuffer, info: string | ArrayBuffer, -- outputByteSize: number, algorithm: 'sha256' | 'sha512') => Promise; -- hkdfExpand: (prk: ArrayBuffer, info: string | ArrayBuffer, outputByteSize: number, -- algorithm: 'sha256' | 'sha512') => Promise; -- hash: (value: string | ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512' | 'md5') => Promise; -- hmac: (value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512') => Promise; -- compare: (a: ArrayBuffer, b: ArrayBuffer) => Promise; -- hmacFast: (value: ArrayBuffer | string, key: ArrayBuffer | string, algorithm: 'sha1' | 'sha256' | 'sha512') => -- Promise; -- compareFast: (a: ArrayBuffer | string, b: ArrayBuffer | string) => Promise; -- aesEncrypt: (data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer) => Promise; -- aesDecryptFastParameters: (data: string, iv: string, mac: string, key: SymmetricCryptoKey) => -- DecryptParameters; -- aesDecryptFast: (parameters: DecryptParameters) => Promise; -- aesDecrypt: (data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer) => Promise; -- rsaEncrypt: (data: ArrayBuffer, publicKey: ArrayBuffer, algorithm: 'sha1' | 'sha256') => Promise; -- rsaDecrypt: (data: ArrayBuffer, privateKey: ArrayBuffer, algorithm: 'sha1' | 'sha256') => Promise; -- rsaExtractPublicKey: (privateKey: ArrayBuffer) => Promise; -- rsaGenerateKeyPair: (length: 1024 | 2048 | 4096) => Promise<[ArrayBuffer, ArrayBuffer]>; -- randomBytes: (length: number) => Promise; -+ pbkdf2: ( -+ password: string | ArrayBuffer, -+ salt: string | ArrayBuffer, -+ algorithm: "sha256" | "sha512", -+ iterations: number -+ ) => Promise; -+ hkdf: ( -+ ikm: ArrayBuffer, -+ salt: string | ArrayBuffer, -+ info: string | ArrayBuffer, -+ outputByteSize: number, -+ algorithm: "sha256" | "sha512" -+ ) => Promise; -+ hkdfExpand: ( -+ prk: ArrayBuffer, -+ info: string | ArrayBuffer, -+ outputByteSize: number, -+ algorithm: "sha256" | "sha512" -+ ) => Promise; -+ hash: ( -+ value: string | ArrayBuffer, -+ algorithm: "sha1" | "sha256" | "sha512" | "md5" -+ ) => Promise; -+ hmac: ( -+ value: ArrayBuffer, -+ key: ArrayBuffer, -+ algorithm: "sha1" | "sha256" | "sha512" -+ ) => Promise; -+ compare: (a: ArrayBuffer, b: ArrayBuffer) => Promise; -+ hmacFast: ( -+ value: ArrayBuffer | string, -+ key: ArrayBuffer | string, -+ algorithm: "sha1" | "sha256" | "sha512" -+ ) => Promise; -+ compareFast: (a: ArrayBuffer | string, b: ArrayBuffer | string) => Promise; -+ aesEncrypt: (data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer) => Promise; -+ aesDecryptFastParameters: ( -+ data: string, -+ iv: string, -+ mac: string, -+ key: SymmetricCryptoKey -+ ) => DecryptParameters; -+ aesDecryptFast: (parameters: DecryptParameters) => Promise; -+ aesDecrypt: (data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer) => Promise; -+ rsaEncrypt: ( -+ data: ArrayBuffer, -+ publicKey: ArrayBuffer, -+ algorithm: "sha1" | "sha256" -+ ) => Promise; -+ rsaDecrypt: ( -+ data: ArrayBuffer, -+ privateKey: ArrayBuffer, -+ algorithm: "sha1" | "sha256" -+ ) => Promise; -+ rsaExtractPublicKey: (privateKey: ArrayBuffer) => Promise; -+ rsaGenerateKeyPair: (length: 1024 | 2048 | 4096) => Promise<[ArrayBuffer, ArrayBuffer]>; -+ randomBytes: (length: number) => Promise; - } -diff --git a/jslib/common/src/abstractions/environment.service.ts b/jslib/common/src/abstractions/environment.service.ts -index db335016..8398b4c6 100644 ---- a/jslib/common/src/abstractions/environment.service.ts -+++ b/jslib/common/src/abstractions/environment.service.ts -@@ -1,34 +1,34 @@ --import { Observable } from 'rxjs'; -+import { Observable } from "rxjs"; - - export type Urls = { -- base?: string; -- webVault?: string; -- api?: string; -- identity?: string; -- icons?: string; -- notifications?: string; -- events?: string; -- keyConnector?: string; -+ base?: string; -+ webVault?: string; -+ api?: string; -+ identity?: string; -+ icons?: string; -+ notifications?: string; -+ events?: string; -+ keyConnector?: string; - }; - - export type PayPalConfig = { -- businessId?: string; -- buttonAction?: string; -+ businessId?: string; -+ buttonAction?: string; - }; - - export abstract class EnvironmentService { -- urls: Observable; -+ urls: Observable; - -- hasBaseUrl: () => boolean; -- getNotificationsUrl: () => string; -- getWebVaultUrl: () => string; -- getSendUrl: () => string; -- getIconsUrl: () => string; -- getApiUrl: () => string; -- getIdentityUrl: () => string; -- getEventsUrl: () => string; -- getKeyConnectorUrl: () => string; -- setUrlsFromStorage: () => Promise; -- setUrls: (urls: any, saveSettings?: boolean) => Promise; -- getUrls: () => Urls; -+ hasBaseUrl: () => boolean; -+ getNotificationsUrl: () => string; -+ getWebVaultUrl: () => string; -+ getSendUrl: () => string; -+ getIconsUrl: () => string; -+ getApiUrl: () => string; -+ getIdentityUrl: () => string; -+ getEventsUrl: () => string; -+ getKeyConnectorUrl: () => string; -+ setUrlsFromStorage: () => Promise; -+ setUrls: (urls: Urls) => Promise; -+ getUrls: () => Urls; - } -diff --git a/jslib/common/src/abstractions/event.service.ts b/jslib/common/src/abstractions/event.service.ts -index 40c08027..2f7660fb 100644 ---- a/jslib/common/src/abstractions/event.service.ts -+++ b/jslib/common/src/abstractions/event.service.ts -@@ -1,7 +1,7 @@ --import { EventType } from '../enums/eventType'; -+import { EventType } from "../enums/eventType"; - - export abstract class EventService { -- collect: (eventType: EventType, cipherId?: string, uploadImmediately?: boolean) => Promise; -- uploadEvents: () => Promise; -- clearEvents: () => Promise; -+ collect: (eventType: EventType, cipherId?: string, uploadImmediately?: boolean) => Promise; -+ uploadEvents: (userId?: string) => Promise; -+ clearEvents: (userId?: string) => Promise; - } -diff --git a/jslib/common/src/abstractions/export.service.ts b/jslib/common/src/abstractions/export.service.ts -index 4e23823a..fa0b63ff 100644 ---- a/jslib/common/src/abstractions/export.service.ts -+++ b/jslib/common/src/abstractions/export.service.ts -@@ -1,8 +1,11 @@ --import { EventView } from '../models/view/eventView'; -+import { EventView } from "../models/view/eventView"; - - export abstract class ExportService { -- getExport: (format?: 'csv' | 'json' | 'encrypted_json') => Promise; -- getOrganizationExport: (organizationId: string, format?: 'csv' | 'json' | 'encrypted_json') => Promise; -- getEventExport: (events: EventView[]) => Promise; -- getFileName: (prefix?: string, extension?: string) => string; -+ getExport: (format?: "csv" | "json" | "encrypted_json") => Promise; -+ getOrganizationExport: ( -+ organizationId: string, -+ format?: "csv" | "json" | "encrypted_json" -+ ) => Promise; -+ getEventExport: (events: EventView[]) => Promise; -+ getFileName: (prefix?: string, extension?: string) => string; - } -diff --git a/jslib/common/src/abstractions/fileUpload.service.ts b/jslib/common/src/abstractions/fileUpload.service.ts -index b24ed9f0..fcd3c22c 100644 ---- a/jslib/common/src/abstractions/fileUpload.service.ts -+++ b/jslib/common/src/abstractions/fileUpload.service.ts -@@ -1,11 +1,18 @@ --import { EncArrayBuffer } from '../models/domain/encArrayBuffer'; --import { EncString } from '../models/domain/encString'; --import { AttachmentUploadDataResponse } from '../models/response/attachmentUploadDataResponse'; --import { SendFileUploadDataResponse } from '../models/response/sendFileUploadDataResponse'; -+import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; -+import { EncString } from "../models/domain/encString"; -+import { AttachmentUploadDataResponse } from "../models/response/attachmentUploadDataResponse"; -+import { SendFileUploadDataResponse } from "../models/response/sendFileUploadDataResponse"; - - export abstract class FileUploadService { -- uploadSendFile: (uploadData: SendFileUploadDataResponse, fileName: EncString, -- encryptedFileData: EncArrayBuffer) => Promise; -- uploadCipherAttachment: (admin: boolean, uploadData: AttachmentUploadDataResponse, fileName: EncString, -- encryptedFileData: EncArrayBuffer) => Promise; -+ uploadSendFile: ( -+ uploadData: SendFileUploadDataResponse, -+ fileName: EncString, -+ encryptedFileData: EncArrayBuffer -+ ) => Promise; -+ uploadCipherAttachment: ( -+ admin: boolean, -+ uploadData: AttachmentUploadDataResponse, -+ fileName: EncString, -+ encryptedFileData: EncArrayBuffer -+ ) => Promise; - } -diff --git a/jslib/common/src/abstractions/folder.service.ts b/jslib/common/src/abstractions/folder.service.ts -index f1997ffe..139525e0 100644 ---- a/jslib/common/src/abstractions/folder.service.ts -+++ b/jslib/common/src/abstractions/folder.service.ts -@@ -1,25 +1,23 @@ --import { FolderData } from '../models/data/folderData'; -+import { FolderData } from "../models/data/folderData"; - --import { Folder } from '../models/domain/folder'; --import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; --import { TreeNode } from '../models/domain/treeNode'; -+import { Folder } from "../models/domain/folder"; -+import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; -+import { TreeNode } from "../models/domain/treeNode"; - --import { FolderView } from '../models/view/folderView'; -+import { FolderView } from "../models/view/folderView"; - - export abstract class FolderService { -- decryptedFolderCache: FolderView[]; -- -- clearCache: () => void; -- encrypt: (model: FolderView, key?: SymmetricCryptoKey) => Promise; -- get: (id: string) => Promise; -- getAll: () => Promise; -- getAllDecrypted: () => Promise; -- getAllNested: () => Promise[]>; -- getNested: (id: string) => Promise>; -- saveWithServer: (folder: Folder) => Promise; -- upsert: (folder: FolderData | FolderData[]) => Promise; -- replace: (folders: { [id: string]: FolderData; }) => Promise; -- clear: (userId: string) => Promise; -- delete: (id: string | string[]) => Promise; -- deleteWithServer: (id: string) => Promise; -+ clearCache: (userId?: string) => Promise; -+ encrypt: (model: FolderView, key?: SymmetricCryptoKey) => Promise; -+ get: (id: string) => Promise; -+ getAll: () => Promise; -+ getAllDecrypted: () => Promise; -+ getAllNested: () => Promise[]>; -+ getNested: (id: string) => Promise>; -+ saveWithServer: (folder: Folder) => Promise; -+ upsert: (folder: FolderData | FolderData[]) => Promise; -+ replace: (folders: { [id: string]: FolderData }) => Promise; -+ clear: (userId: string) => Promise; -+ delete: (id: string | string[]) => Promise; -+ deleteWithServer: (id: string) => Promise; - } -diff --git a/jslib/common/src/abstractions/i18n.service.ts b/jslib/common/src/abstractions/i18n.service.ts -index d53494dd..4706e0d1 100644 ---- a/jslib/common/src/abstractions/i18n.service.ts -+++ b/jslib/common/src/abstractions/i18n.service.ts -@@ -1,9 +1,9 @@ - export abstract class I18nService { -- locale: string; -- supportedTranslationLocales: string[]; -- translationLocale: string; -- collator: Intl.Collator; -- localeNames: Map; -- t: (id: string, p1?: string, p2?: string, p3?: string) => string; -- translate: (id: string, p1?: string, p2?: string, p3?: string) => string; -+ locale: string; -+ supportedTranslationLocales: string[]; -+ translationLocale: string; -+ collator: Intl.Collator; -+ localeNames: Map; -+ t: (id: string, p1?: string, p2?: string, p3?: string) => string; -+ translate: (id: string, p1?: string, p2?: string, p3?: string) => string; - } -diff --git a/jslib/common/src/abstractions/import.service.ts b/jslib/common/src/abstractions/import.service.ts -index 8115c2c0..e9c92a18 100644 ---- a/jslib/common/src/abstractions/import.service.ts -+++ b/jslib/common/src/abstractions/import.service.ts -@@ -1,13 +1,13 @@ --import { Importer } from '../importers/importer'; -+import { Importer } from "../importers/importer"; - - export interface ImportOption { -- id: string; -- name: string; -+ id: string; -+ name: string; - } - export abstract class ImportService { -- featuredImportOptions: ImportOption[]; -- regularImportOptions: ImportOption[]; -- getImportOptions: () => ImportOption[]; -- import: (importer: Importer, fileContents: string, organizationId?: string) => Promise; -- getImporter: (format: string, organizationId: string) => Importer; -+ featuredImportOptions: ImportOption[]; -+ regularImportOptions: ImportOption[]; -+ getImportOptions: () => ImportOption[]; -+ import: (importer: Importer, fileContents: string, organizationId?: string) => Promise; -+ getImporter: (format: string, organizationId: string) => Importer; - } -diff --git a/jslib/common/src/abstractions/keyConnector.service.ts b/jslib/common/src/abstractions/keyConnector.service.ts -index a4fbbf5a..ca57bbf6 100644 ---- a/jslib/common/src/abstractions/keyConnector.service.ts -+++ b/jslib/common/src/abstractions/keyConnector.service.ts -@@ -1,14 +1,14 @@ --import { Organization } from '../models/domain/organization'; -+import { Organization } from "../models/domain/organization"; - - export abstract class KeyConnectorService { -- getAndSetKey: (url?: string) => Promise; -- getManagingOrganization: () => Promise; -- getUsesKeyConnector: () => Promise; -- migrateUser: () => Promise; -- userNeedsMigration: () => Promise; -- setUsesKeyConnector: (enabled: boolean) => Promise; -- setConvertAccountRequired: (status: boolean) => Promise; -- getConvertAccountRequired: () => Promise; -- removeConvertAccountRequired: () => Promise; -- clear: () => Promise; -+ getAndSetKey: (url?: string) => Promise; -+ getManagingOrganization: () => Promise; -+ getUsesKeyConnector: () => Promise; -+ migrateUser: () => Promise; -+ userNeedsMigration: () => Promise; -+ setUsesKeyConnector: (enabled: boolean) => Promise; -+ setConvertAccountRequired: (status: boolean) => Promise; -+ getConvertAccountRequired: () => Promise; -+ removeConvertAccountRequired: () => Promise; -+ clear: () => Promise; - } -diff --git a/jslib/common/src/abstractions/log.service.ts b/jslib/common/src/abstractions/log.service.ts -index a46284dd..9997e480 100644 ---- a/jslib/common/src/abstractions/log.service.ts -+++ b/jslib/common/src/abstractions/log.service.ts -@@ -1,11 +1,11 @@ --import { LogLevelType } from '../enums/logLevelType'; -+import { LogLevelType } from "../enums/logLevelType"; - - export abstract class LogService { -- debug: (message: string) => void; -- info: (message: string) => void; -- warning: (message: string) => void; -- error: (message: string) => void; -- write: (level: LogLevelType, message: string) => void; -- time: (label: string) => void; -- timeEnd: (label: string) => [number, number]; -+ debug: (message: string) => void; -+ info: (message: string) => void; -+ warning: (message: string) => void; -+ error: (message: string) => void; -+ write: (level: LogLevelType, message: string) => void; -+ time: (label: string) => void; -+ timeEnd: (label: string) => [number, number]; - } -diff --git a/jslib/common/src/abstractions/messaging.service.ts b/jslib/common/src/abstractions/messaging.service.ts -index a38b4c7f..7c5f05f9 100644 ---- a/jslib/common/src/abstractions/messaging.service.ts -+++ b/jslib/common/src/abstractions/messaging.service.ts -@@ -1,3 +1,3 @@ - export abstract class MessagingService { -- send: (subscriber: string, arg?: any) => void; -+ send: (subscriber: string, arg?: any) => void; - } -diff --git a/jslib/common/src/abstractions/notifications.service.ts b/jslib/common/src/abstractions/notifications.service.ts -index 3e4c06e4..921e8e62 100644 ---- a/jslib/common/src/abstractions/notifications.service.ts -+++ b/jslib/common/src/abstractions/notifications.service.ts -@@ -1,6 +1,6 @@ - export abstract class NotificationsService { -- init: () => Promise; -- updateConnection: (sync?: boolean) => Promise; -- reconnectFromActivity: () => Promise; -- disconnectFromInactivity: () => Promise; -+ init: () => Promise; -+ updateConnection: (sync?: boolean) => Promise; -+ reconnectFromActivity: () => Promise; -+ disconnectFromInactivity: () => Promise; - } -diff --git a/jslib/common/src/abstractions/organization.service.ts b/jslib/common/src/abstractions/organization.service.ts -new file mode 100644 -index 00000000..a878335d ---- /dev/null -+++ b/jslib/common/src/abstractions/organization.service.ts -@@ -0,0 +1,11 @@ -+import { OrganizationData } from "../models/data/organizationData"; -+ -+import { Organization } from "../models/domain/organization"; -+ -+export abstract class OrganizationService { -+ get: (id: string) => Promise; -+ getByIdentifier: (identifier: string) => Promise; -+ getAll: (userId?: string) => Promise; -+ save: (orgs: { [id: string]: OrganizationData }) => Promise; -+ canManageSponsorships: () => Promise; -+} -diff --git a/jslib/common/src/abstractions/passwordGeneration.service.ts b/jslib/common/src/abstractions/passwordGeneration.service.ts -index 52d18dde..82bc021f 100644 ---- a/jslib/common/src/abstractions/passwordGeneration.service.ts -+++ b/jslib/common/src/abstractions/passwordGeneration.service.ts -@@ -1,18 +1,20 @@ --import * as zxcvbn from 'zxcvbn'; -+import * as zxcvbn from "zxcvbn"; - --import { GeneratedPasswordHistory } from '../models/domain/generatedPasswordHistory'; --import { PasswordGeneratorPolicyOptions } from '../models/domain/passwordGeneratorPolicyOptions'; -+import { GeneratedPasswordHistory } from "../models/domain/generatedPasswordHistory"; -+import { PasswordGeneratorPolicyOptions } from "../models/domain/passwordGeneratorPolicyOptions"; - - export abstract class PasswordGenerationService { -- generatePassword: (options: any) => Promise; -- generatePassphrase: (options: any) => Promise; -- getOptions: () => Promise<[any, PasswordGeneratorPolicyOptions]>; -- enforcePasswordGeneratorPoliciesOnOptions: (options: any) => Promise<[any, PasswordGeneratorPolicyOptions]>; -- getPasswordGeneratorPolicyOptions: () => Promise; -- saveOptions: (options: any) => Promise; -- getHistory: () => Promise; -- addHistory: (password: string) => Promise; -- clear: () => Promise; -- passwordStrength: (password: string, userInputs?: string[]) => zxcvbn.ZXCVBNResult; -- normalizeOptions: (options: any, enforcedPolicyOptions: PasswordGeneratorPolicyOptions) => void; -+ generatePassword: (options: any) => Promise; -+ generatePassphrase: (options: any) => Promise; -+ getOptions: () => Promise<[any, PasswordGeneratorPolicyOptions]>; -+ enforcePasswordGeneratorPoliciesOnOptions: ( -+ options: any -+ ) => Promise<[any, PasswordGeneratorPolicyOptions]>; -+ getPasswordGeneratorPolicyOptions: () => Promise; -+ saveOptions: (options: any) => Promise; -+ getHistory: () => Promise; -+ addHistory: (password: string) => Promise; -+ clear: (userId?: string) => Promise; -+ passwordStrength: (password: string, userInputs?: string[]) => zxcvbn.ZXCVBNResult; -+ normalizeOptions: (options: any, enforcedPolicyOptions: PasswordGeneratorPolicyOptions) => void; - } -diff --git a/jslib/common/src/abstractions/passwordReprompt.service.ts b/jslib/common/src/abstractions/passwordReprompt.service.ts -index 84ca84f5..6253425b 100644 ---- a/jslib/common/src/abstractions/passwordReprompt.service.ts -+++ b/jslib/common/src/abstractions/passwordReprompt.service.ts -@@ -1,5 +1,5 @@ - export abstract class PasswordRepromptService { -- protectedFields: () => string[]; -- showPasswordPrompt: () => Promise; -- enabled: () => Promise; -+ protectedFields: () => string[]; -+ showPasswordPrompt: () => Promise; -+ enabled: () => Promise; - } -diff --git a/jslib/common/src/abstractions/platformUtils.service.ts b/jslib/common/src/abstractions/platformUtils.service.ts -index 7f792e61..f5a6203d 100644 ---- a/jslib/common/src/abstractions/platformUtils.service.ts -+++ b/jslib/common/src/abstractions/platformUtils.service.ts -@@ -1,36 +1,52 @@ --import { DeviceType } from '../enums/deviceType'; --import { ThemeType } from '../enums/themeType'; -+import { DeviceType } from "../enums/deviceType"; -+import { ThemeType } from "../enums/themeType"; -+ -+interface ToastOptions { -+ timeout?: number; -+} - - export abstract class PlatformUtilsService { -- identityClientId: string; -- getDevice: () => DeviceType; -- getDeviceString: () => string; -- isFirefox: () => boolean; -- isChrome: () => boolean; -- isEdge: () => boolean; -- isOpera: () => boolean; -- isVivaldi: () => boolean; -- isSafari: () => boolean; -- isIE: () => boolean; -- isMacAppStore: () => boolean; -- isViewOpen: () => Promise; -- launchUri: (uri: string, options?: any) => void; -- saveFile: (win: Window, blobData: any, blobOptions: any, fileName: string) => void; -- getApplicationVersion: () => Promise; -- supportsWebAuthn: (win: Window) => boolean; -- supportsDuo: () => boolean; -- showToast: (type: 'error' | 'success' | 'warning' | 'info', title: string, text: string | string[], -- options?: any) => void; -- showDialog: (body: string, title?: string, confirmText?: string, cancelText?: string, -- type?: string, bodyIsHtml?: boolean) => Promise; -- isDev: () => boolean; -- isSelfHost: () => boolean; -- copyToClipboard: (text: string, options?: any) => void | boolean; -- readFromClipboard: (options?: any) => Promise; -- supportsBiometric: () => Promise; -- authenticateBiometric: () => Promise; -- getDefaultSystemTheme: () => Promise; -- onDefaultSystemThemeChange: (callback: ((theme: ThemeType.Light | ThemeType.Dark) => unknown)) => unknown; -- getEffectiveTheme: () => Promise; -- supportsSecureStorage: () => boolean; -+ identityClientId: string; -+ getDevice: () => DeviceType; -+ getDeviceString: () => string; -+ isFirefox: () => boolean; -+ isChrome: () => boolean; -+ isEdge: () => boolean; -+ isOpera: () => boolean; -+ isVivaldi: () => boolean; -+ isSafari: () => boolean; -+ isIE: () => boolean; -+ isMacAppStore: () => boolean; -+ isViewOpen: () => Promise; -+ launchUri: (uri: string, options?: any) => void; -+ saveFile: (win: Window, blobData: any, blobOptions: any, fileName: string) => void; -+ getApplicationVersion: () => Promise; -+ supportsWebAuthn: (win: Window) => boolean; -+ supportsDuo: () => boolean; -+ showToast: ( -+ type: "error" | "success" | "warning" | "info", -+ title: string, -+ text: string | string[], -+ options?: ToastOptions -+ ) => void; -+ showDialog: ( -+ body: string, -+ title?: string, -+ confirmText?: string, -+ cancelText?: string, -+ type?: string, -+ bodyIsHtml?: boolean -+ ) => Promise; -+ isDev: () => boolean; -+ isSelfHost: () => boolean; -+ copyToClipboard: (text: string, options?: any) => void | boolean; -+ readFromClipboard: (options?: any) => Promise; -+ supportsBiometric: () => Promise; -+ authenticateBiometric: () => Promise; -+ getDefaultSystemTheme: () => Promise; -+ onDefaultSystemThemeChange: ( -+ callback: (theme: ThemeType.Light | ThemeType.Dark) => unknown -+ ) => unknown; -+ getEffectiveTheme: () => Promise; -+ supportsSecureStorage: () => boolean; - } -diff --git a/jslib/common/src/abstractions/policy.service.ts b/jslib/common/src/abstractions/policy.service.ts -index 6acc8253..f1f6b4e5 100644 ---- a/jslib/common/src/abstractions/policy.service.ts -+++ b/jslib/common/src/abstractions/policy.service.ts -@@ -1,26 +1,35 @@ --import { PolicyData } from '../models/data/policyData'; -+import { PolicyData } from "../models/data/policyData"; - --import { MasterPasswordPolicyOptions } from '../models/domain/masterPasswordPolicyOptions'; --import { Policy } from '../models/domain/policy'; --import { ResetPasswordPolicyOptions } from '../models/domain/resetPasswordPolicyOptions'; -+import { MasterPasswordPolicyOptions } from "../models/domain/masterPasswordPolicyOptions"; -+import { Policy } from "../models/domain/policy"; -+import { ResetPasswordPolicyOptions } from "../models/domain/resetPasswordPolicyOptions"; - --import { ListResponse } from '../models/response/listResponse'; --import { PolicyResponse } from '../models/response/policyResponse'; -+import { ListResponse } from "../models/response/listResponse"; -+import { PolicyResponse } from "../models/response/policyResponse"; - --import { PolicyType } from '../enums/policyType'; -+import { PolicyType } from "../enums/policyType"; - - export abstract class PolicyService { -- policyCache: Policy[]; -- -- clearCache: () => void; -- getAll: (type?: PolicyType) => Promise; -- getPolicyForOrganization: (policyType: PolicyType, organizationId: string) => Promise; -- replace: (policies: { [id: string]: PolicyData; }) => Promise; -- clear: (userId: string) => Promise; -- getMasterPasswordPolicyOptions: (policies?: Policy[]) => Promise; -- evaluateMasterPassword: (passwordStrength: number, newPassword: string, -- enforcedPolicyOptions?: MasterPasswordPolicyOptions) => boolean; -- getResetPasswordPolicyOptions: (policies: Policy[], orgId: string) => [ResetPasswordPolicyOptions, boolean]; -- mapPoliciesFromToken: (policiesResponse: ListResponse) => Policy[]; -- policyAppliesToUser: (policyType: PolicyType, policyFilter?: (policy: Policy) => boolean) => Promise; -+ clearCache: () => void; -+ getAll: (type?: PolicyType, userId?: string) => Promise; -+ getPolicyForOrganization: (policyType: PolicyType, organizationId: string) => Promise; -+ replace: (policies: { [id: string]: PolicyData }) => Promise; -+ clear: (userId?: string) => Promise; -+ getMasterPasswordPoliciesForInvitedUsers: (orgId: string) => Promise; -+ getMasterPasswordPolicyOptions: (policies?: Policy[]) => Promise; -+ evaluateMasterPassword: ( -+ passwordStrength: number, -+ newPassword: string, -+ enforcedPolicyOptions?: MasterPasswordPolicyOptions -+ ) => boolean; -+ getResetPasswordPolicyOptions: ( -+ policies: Policy[], -+ orgId: string -+ ) => [ResetPasswordPolicyOptions, boolean]; -+ mapPoliciesFromToken: (policiesResponse: ListResponse) => Policy[]; -+ policyAppliesToUser: ( -+ policyType: PolicyType, -+ policyFilter?: (policy: Policy) => boolean, -+ userId?: string -+ ) => Promise; - } -diff --git a/jslib/common/src/abstractions/provider.service.ts b/jslib/common/src/abstractions/provider.service.ts -new file mode 100644 -index 00000000..e3e86a1a ---- /dev/null -+++ b/jslib/common/src/abstractions/provider.service.ts -@@ -0,0 +1,9 @@ -+import { ProviderData } from "../models/data/providerData"; -+ -+import { Provider } from "../models/domain/provider"; -+ -+export abstract class ProviderService { -+ get: (id: string) => Promise; -+ getAll: () => Promise; -+ save: (providers: { [id: string]: ProviderData }) => Promise; -+} -diff --git a/jslib/common/src/abstractions/search.service.ts b/jslib/common/src/abstractions/search.service.ts -index d95bea41..9c2b0eeb 100644 ---- a/jslib/common/src/abstractions/search.service.ts -+++ b/jslib/common/src/abstractions/search.service.ts -@@ -1,14 +1,16 @@ --import { CipherView } from '../models/view/cipherView'; --import { SendView } from '../models/view/sendView'; -+import { CipherView } from "../models/view/cipherView"; -+import { SendView } from "../models/view/sendView"; - - export abstract class SearchService { -- indexedEntityId?: string = null; -- clearIndex: () => void; -- isSearchable: (query: string) => boolean; -- indexCiphers: (indexedEntityGuid?: string, ciphersToIndex?: CipherView[]) => Promise; -- searchCiphers: (query: string, -- filter?: ((cipher: CipherView) => boolean) | (((cipher: CipherView) => boolean)[]), -- ciphers?: CipherView[]) => Promise; -- searchCiphersBasic: (ciphers: CipherView[], query: string, deleted?: boolean) => CipherView[]; -- searchSends: (sends: SendView[], query: string) => SendView[]; -+ indexedEntityId?: string = null; -+ clearIndex: () => void; -+ isSearchable: (query: string) => boolean; -+ indexCiphers: (indexedEntityGuid?: string, ciphersToIndex?: CipherView[]) => Promise; -+ searchCiphers: ( -+ query: string, -+ filter?: ((cipher: CipherView) => boolean) | ((cipher: CipherView) => boolean)[], -+ ciphers?: CipherView[] -+ ) => Promise; -+ searchCiphersBasic: (ciphers: CipherView[], query: string, deleted?: boolean) => CipherView[]; -+ searchSends: (sends: SendView[], query: string) => SendView[]; - } -diff --git a/jslib/common/src/abstractions/send.service.ts b/jslib/common/src/abstractions/send.service.ts -index f7ede223..37b806f2 100644 ---- a/jslib/common/src/abstractions/send.service.ts -+++ b/jslib/common/src/abstractions/send.service.ts -@@ -1,24 +1,27 @@ --import { SendData } from '../models/data/sendData'; -+import { SendData } from "../models/data/sendData"; - --import { EncArrayBuffer } from '../models/domain/encArrayBuffer'; --import { Send } from '../models/domain/send'; --import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; -+import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; -+import { Send } from "../models/domain/send"; -+import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; - --import { SendView } from '../models/view/sendView'; -+import { SendView } from "../models/view/sendView"; - - export abstract class SendService { -- decryptedSendCache: SendView[]; -- -- clearCache: () => void; -- encrypt: (model: SendView, file: File | ArrayBuffer, password: string, key?: SymmetricCryptoKey) => Promise<[Send, EncArrayBuffer]>; -- get: (id: string) => Promise; -- getAll: () => Promise; -- getAllDecrypted: () => Promise; -- saveWithServer: (sendData: [Send, EncArrayBuffer]) => Promise; -- upsert: (send: SendData | SendData[]) => Promise; -- replace: (sends: { [id: string]: SendData; }) => Promise; -- clear: (userId: string) => Promise; -- delete: (id: string | string[]) => Promise; -- deleteWithServer: (id: string) => Promise; -- removePasswordWithServer: (id: string) => Promise; -+ clearCache: () => Promise; -+ encrypt: ( -+ model: SendView, -+ file: File | ArrayBuffer, -+ password: string, -+ key?: SymmetricCryptoKey -+ ) => Promise<[Send, EncArrayBuffer]>; -+ get: (id: string) => Promise; -+ getAll: () => Promise; -+ getAllDecrypted: () => Promise; -+ saveWithServer: (sendData: [Send, EncArrayBuffer]) => Promise; -+ upsert: (send: SendData | SendData[]) => Promise; -+ replace: (sends: { [id: string]: SendData }) => Promise; -+ clear: (userId: string) => Promise; -+ delete: (id: string | string[]) => Promise; -+ deleteWithServer: (id: string) => Promise; -+ removePasswordWithServer: (id: string) => Promise; - } -diff --git a/jslib/common/src/abstractions/settings.service.ts b/jslib/common/src/abstractions/settings.service.ts -index 6104c1d1..e7886585 100644 ---- a/jslib/common/src/abstractions/settings.service.ts -+++ b/jslib/common/src/abstractions/settings.service.ts -@@ -1,6 +1,6 @@ - export abstract class SettingsService { -- clearCache: () => void; -- getEquivalentDomains: () => Promise; -- setEquivalentDomains: (equivalentDomains: string[][]) => Promise; -- clear: (userId: string) => Promise; -+ clearCache: () => Promise; -+ getEquivalentDomains: () => Promise; -+ setEquivalentDomains: (equivalentDomains: string[][]) => Promise; -+ clear: (userId?: string) => Promise; - } -diff --git a/jslib/common/src/abstractions/state.service.ts b/jslib/common/src/abstractions/state.service.ts -index 78658882..e4c38025 100644 ---- a/jslib/common/src/abstractions/state.service.ts -+++ b/jslib/common/src/abstractions/state.service.ts -@@ -1,6 +1,306 @@ --export abstract class StateService { -- get: (key: string) => Promise; -- save: (key: string, obj: any) => Promise; -- remove: (key: string) => Promise; -- purge: () => Promise; -+import { BehaviorSubject } from "rxjs"; -+ -+import { KdfType } from "../enums/kdfType"; -+import { ThemeType } from "../enums/themeType"; -+import { UriMatchType } from "../enums/uriMatchType"; -+ -+import { CipherData } from "../models/data/cipherData"; -+import { CollectionData } from "../models/data/collectionData"; -+import { EventData } from "../models/data/eventData"; -+import { FolderData } from "../models/data/folderData"; -+import { OrganizationData } from "../models/data/organizationData"; -+import { PolicyData } from "../models/data/policyData"; -+import { ProviderData } from "../models/data/providerData"; -+import { SendData } from "../models/data/sendData"; -+ -+import { Account } from "../models/domain/account"; -+import { EncString } from "../models/domain/encString"; -+import { EnvironmentUrls } from "../models/domain/environmentUrls"; -+import { GeneratedPasswordHistory } from "../models/domain/generatedPasswordHistory"; -+import { Policy } from "../models/domain/policy"; -+import { StorageOptions } from "../models/domain/storageOptions"; -+import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; -+import { WindowState } from "../models/domain/windowState"; -+ -+import { CipherView } from "../models/view/cipherView"; -+import { CollectionView } from "../models/view/collectionView"; -+import { FolderView } from "../models/view/folderView"; -+import { SendView } from "../models/view/sendView"; -+ -+export abstract class StateService { -+ accounts: BehaviorSubject<{ [userId: string]: T }>; -+ activeAccount: BehaviorSubject; -+ -+ addAccount: (account: T) => Promise; -+ setActiveUser: (userId: string) => Promise; -+ clean: (options?: StorageOptions) => Promise; -+ init: () => Promise; -+ -+ getAccessToken: (options?: StorageOptions) => Promise; -+ setAccessToken: (value: string, options?: StorageOptions) => Promise; -+ getAddEditCipherInfo: (options?: StorageOptions) => Promise; -+ setAddEditCipherInfo: (value: any, options?: StorageOptions) => Promise; -+ getAlwaysShowDock: (options?: StorageOptions) => Promise; -+ setAlwaysShowDock: (value: boolean, options?: StorageOptions) => Promise; -+ getApiKeyClientId: (options?: StorageOptions) => Promise; -+ setApiKeyClientId: (value: string, options?: StorageOptions) => Promise; -+ getApiKeyClientSecret: (options?: StorageOptions) => Promise; -+ setApiKeyClientSecret: (value: string, options?: StorageOptions) => Promise; -+ getAutoConfirmFingerPrints: (options?: StorageOptions) => Promise; -+ setAutoConfirmFingerprints: (value: boolean, options?: StorageOptions) => Promise; -+ getAutoFillOnPageLoadDefault: (options?: StorageOptions) => Promise; -+ setAutoFillOnPageLoadDefault: (value: boolean, options?: StorageOptions) => Promise; -+ getBiometricAwaitingAcceptance: (options?: StorageOptions) => Promise; -+ setBiometricAwaitingAcceptance: (value: boolean, options?: StorageOptions) => Promise; -+ getBiometricFingerprintValidated: (options?: StorageOptions) => Promise; -+ setBiometricFingerprintValidated: (value: boolean, options?: StorageOptions) => Promise; -+ getBiometricLocked: (options?: StorageOptions) => Promise; -+ setBiometricLocked: (value: boolean, options?: StorageOptions) => Promise; -+ getBiometricText: (options?: StorageOptions) => Promise; -+ setBiometricText: (value: string, options?: StorageOptions) => Promise; -+ getBiometricUnlock: (options?: StorageOptions) => Promise; -+ setBiometricUnlock: (value: boolean, options?: StorageOptions) => Promise; -+ getCanAccessPremium: (options?: StorageOptions) => Promise; -+ getClearClipboard: (options?: StorageOptions) => Promise; -+ setClearClipboard: (value: number, options?: StorageOptions) => Promise; -+ getCollapsedGroupings: (options?: StorageOptions) => Promise>; -+ setCollapsedGroupings: (value: Set, options?: StorageOptions) => Promise; -+ getConvertAccountToKeyConnector: (options?: StorageOptions) => Promise; -+ setConvertAccountToKeyConnector: (value: boolean, options?: StorageOptions) => Promise; -+ getCryptoMasterKey: (options?: StorageOptions) => Promise; -+ setCryptoMasterKey: (value: SymmetricCryptoKey, options?: StorageOptions) => Promise; -+ getCryptoMasterKeyAuto: (options?: StorageOptions) => Promise; -+ setCryptoMasterKeyAuto: (value: string, options?: StorageOptions) => Promise; -+ getCryptoMasterKeyB64: (options?: StorageOptions) => Promise; -+ setCryptoMasterKeyB64: (value: string, options?: StorageOptions) => Promise; -+ getCryptoMasterKeyBiometric: (options?: StorageOptions) => Promise; -+ hasCryptoMasterKeyBiometric: (options?: StorageOptions) => Promise; -+ setCryptoMasterKeyBiometric: (value: string, options?: StorageOptions) => Promise; -+ getDecodedToken: (options?: StorageOptions) => Promise; -+ setDecodedToken: (value: any, options?: StorageOptions) => Promise; -+ getDecryptedCiphers: (options?: StorageOptions) => Promise; -+ setDecryptedCiphers: (value: CipherView[], options?: StorageOptions) => Promise; -+ getDecryptedCollections: (options?: StorageOptions) => Promise; -+ setDecryptedCollections: (value: CollectionView[], options?: StorageOptions) => Promise; -+ getDecryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise; -+ setDecryptedCryptoSymmetricKey: ( -+ value: SymmetricCryptoKey, -+ options?: StorageOptions -+ ) => Promise; -+ getDecryptedFolders: (options?: StorageOptions) => Promise; -+ setDecryptedFolders: (value: FolderView[], options?: StorageOptions) => Promise; -+ getDecryptedOrganizationKeys: ( -+ options?: StorageOptions -+ ) => Promise>; -+ setDecryptedOrganizationKeys: ( -+ value: Map, -+ options?: StorageOptions -+ ) => Promise; -+ getDecryptedPasswordGenerationHistory: ( -+ options?: StorageOptions -+ ) => Promise; -+ setDecryptedPasswordGenerationHistory: ( -+ value: GeneratedPasswordHistory[], -+ options?: StorageOptions -+ ) => Promise; -+ getDecryptedPinProtected: (options?: StorageOptions) => Promise; -+ setDecryptedPinProtected: (value: EncString, options?: StorageOptions) => Promise; -+ getDecryptedPolicies: (options?: StorageOptions) => Promise; -+ setDecryptedPolicies: (value: Policy[], options?: StorageOptions) => Promise; -+ getDecryptedPrivateKey: (options?: StorageOptions) => Promise; -+ setDecryptedPrivateKey: (value: ArrayBuffer, options?: StorageOptions) => Promise; -+ getDecryptedProviderKeys: (options?: StorageOptions) => Promise>; -+ setDecryptedProviderKeys: ( -+ value: Map, -+ options?: StorageOptions -+ ) => Promise; -+ getDecryptedSends: (options?: StorageOptions) => Promise; -+ setDecryptedSends: (value: SendView[], options?: StorageOptions) => Promise; -+ getDefaultUriMatch: (options?: StorageOptions) => Promise; -+ setDefaultUriMatch: (value: UriMatchType, options?: StorageOptions) => Promise; -+ getDisableAddLoginNotification: (options?: StorageOptions) => Promise; -+ setDisableAddLoginNotification: (value: boolean, options?: StorageOptions) => Promise; -+ getDisableAutoBiometricsPrompt: (options?: StorageOptions) => Promise; -+ setDisableAutoBiometricsPrompt: (value: boolean, options?: StorageOptions) => Promise; -+ getDisableAutoTotpCopy: (options?: StorageOptions) => Promise; -+ setDisableAutoTotpCopy: (value: boolean, options?: StorageOptions) => Promise; -+ getDisableBadgeCounter: (options?: StorageOptions) => Promise; -+ setDisableBadgeCounter: (value: boolean, options?: StorageOptions) => Promise; -+ getDisableChangedPasswordNotification: (options?: StorageOptions) => Promise; -+ setDisableChangedPasswordNotification: ( -+ value: boolean, -+ options?: StorageOptions -+ ) => Promise; -+ getDisableContextMenuItem: (options?: StorageOptions) => Promise; -+ setDisableContextMenuItem: (value: boolean, options?: StorageOptions) => Promise; -+ getDisableFavicon: (options?: StorageOptions) => Promise; -+ setDisableFavicon: (value: boolean, options?: StorageOptions) => Promise; -+ getDisableGa: (options?: StorageOptions) => Promise; -+ setDisableGa: (value: boolean, options?: StorageOptions) => Promise; -+ getDontShowCardsCurrentTab: (options?: StorageOptions) => Promise; -+ setDontShowCardsCurrentTab: (value: boolean, options?: StorageOptions) => Promise; -+ getDontShowIdentitiesCurrentTab: (options?: StorageOptions) => Promise; -+ setDontShowIdentitiesCurrentTab: (value: boolean, options?: StorageOptions) => Promise; -+ getEmail: (options?: StorageOptions) => Promise; -+ setEmail: (value: string, options?: StorageOptions) => Promise; -+ getEmailVerified: (options?: StorageOptions) => Promise; -+ setEmailVerified: (value: boolean, options?: StorageOptions) => Promise; -+ getEnableAlwaysOnTop: (options?: StorageOptions) => Promise; -+ setEnableAlwaysOnTop: (value: boolean, options?: StorageOptions) => Promise; -+ getEnableAutoFillOnPageLoad: (options?: StorageOptions) => Promise; -+ setEnableAutoFillOnPageLoad: (value: boolean, options?: StorageOptions) => Promise; -+ getEnableBiometric: (options?: StorageOptions) => Promise; -+ setEnableBiometric: (value: boolean, options?: StorageOptions) => Promise; -+ getEnableBrowserIntegration: (options?: StorageOptions) => Promise; -+ setEnableBrowserIntegration: (value: boolean, options?: StorageOptions) => Promise; -+ getEnableBrowserIntegrationFingerprint: (options?: StorageOptions) => Promise; -+ setEnableBrowserIntegrationFingerprint: ( -+ value: boolean, -+ options?: StorageOptions -+ ) => Promise; -+ getEnableCloseToTray: (options?: StorageOptions) => Promise; -+ setEnableCloseToTray: (value: boolean, options?: StorageOptions) => Promise; -+ getEnableFullWidth: (options?: StorageOptions) => Promise; -+ setEnableFullWidth: (value: boolean, options?: StorageOptions) => Promise; -+ getEnableGravitars: (options?: StorageOptions) => Promise; -+ setEnableGravitars: (value: boolean, options?: StorageOptions) => Promise; -+ getEnableMinimizeToTray: (options?: StorageOptions) => Promise; -+ setEnableMinimizeToTray: (value: boolean, options?: StorageOptions) => Promise; -+ getEnableStartToTray: (options?: StorageOptions) => Promise; -+ setEnableStartToTray: (value: boolean, options?: StorageOptions) => Promise; -+ getEnableTray: (options?: StorageOptions) => Promise; -+ setEnableTray: (value: boolean, options?: StorageOptions) => Promise; -+ getEncryptedCiphers: (options?: StorageOptions) => Promise<{ [id: string]: CipherData }>; -+ setEncryptedCiphers: ( -+ value: { [id: string]: CipherData }, -+ options?: StorageOptions -+ ) => Promise; -+ getEncryptedCollections: (options?: StorageOptions) => Promise<{ [id: string]: CollectionData }>; -+ setEncryptedCollections: ( -+ value: { [id: string]: CollectionData }, -+ options?: StorageOptions -+ ) => Promise; -+ getEncryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise; -+ setEncryptedCryptoSymmetricKey: (value: string, options?: StorageOptions) => Promise; -+ getEncryptedFolders: (options?: StorageOptions) => Promise<{ [id: string]: FolderData }>; -+ setEncryptedFolders: ( -+ value: { [id: string]: FolderData }, -+ options?: StorageOptions -+ ) => Promise; -+ getEncryptedOrganizationKeys: (options?: StorageOptions) => Promise; -+ setEncryptedOrganizationKeys: ( -+ value: Map, -+ options?: StorageOptions -+ ) => Promise; -+ getEncryptedPasswordGenerationHistory: ( -+ options?: StorageOptions -+ ) => Promise; -+ setEncryptedPasswordGenerationHistory: ( -+ value: GeneratedPasswordHistory[], -+ options?: StorageOptions -+ ) => Promise; -+ getEncryptedPinProtected: (options?: StorageOptions) => Promise; -+ setEncryptedPinProtected: (value: string, options?: StorageOptions) => Promise; -+ getEncryptedPolicies: (options?: StorageOptions) => Promise<{ [id: string]: PolicyData }>; -+ setEncryptedPolicies: ( -+ value: { [id: string]: PolicyData }, -+ options?: StorageOptions -+ ) => Promise; -+ getEncryptedPrivateKey: (options?: StorageOptions) => Promise; -+ setEncryptedPrivateKey: (value: string, options?: StorageOptions) => Promise; -+ getEncryptedProviderKeys: (options?: StorageOptions) => Promise; -+ setEncryptedProviderKeys: (value: any, options?: StorageOptions) => Promise; -+ getEncryptedSends: (options?: StorageOptions) => Promise<{ [id: string]: SendData }>; -+ setEncryptedSends: (value: { [id: string]: SendData }, options?: StorageOptions) => Promise; -+ getEntityId: (options?: StorageOptions) => Promise; -+ setEntityId: (value: string, options?: StorageOptions) => Promise; -+ getEntityType: (options?: StorageOptions) => Promise; -+ setEntityType: (value: string, options?: StorageOptions) => Promise; -+ getEnvironmentUrls: (options?: StorageOptions) => Promise; -+ setEnvironmentUrls: (value: EnvironmentUrls, options?: StorageOptions) => Promise; -+ getEquivalentDomains: (options?: StorageOptions) => Promise; -+ setEquivalentDomains: (value: string, options?: StorageOptions) => Promise; -+ getEventCollection: (options?: StorageOptions) => Promise; -+ setEventCollection: (value: EventData[], options?: StorageOptions) => Promise; -+ getEverBeenUnlocked: (options?: StorageOptions) => Promise; -+ setEverBeenUnlocked: (value: boolean, options?: StorageOptions) => Promise; -+ getForcePasswordReset: (options?: StorageOptions) => Promise; -+ setForcePasswordReset: (value: boolean, options?: StorageOptions) => Promise; -+ getInstalledVersion: (options?: StorageOptions) => Promise; -+ setInstalledVersion: (value: string, options?: StorageOptions) => Promise; -+ getIsAuthenticated: (options?: StorageOptions) => Promise; -+ getKdfIterations: (options?: StorageOptions) => Promise; -+ setKdfIterations: (value: number, options?: StorageOptions) => Promise; -+ getKdfType: (options?: StorageOptions) => Promise; -+ setKdfType: (value: KdfType, options?: StorageOptions) => Promise; -+ getKeyHash: (options?: StorageOptions) => Promise; -+ setKeyHash: (value: string, options?: StorageOptions) => Promise; -+ getLastActive: (options?: StorageOptions) => Promise; -+ setLastActive: (value: number, options?: StorageOptions) => Promise; -+ getLastSync: (options?: StorageOptions) => Promise; -+ setLastSync: (value: string, options?: StorageOptions) => Promise; -+ getLegacyEtmKey: (options?: StorageOptions) => Promise; -+ setLegacyEtmKey: (value: SymmetricCryptoKey, options?: StorageOptions) => Promise; -+ getLocalData: (options?: StorageOptions) => Promise; -+ setLocalData: (value: string, options?: StorageOptions) => Promise; -+ getLocale: (options?: StorageOptions) => Promise; -+ setLocale: (value: string, options?: StorageOptions) => Promise; -+ getLoginRedirect: (options?: StorageOptions) => Promise; -+ setLoginRedirect: (value: any, options?: StorageOptions) => Promise; -+ getMainWindowSize: (options?: StorageOptions) => Promise; -+ setMainWindowSize: (value: number, options?: StorageOptions) => Promise; -+ getMinimizeOnCopyToClipboard: (options?: StorageOptions) => Promise; -+ setMinimizeOnCopyToClipboard: (value: boolean, options?: StorageOptions) => Promise; -+ getNeverDomains: (options?: StorageOptions) => Promise<{ [id: string]: any }>; -+ setNeverDomains: (value: { [id: string]: any }, options?: StorageOptions) => Promise; -+ getNoAutoPromptBiometrics: (options?: StorageOptions) => Promise; -+ setNoAutoPromptBiometrics: (value: boolean, options?: StorageOptions) => Promise; -+ getNoAutoPromptBiometricsText: (options?: StorageOptions) => Promise; -+ setNoAutoPromptBiometricsText: (value: string, options?: StorageOptions) => Promise; -+ getOpenAtLogin: (options?: StorageOptions) => Promise; -+ setOpenAtLogin: (value: boolean, options?: StorageOptions) => Promise; -+ getOrganizationInvitation: (options?: StorageOptions) => Promise; -+ setOrganizationInvitation: (value: any, options?: StorageOptions) => Promise; -+ getOrganizations: (options?: StorageOptions) => Promise<{ [id: string]: OrganizationData }>; -+ setOrganizations: ( -+ value: { [id: string]: OrganizationData }, -+ options?: StorageOptions -+ ) => Promise; -+ getPasswordGenerationOptions: (options?: StorageOptions) => Promise; -+ setPasswordGenerationOptions: (value: any, options?: StorageOptions) => Promise; -+ getProtectedPin: (options?: StorageOptions) => Promise; -+ setProtectedPin: (value: string, options?: StorageOptions) => Promise; -+ getProviders: (options?: StorageOptions) => Promise<{ [id: string]: ProviderData }>; -+ setProviders: (value: { [id: string]: ProviderData }, options?: StorageOptions) => Promise; -+ getPublicKey: (options?: StorageOptions) => Promise; -+ setPublicKey: (value: ArrayBuffer, options?: StorageOptions) => Promise; -+ getRefreshToken: (options?: StorageOptions) => Promise; -+ setRefreshToken: (value: string, options?: StorageOptions) => Promise; -+ getRememberedEmail: (options?: StorageOptions) => Promise; -+ setRememberedEmail: (value: string, options?: StorageOptions) => Promise; -+ getSecurityStamp: (options?: StorageOptions) => Promise; -+ setSecurityStamp: (value: string, options?: StorageOptions) => Promise; -+ getSettings: (options?: StorageOptions) => Promise; -+ setSettings: (value: string, options?: StorageOptions) => Promise; -+ getSsoCodeVerifier: (options?: StorageOptions) => Promise; -+ setSsoCodeVerifier: (value: string, options?: StorageOptions) => Promise; -+ getSsoOrgIdentifier: (options?: StorageOptions) => Promise; -+ setSsoOrganizationIdentifier: (value: string, options?: StorageOptions) => Promise; -+ getSsoState: (options?: StorageOptions) => Promise; -+ setSsoState: (value: string, options?: StorageOptions) => Promise; -+ getTheme: (options?: StorageOptions) => Promise; -+ setTheme: (value: ThemeType, options?: StorageOptions) => Promise; -+ getTwoFactorToken: (options?: StorageOptions) => Promise; -+ setTwoFactorToken: (value: string, options?: StorageOptions) => Promise; -+ getUserId: (options?: StorageOptions) => Promise; -+ getUsesKeyConnector: (options?: StorageOptions) => Promise; -+ setUsesKeyConnector: (vaule: boolean, options?: StorageOptions) => Promise; -+ getVaultTimeout: (options?: StorageOptions) => Promise; -+ setVaultTimeout: (value: number, options?: StorageOptions) => Promise; -+ getVaultTimeoutAction: (options?: StorageOptions) => Promise; -+ setVaultTimeoutAction: (value: string, options?: StorageOptions) => Promise; -+ getStateVersion: () => Promise; -+ setStateVersion: (value: number) => Promise; -+ getWindow: () => Promise; -+ setWindow: (value: WindowState) => Promise; - } -diff --git a/jslib/common/src/abstractions/stateMigration.service.ts b/jslib/common/src/abstractions/stateMigration.service.ts -new file mode 100644 -index 00000000..f16777a1 ---- /dev/null -+++ b/jslib/common/src/abstractions/stateMigration.service.ts -@@ -0,0 +1,4 @@ -+export abstract class StateMigrationService { -+ needsMigration: () => Promise; -+ migrate: () => Promise; -+} -diff --git a/jslib/common/src/abstractions/storage.service.ts b/jslib/common/src/abstractions/storage.service.ts -index cfedb2d0..f522d3cf 100644 ---- a/jslib/common/src/abstractions/storage.service.ts -+++ b/jslib/common/src/abstractions/storage.service.ts -@@ -1,12 +1,8 @@ --export abstract class StorageService { -- get: (key: string, options?: StorageServiceOptions) => Promise; -- has: (key: string, options?: StorageServiceOptions) => Promise; -- save: (key: string, obj: any, options?: StorageServiceOptions) => Promise; -- remove: (key: string, options?: StorageServiceOptions) => Promise; --} -+import { StorageOptions } from "../models/domain/storageOptions"; - --export interface StorageServiceOptions { -- keySuffix: KeySuffixOptions; -+export abstract class StorageService { -+ get: (key: string, options?: StorageOptions) => Promise; -+ has: (key: string, options?: StorageOptions) => Promise; -+ save: (key: string, obj: any, options?: StorageOptions) => Promise; -+ remove: (key: string, options?: StorageOptions) => Promise; - } -- --export type KeySuffixOptions = 'auto' | 'biometric'; -diff --git a/jslib/common/src/abstractions/sync.service.ts b/jslib/common/src/abstractions/sync.service.ts -index 5d35cf20..936d027f 100644 ---- a/jslib/common/src/abstractions/sync.service.ts -+++ b/jslib/common/src/abstractions/sync.service.ts -@@ -1,19 +1,19 @@ - import { -- SyncCipherNotification, -- SyncFolderNotification, -- SyncSendNotification, --} from '../models/response/notificationResponse'; -+ SyncCipherNotification, -+ SyncFolderNotification, -+ SyncSendNotification, -+} from "../models/response/notificationResponse"; - - export abstract class SyncService { -- syncInProgress: boolean; -+ syncInProgress: boolean; - -- getLastSync: () => Promise; -- setLastSync: (date: Date) => Promise; -- fullSync: (forceSync: boolean, allowThrowOnError?: boolean) => Promise; -- syncUpsertFolder: (notification: SyncFolderNotification, isEdit: boolean) => Promise; -- syncDeleteFolder: (notification: SyncFolderNotification) => Promise; -- syncUpsertCipher: (notification: SyncCipherNotification, isEdit: boolean) => Promise; -- syncDeleteCipher: (notification: SyncFolderNotification) => Promise; -- syncUpsertSend: (notification: SyncSendNotification, isEdit: boolean) => Promise; -- syncDeleteSend: (notification: SyncSendNotification) => Promise; -+ getLastSync: () => Promise; -+ setLastSync: (date: Date, userId?: string) => Promise; -+ fullSync: (forceSync: boolean, allowThrowOnError?: boolean) => Promise; -+ syncUpsertFolder: (notification: SyncFolderNotification, isEdit: boolean) => Promise; -+ syncDeleteFolder: (notification: SyncFolderNotification) => Promise; -+ syncUpsertCipher: (notification: SyncCipherNotification, isEdit: boolean) => Promise; -+ syncDeleteCipher: (notification: SyncFolderNotification) => Promise; -+ syncUpsertSend: (notification: SyncSendNotification, isEdit: boolean) => Promise; -+ syncDeleteSend: (notification: SyncSendNotification) => Promise; - } -diff --git a/jslib/common/src/abstractions/system.service.ts b/jslib/common/src/abstractions/system.service.ts -index c8948bc1..b5c1e595 100644 ---- a/jslib/common/src/abstractions/system.service.ts -+++ b/jslib/common/src/abstractions/system.service.ts -@@ -1,6 +1,6 @@ - export abstract class SystemService { -- startProcessReload: () => void; -- cancelProcessReload: () => void; -- clearClipboard: (clipboardValue: string, timeoutMs?: number) => void; -- clearPendingClipboard: () => Promise; -+ startProcessReload: () => Promise; -+ cancelProcessReload: () => void; -+ clearClipboard: (clipboardValue: string, timeoutMs?: number) => Promise; -+ clearPendingClipboard: () => Promise; - } -diff --git a/jslib/common/src/abstractions/token.service.ts b/jslib/common/src/abstractions/token.service.ts -index 73c960ec..6006ba10 100644 ---- a/jslib/common/src/abstractions/token.service.ts -+++ b/jslib/common/src/abstractions/token.service.ts -@@ -1,30 +1,31 @@ - export abstract class TokenService { -- token: string; -- decodedToken: any; -- refreshToken: string; -- setTokens: (accessToken: string, refreshToken: string, clientIdClientSecret: [string, string]) => Promise; -- setToken: (token: string) => Promise; -- getToken: () => Promise; -- setRefreshToken: (refreshToken: string) => Promise; -- getRefreshToken: () => Promise; -- setClientId: (clientId: string) => Promise; -- getClientId: () => Promise; -- setClientSecret: (clientSecret: string) => Promise; -- getClientSecret: () => Promise; -- toggleTokens: () => Promise; -- setTwoFactorToken: (token: string, email: string) => Promise; -- getTwoFactorToken: (email: string) => Promise; -- clearTwoFactorToken: (email: string) => Promise; -- clearToken: () => Promise; -- decodeToken: () => any; -- getTokenExpirationDate: () => Date; -- tokenSecondsRemaining: (offsetSeconds?: number) => number; -- tokenNeedsRefresh: (minutes?: number) => boolean; -- getUserId: () => string; -- getEmail: () => string; -- getEmailVerified: () => boolean; -- getName: () => string; -- getPremium: () => boolean; -- getIssuer: () => string; -- getIsExternal: () => boolean; -+ setTokens: ( -+ accessToken: string, -+ refreshToken: string, -+ clientIdClientSecret: [string, string] -+ ) => Promise; -+ setToken: (token: string) => Promise; -+ getToken: () => Promise; -+ setRefreshToken: (refreshToken: string) => Promise; -+ getRefreshToken: () => Promise; -+ setClientId: (clientId: string) => Promise; -+ getClientId: () => Promise; -+ setClientSecret: (clientSecret: string) => Promise; -+ getClientSecret: () => Promise; -+ toggleTokens: () => Promise; -+ setTwoFactorToken: (token: string, email: string) => Promise; -+ getTwoFactorToken: (email: string) => Promise; -+ clearTwoFactorToken: (email: string) => Promise; -+ clearToken: (userId?: string) => Promise; -+ decodeToken: (token?: string) => any; -+ getTokenExpirationDate: () => Promise; -+ tokenSecondsRemaining: (offsetSeconds?: number) => Promise; -+ tokenNeedsRefresh: (minutes?: number) => Promise; -+ getUserId: () => Promise; -+ getEmail: () => Promise; -+ getEmailVerified: () => Promise; -+ getName: () => Promise; -+ getPremium: () => Promise; -+ getIssuer: () => Promise; -+ getIsExternal: () => Promise; - } -diff --git a/jslib/common/src/abstractions/totp.service.ts b/jslib/common/src/abstractions/totp.service.ts -index 608c7d1b..bf143e26 100644 ---- a/jslib/common/src/abstractions/totp.service.ts -+++ b/jslib/common/src/abstractions/totp.service.ts -@@ -1,5 +1,5 @@ - export abstract class TotpService { -- getCode: (key: string) => Promise; -- getTimeInterval: (key: string) => number; -- isAutoCopyEnabled: () => Promise; -+ getCode: (key: string) => Promise; -+ getTimeInterval: (key: string) => number; -+ isAutoCopyEnabled: () => Promise; - } -diff --git a/jslib/common/src/abstractions/user.service.ts b/jslib/common/src/abstractions/user.service.ts -deleted file mode 100644 -index 8170ae73..00000000 ---- a/jslib/common/src/abstractions/user.service.ts -+++ /dev/null -@@ -1,34 +0,0 @@ --import { OrganizationData } from '../models/data/organizationData'; --import { ProviderData } from '../models/data/providerData'; -- --import { Organization } from '../models/domain/organization'; --import { Provider } from '../models/domain/provider'; -- --import { KdfType } from '../enums/kdfType'; -- --export abstract class UserService { -- setInformation: (userId: string, email: string, kdf: KdfType, kdfIterations: number) => Promise; -- setEmailVerified: (emailVerified: boolean) => Promise; -- setSecurityStamp: (stamp: string) => Promise; -- setForcePasswordReset: (forcePasswordReset: boolean) => Promise; -- getUserId: () => Promise; -- getEmail: () => Promise; -- getSecurityStamp: () => Promise; -- getKdf: () => Promise; -- getKdfIterations: () => Promise; -- getEmailVerified: () => Promise; -- getForcePasswordReset: () => Promise; -- clear: () => Promise; -- isAuthenticated: () => Promise; -- canAccessPremium: () => Promise; -- canManageSponsorships: () => Promise; -- getOrganization: (id: string) => Promise; -- getOrganizationByIdentifier: (identifier: string) => Promise; -- getAllOrganizations: () => Promise; -- replaceOrganizations: (organizations: { [id: string]: OrganizationData; }) => Promise; -- clearOrganizations: (userId: string) => Promise; -- getProvider: (id: string) => Promise; -- getAllProviders: () => Promise; -- replaceProviders: (providers: { [id: string]: ProviderData; }) => Promise; -- clearProviders: (userId: string) => Promise; --} -diff --git a/jslib/common/src/abstractions/userVerification.service.ts b/jslib/common/src/abstractions/userVerification.service.ts -index 7dcf1d10..42370ae1 100644 ---- a/jslib/common/src/abstractions/userVerification.service.ts -+++ b/jslib/common/src/abstractions/userVerification.service.ts -@@ -1,10 +1,13 @@ --import { SecretVerificationRequest } from '../models/request/secretVerificationRequest'; -+import { SecretVerificationRequest } from "../models/request/secretVerificationRequest"; - --import { Verification } from '../types/verification'; -+import { Verification } from "../types/verification"; - - export abstract class UserVerificationService { -- buildRequest: (verification: Verification, -- requestClass?: new () => T, alreadyHashed?: boolean) => Promise; -- verifyUser: (verification: Verification) => Promise; -- requestOTP: () => Promise; -+ buildRequest: ( -+ verification: Verification, -+ requestClass?: new () => T, -+ alreadyHashed?: boolean -+ ) => Promise; -+ verifyUser: (verification: Verification) => Promise; -+ requestOTP: () => Promise; - } -diff --git a/jslib/common/src/abstractions/vaultTimeout.service.ts b/jslib/common/src/abstractions/vaultTimeout.service.ts -index a5944c48..2c2b2521 100644 ---- a/jslib/common/src/abstractions/vaultTimeout.service.ts -+++ b/jslib/common/src/abstractions/vaultTimeout.service.ts -@@ -1,16 +1,11 @@ --import { EncString } from '../models/domain/encString'; -- - export abstract class VaultTimeoutService { -- biometricLocked: boolean; -- everBeenUnlocked: boolean; -- pinProtectedKey: EncString; -- isLocked: () => Promise; -- checkVaultTimeout: () => Promise; -- lock: (allowSoftLock?: boolean) => Promise; -- logOut: () => Promise; -- setVaultTimeoutOptions: (vaultTimeout: number, vaultTimeoutAction: string) => Promise; -- getVaultTimeout: () => Promise; -- isPinLockSet: () => Promise<[boolean, boolean]>; -- isBiometricLockSet: () => Promise; -- clear: () => Promise; -+ isLocked: (userId?: string) => Promise; -+ checkVaultTimeout: () => Promise; -+ lock: (allowSoftLock?: boolean, userId?: string) => Promise; -+ logOut: (userId?: string) => Promise; -+ setVaultTimeoutOptions: (vaultTimeout: number, vaultTimeoutAction: string) => Promise; -+ getVaultTimeout: () => Promise; -+ isPinLockSet: () => Promise<[boolean, boolean]>; -+ isBiometricLockSet: () => Promise; -+ clear: (userId?: string) => Promise; - } -diff --git a/jslib/common/src/enums/authenticationStatus.ts b/jslib/common/src/enums/authenticationStatus.ts -new file mode 100644 -index 00000000..8e1db548 ---- /dev/null -+++ b/jslib/common/src/enums/authenticationStatus.ts -@@ -0,0 +1,6 @@ -+export enum AuthenticationStatus { -+ Locked = "locked", -+ Unlocked = "unlocked", -+ LoggedOut = "loggedOut", -+ Active = "active", -+} -diff --git a/jslib/common/src/enums/cipherRepromptType.ts b/jslib/common/src/enums/cipherRepromptType.ts -index 5b2a9b2f..1d0a523c 100644 ---- a/jslib/common/src/enums/cipherRepromptType.ts -+++ b/jslib/common/src/enums/cipherRepromptType.ts -@@ -1,4 +1,4 @@ - export enum CipherRepromptType { -- None = 0, -- Password = 1, -+ None = 0, -+ Password = 1, - } -diff --git a/jslib/common/src/enums/cipherType.ts b/jslib/common/src/enums/cipherType.ts -index c081fb2d..cce7874d 100644 ---- a/jslib/common/src/enums/cipherType.ts -+++ b/jslib/common/src/enums/cipherType.ts -@@ -1,6 +1,6 @@ - export enum CipherType { -- Login = 1, -- SecureNote = 2, -- Card = 3, -- Identity = 4, -+ Login = 1, -+ SecureNote = 2, -+ Card = 3, -+ Identity = 4, - } -diff --git a/jslib/common/src/enums/deviceType.ts b/jslib/common/src/enums/deviceType.ts -index 6f8ddd21..707f83d8 100644 ---- a/jslib/common/src/enums/deviceType.ts -+++ b/jslib/common/src/enums/deviceType.ts -@@ -1,23 +1,23 @@ - export enum DeviceType { -- Android = 0, -- iOS = 1, -- ChromeExtension = 2, -- FirefoxExtension = 3, -- OperaExtension = 4, -- EdgeExtension = 5, -- WindowsDesktop = 6, -- MacOsDesktop = 7, -- LinuxDesktop = 8, -- ChromeBrowser = 9, -- FirefoxBrowser = 10, -- OperaBrowser = 11, -- EdgeBrowser = 12, -- IEBrowser = 13, -- UnknownBrowser = 14, -- AndroidAmazon = 15, -- UWP = 16, -- SafariBrowser = 17, -- VivaldiBrowser = 18, -- VivaldiExtension = 19, -- SafariExtension = 20, -+ Android = 0, -+ iOS = 1, -+ ChromeExtension = 2, -+ FirefoxExtension = 3, -+ OperaExtension = 4, -+ EdgeExtension = 5, -+ WindowsDesktop = 6, -+ MacOsDesktop = 7, -+ LinuxDesktop = 8, -+ ChromeBrowser = 9, -+ FirefoxBrowser = 10, -+ OperaBrowser = 11, -+ EdgeBrowser = 12, -+ IEBrowser = 13, -+ UnknownBrowser = 14, -+ AndroidAmazon = 15, -+ UWP = 16, -+ SafariBrowser = 17, -+ VivaldiBrowser = 18, -+ VivaldiExtension = 19, -+ SafariExtension = 20, - } -diff --git a/jslib/common/src/enums/emergencyAccessStatusType.ts b/jslib/common/src/enums/emergencyAccessStatusType.ts -index eb362edf..94400f34 100644 ---- a/jslib/common/src/enums/emergencyAccessStatusType.ts -+++ b/jslib/common/src/enums/emergencyAccessStatusType.ts -@@ -1,7 +1,7 @@ - export enum EmergencyAccessStatusType { -- Invited = 0, -- Accepted = 1, -- Confirmed = 2, -- RecoveryInitiated = 3, -- RecoveryApproved = 4, -+ Invited = 0, -+ Accepted = 1, -+ Confirmed = 2, -+ RecoveryInitiated = 3, -+ RecoveryApproved = 4, - } -diff --git a/jslib/common/src/enums/emergencyAccessType.ts b/jslib/common/src/enums/emergencyAccessType.ts -index 803634f4..61a366c4 100644 ---- a/jslib/common/src/enums/emergencyAccessType.ts -+++ b/jslib/common/src/enums/emergencyAccessType.ts -@@ -1,5 +1,4 @@ --export enum EmergencyAccessType --{ -- View = 0, -- Takeover = 1, -+export enum EmergencyAccessType { -+ View = 0, -+ Takeover = 1, - } -diff --git a/jslib/common/src/enums/encryptionType.ts b/jslib/common/src/enums/encryptionType.ts -index 7a0caa66..1c12dba2 100644 ---- a/jslib/common/src/enums/encryptionType.ts -+++ b/jslib/common/src/enums/encryptionType.ts -@@ -1,9 +1,9 @@ - export enum EncryptionType { -- AesCbc256_B64 = 0, -- AesCbc128_HmacSha256_B64 = 1, -- AesCbc256_HmacSha256_B64 = 2, -- Rsa2048_OaepSha256_B64 = 3, -- Rsa2048_OaepSha1_B64 = 4, -- Rsa2048_OaepSha256_HmacSha256_B64 = 5, -- Rsa2048_OaepSha1_HmacSha256_B64 = 6, -+ AesCbc256_B64 = 0, -+ AesCbc128_HmacSha256_B64 = 1, -+ AesCbc256_HmacSha256_B64 = 2, -+ Rsa2048_OaepSha256_B64 = 3, -+ Rsa2048_OaepSha1_B64 = 4, -+ Rsa2048_OaepSha256_HmacSha256_B64 = 5, -+ Rsa2048_OaepSha1_HmacSha256_B64 = 6, - } -diff --git a/jslib/common/src/enums/eventType.ts b/jslib/common/src/enums/eventType.ts -index b45f45f9..236e73c0 100644 ---- a/jslib/common/src/enums/eventType.ts -+++ b/jslib/common/src/enums/eventType.ts -@@ -1,72 +1,72 @@ - export enum EventType { -- User_LoggedIn = 1000, -- User_ChangedPassword = 1001, -- User_Updated2fa = 1002, -- User_Disabled2fa = 1003, -- User_Recovered2fa = 1004, -- User_FailedLogIn = 1005, -- User_FailedLogIn2fa = 1006, -- User_ClientExportedVault = 1007, -- User_UpdatedTempPassword = 1008, -- User_MigratedKeyToKeyConnector = 1009, -+ User_LoggedIn = 1000, -+ User_ChangedPassword = 1001, -+ User_Updated2fa = 1002, -+ User_Disabled2fa = 1003, -+ User_Recovered2fa = 1004, -+ User_FailedLogIn = 1005, -+ User_FailedLogIn2fa = 1006, -+ User_ClientExportedVault = 1007, -+ User_UpdatedTempPassword = 1008, -+ User_MigratedKeyToKeyConnector = 1009, - -- Cipher_Created = 1100, -- Cipher_Updated = 1101, -- Cipher_Deleted = 1102, -- Cipher_AttachmentCreated = 1103, -- Cipher_AttachmentDeleted = 1104, -- Cipher_Shared = 1105, -- Cipher_UpdatedCollections = 1106, -- Cipher_ClientViewed = 1107, -- Cipher_ClientToggledPasswordVisible = 1108, -- Cipher_ClientToggledHiddenFieldVisible = 1109, -- Cipher_ClientToggledCardCodeVisible = 1110, -- Cipher_ClientCopiedPassword = 1111, -- Cipher_ClientCopiedHiddenField = 1112, -- Cipher_ClientCopiedCardCode = 1113, -- Cipher_ClientAutofilled = 1114, -- Cipher_SoftDeleted = 1115, -- Cipher_Restored = 1116, -- Cipher_ClientToggledCardNumberVisible = 1117, -+ Cipher_Created = 1100, -+ Cipher_Updated = 1101, -+ Cipher_Deleted = 1102, -+ Cipher_AttachmentCreated = 1103, -+ Cipher_AttachmentDeleted = 1104, -+ Cipher_Shared = 1105, -+ Cipher_UpdatedCollections = 1106, -+ Cipher_ClientViewed = 1107, -+ Cipher_ClientToggledPasswordVisible = 1108, -+ Cipher_ClientToggledHiddenFieldVisible = 1109, -+ Cipher_ClientToggledCardCodeVisible = 1110, -+ Cipher_ClientCopiedPassword = 1111, -+ Cipher_ClientCopiedHiddenField = 1112, -+ Cipher_ClientCopiedCardCode = 1113, -+ Cipher_ClientAutofilled = 1114, -+ Cipher_SoftDeleted = 1115, -+ Cipher_Restored = 1116, -+ Cipher_ClientToggledCardNumberVisible = 1117, - -- Collection_Created = 1300, -- Collection_Updated = 1301, -- Collection_Deleted = 1302, -+ Collection_Created = 1300, -+ Collection_Updated = 1301, -+ Collection_Deleted = 1302, - -- Group_Created = 1400, -- Group_Updated = 1401, -- Group_Deleted = 1402, -+ Group_Created = 1400, -+ Group_Updated = 1401, -+ Group_Deleted = 1402, - -- OrganizationUser_Invited = 1500, -- OrganizationUser_Confirmed = 1501, -- OrganizationUser_Updated = 1502, -- OrganizationUser_Removed = 1503, -- OrganizationUser_UpdatedGroups = 1504, -- OrganizationUser_UnlinkedSso = 1505, -- OrganizationUser_ResetPassword_Enroll = 1506, -- OrganizationUser_ResetPassword_Withdraw = 1507, -- OrganizationUser_AdminResetPassword = 1508, -- OrganizationUser_ResetSsoLink = 1509, -- OrganizationUser_FirstSsoLogin = 1510, -+ OrganizationUser_Invited = 1500, -+ OrganizationUser_Confirmed = 1501, -+ OrganizationUser_Updated = 1502, -+ OrganizationUser_Removed = 1503, -+ OrganizationUser_UpdatedGroups = 1504, -+ OrganizationUser_UnlinkedSso = 1505, -+ OrganizationUser_ResetPassword_Enroll = 1506, -+ OrganizationUser_ResetPassword_Withdraw = 1507, -+ OrganizationUser_AdminResetPassword = 1508, -+ OrganizationUser_ResetSsoLink = 1509, -+ OrganizationUser_FirstSsoLogin = 1510, - -- Organization_Updated = 1600, -- Organization_PurgedVault = 1601, -- // Organization_ClientExportedVault = 1602, -- Organization_VaultAccessed = 1603, -- Organization_EnabledSso = 1604, -- Organization_DisabledSso = 1605, -- Organization_EnabledKeyConnector = 1606, -- Organization_DisabledKeyConnector = 1607, -+ Organization_Updated = 1600, -+ Organization_PurgedVault = 1601, -+ // Organization_ClientExportedVault = 1602, -+ Organization_VaultAccessed = 1603, -+ Organization_EnabledSso = 1604, -+ Organization_DisabledSso = 1605, -+ Organization_EnabledKeyConnector = 1606, -+ Organization_DisabledKeyConnector = 1607, - -- Policy_Updated = 1700, -+ Policy_Updated = 1700, - -- ProviderUser_Invited = 1800, -- ProviderUser_Confirmed = 1801, -- ProviderUser_Updated = 1802, -- ProviderUser_Removed = 1803, -+ ProviderUser_Invited = 1800, -+ ProviderUser_Confirmed = 1801, -+ ProviderUser_Updated = 1802, -+ ProviderUser_Removed = 1803, - -- ProviderOrganization_Created = 1900, -- ProviderOrganization_Added = 1901, -- ProviderOrganization_Removed = 1902, -- ProviderOrganization_VaultAccessed = 1903, -+ ProviderOrganization_Created = 1900, -+ ProviderOrganization_Added = 1901, -+ ProviderOrganization_Removed = 1902, -+ ProviderOrganization_VaultAccessed = 1903, - } -diff --git a/jslib/common/src/enums/fieldType.ts b/jslib/common/src/enums/fieldType.ts -index 594beba4..d6deb30e 100644 ---- a/jslib/common/src/enums/fieldType.ts -+++ b/jslib/common/src/enums/fieldType.ts -@@ -1,6 +1,6 @@ - export enum FieldType { -- Text = 0, -- Hidden = 1, -- Boolean = 2, -- Linked = 3, -+ Text = 0, -+ Hidden = 1, -+ Boolean = 2, -+ Linked = 3, - } -diff --git a/jslib/common/src/enums/fileUploadType.ts b/jslib/common/src/enums/fileUploadType.ts -index cbdd88fb..4cf654fd 100644 ---- a/jslib/common/src/enums/fileUploadType.ts -+++ b/jslib/common/src/enums/fileUploadType.ts -@@ -1,4 +1,4 @@ - export enum FileUploadType { -- Direct = 0, -- Azure = 1, -+ Direct = 0, -+ Azure = 1, - } -diff --git a/jslib/common/src/enums/hashPurpose.ts b/jslib/common/src/enums/hashPurpose.ts -index 90a1db9d..887931b9 100644 ---- a/jslib/common/src/enums/hashPurpose.ts -+++ b/jslib/common/src/enums/hashPurpose.ts -@@ -1,4 +1,4 @@ - export enum HashPurpose { -- ServerAuthorization = 1, -- LocalAuthorization = 2, -+ ServerAuthorization = 1, -+ LocalAuthorization = 2, - } -diff --git a/jslib/common/src/enums/htmlStorageLocation.ts b/jslib/common/src/enums/htmlStorageLocation.ts -new file mode 100644 -index 00000000..19c84fe8 ---- /dev/null -+++ b/jslib/common/src/enums/htmlStorageLocation.ts -@@ -0,0 +1,5 @@ -+export enum HtmlStorageLocation { -+ Local = "local", -+ Memory = "memory", -+ Session = "session", -+} -diff --git a/jslib/common/src/enums/kdfType.ts b/jslib/common/src/enums/kdfType.ts -index b23ef8e6..bf331fae 100644 ---- a/jslib/common/src/enums/kdfType.ts -+++ b/jslib/common/src/enums/kdfType.ts -@@ -1,3 +1,3 @@ - export enum KdfType { -- PBKDF2_SHA256 = 0, -+ PBKDF2_SHA256 = 0, - } -diff --git a/jslib/common/src/enums/keySuffixOptions.ts b/jslib/common/src/enums/keySuffixOptions.ts -new file mode 100644 -index 00000000..2ae98d8e ---- /dev/null -+++ b/jslib/common/src/enums/keySuffixOptions.ts -@@ -0,0 +1,4 @@ -+export enum KeySuffixOptions { -+ Auto = "auto", -+ Biometric = "biometric", -+} -diff --git a/jslib/common/src/enums/linkedIdType.ts b/jslib/common/src/enums/linkedIdType.ts -index b2cd2f1c..c38ebc1c 100644 ---- a/jslib/common/src/enums/linkedIdType.ts -+++ b/jslib/common/src/enums/linkedIdType.ts -@@ -2,39 +2,39 @@ export type LinkedIdType = LoginLinkedId | CardLinkedId | IdentityLinkedId; - - // LoginView - export enum LoginLinkedId { -- Username = 100, -- Password = 101, -+ Username = 100, -+ Password = 101, - } - - // CardView - export enum CardLinkedId { -- CardholderName = 300, -- ExpMonth = 301, -- ExpYear = 302, -- Code = 303, -- Brand = 304, -- Number = 305, -+ CardholderName = 300, -+ ExpMonth = 301, -+ ExpYear = 302, -+ Code = 303, -+ Brand = 304, -+ Number = 305, - } - - // IdentityView - export enum IdentityLinkedId { -- Title = 400, -- MiddleName = 401, -- Address1 = 402, -- Address2 = 403, -- Address3 = 404, -- City = 405, -- State = 406, -- PostalCode = 407, -- Country = 408, -- Company = 409, -- Email = 410, -- Phone = 411, -- Ssn = 412, -- Username = 413, -- PassportNumber = 414, -- LicenseNumber = 415, -- FirstName = 416, -- LastName = 417, -- FullName = 418, -+ Title = 400, -+ MiddleName = 401, -+ Address1 = 402, -+ Address2 = 403, -+ Address3 = 404, -+ City = 405, -+ State = 406, -+ PostalCode = 407, -+ Country = 408, -+ Company = 409, -+ Email = 410, -+ Phone = 411, -+ Ssn = 412, -+ Username = 413, -+ PassportNumber = 414, -+ LicenseNumber = 415, -+ FirstName = 416, -+ LastName = 417, -+ FullName = 418, - } -diff --git a/jslib/common/src/enums/logLevelType.ts b/jslib/common/src/enums/logLevelType.ts -index 09f84b80..709871dd 100644 ---- a/jslib/common/src/enums/logLevelType.ts -+++ b/jslib/common/src/enums/logLevelType.ts -@@ -1,6 +1,6 @@ - export enum LogLevelType { -- Debug, -- Info, -- Warning, -- Error, -+ Debug, -+ Info, -+ Warning, -+ Error, - } -diff --git a/jslib/common/src/enums/notificationType.ts b/jslib/common/src/enums/notificationType.ts -index 5adaac30..77ebde01 100644 ---- a/jslib/common/src/enums/notificationType.ts -+++ b/jslib/common/src/enums/notificationType.ts -@@ -1,20 +1,20 @@ - export enum NotificationType { -- SyncCipherUpdate = 0, -- SyncCipherCreate = 1, -- SyncLoginDelete = 2, -- SyncFolderDelete = 3, -- SyncCiphers = 4, -+ SyncCipherUpdate = 0, -+ SyncCipherCreate = 1, -+ SyncLoginDelete = 2, -+ SyncFolderDelete = 3, -+ SyncCiphers = 4, - -- SyncVault = 5, -- SyncOrgKeys = 6, -- SyncFolderCreate = 7, -- SyncFolderUpdate = 8, -- SyncCipherDelete = 9, -- SyncSettings = 10, -+ SyncVault = 5, -+ SyncOrgKeys = 6, -+ SyncFolderCreate = 7, -+ SyncFolderUpdate = 8, -+ SyncCipherDelete = 9, -+ SyncSettings = 10, - -- LogOut = 11, -+ LogOut = 11, - -- SyncSendCreate = 12, -- SyncSendUpdate = 13, -- SyncSendDelete = 14, -+ SyncSendCreate = 12, -+ SyncSendUpdate = 13, -+ SyncSendDelete = 14, - } -diff --git a/jslib/common/src/enums/organizationUserStatusType.ts b/jslib/common/src/enums/organizationUserStatusType.ts -index eae4a0a6..33422180 100644 ---- a/jslib/common/src/enums/organizationUserStatusType.ts -+++ b/jslib/common/src/enums/organizationUserStatusType.ts -@@ -1,5 +1,5 @@ - export enum OrganizationUserStatusType { -- Invited = 0, -- Accepted = 1, -- Confirmed = 2, -+ Invited = 0, -+ Accepted = 1, -+ Confirmed = 2, - } -diff --git a/jslib/common/src/enums/organizationUserType.ts b/jslib/common/src/enums/organizationUserType.ts -index 632d22b0..950fbaae 100644 ---- a/jslib/common/src/enums/organizationUserType.ts -+++ b/jslib/common/src/enums/organizationUserType.ts -@@ -1,7 +1,7 @@ - export enum OrganizationUserType { -- Owner = 0, -- Admin = 1, -- User = 2, -- Manager = 3, -- Custom = 4, -+ Owner = 0, -+ Admin = 1, -+ User = 2, -+ Manager = 3, -+ Custom = 4, - } -diff --git a/jslib/common/src/enums/paymentMethodType.ts b/jslib/common/src/enums/paymentMethodType.ts -index da3e50f6..701bd886 100644 ---- a/jslib/common/src/enums/paymentMethodType.ts -+++ b/jslib/common/src/enums/paymentMethodType.ts -@@ -1,11 +1,11 @@ - export enum PaymentMethodType { -- Card = 0, -- BankAccount = 1, -- PayPal = 2, -- BitPay = 3, -- Credit = 4, -- WireTransfer = 5, -- AppleInApp = 6, -- GoogleInApp = 7, -- Check = 8, -+ Card = 0, -+ BankAccount = 1, -+ PayPal = 2, -+ BitPay = 3, -+ Credit = 4, -+ WireTransfer = 5, -+ AppleInApp = 6, -+ GoogleInApp = 7, -+ Check = 8, - } -diff --git a/jslib/common/src/enums/permissions.ts b/jslib/common/src/enums/permissions.ts -index 8dcd9366..46a74455 100644 ---- a/jslib/common/src/enums/permissions.ts -+++ b/jslib/common/src/enums/permissions.ts -@@ -1,27 +1,27 @@ - export enum Permissions { -- AccessEventLogs, -- AccessImportExport, -- AccessReports, -- /** -- * @deprecated Sep 29 2021: This permission has been split out to `createNewCollections`, `editAnyCollection`, and -- * `deleteAnyCollection`. It exists here for backwards compatibility with Server versions <= 1.43.0 -- */ -- ManageAllCollections, -- /** -- * @deprecated Sep 29 2021: This permission has been split out to `editAssignedCollections` and -- * `deleteAssignedCollections`. It exists here for backwards compatibility with Server versions <= 1.43.0 -- */ -- ManageAssignedCollections, -- ManageGroups, -- ManageOrganization , -- ManagePolicies, -- ManageProvider, -- ManageUsers, -- ManageUsersPassword, -- CreateNewCollections, -- EditAnyCollection, -- DeleteAnyCollection, -- EditAssignedCollections, -- DeleteAssignedCollections, -- ManageSso, -+ AccessEventLogs, -+ AccessImportExport, -+ AccessReports, -+ /** -+ * @deprecated Sep 29 2021: This permission has been split out to `createNewCollections`, `editAnyCollection`, and -+ * `deleteAnyCollection`. It exists here for backwards compatibility with Server versions <= 1.43.0 -+ */ -+ ManageAllCollections, -+ /** -+ * @deprecated Sep 29 2021: This permission has been split out to `editAssignedCollections` and -+ * `deleteAssignedCollections`. It exists here for backwards compatibility with Server versions <= 1.43.0 -+ */ -+ ManageAssignedCollections, -+ ManageGroups, -+ ManageOrganization, -+ ManagePolicies, -+ ManageProvider, -+ ManageUsers, -+ ManageUsersPassword, -+ CreateNewCollections, -+ EditAnyCollection, -+ DeleteAnyCollection, -+ EditAssignedCollections, -+ DeleteAssignedCollections, -+ ManageSso, - } -diff --git a/jslib/common/src/enums/planSponsorshipType.ts b/jslib/common/src/enums/planSponsorshipType.ts -index 330b7ec0..3b4c0046 100644 ---- a/jslib/common/src/enums/planSponsorshipType.ts -+++ b/jslib/common/src/enums/planSponsorshipType.ts -@@ -1,3 +1,3 @@ - export enum PlanSponsorshipType { -- FamiliesForEnterprise = 0, -+ FamiliesForEnterprise = 0, - } -diff --git a/jslib/common/src/enums/planType.ts b/jslib/common/src/enums/planType.ts -index 1b07c723..f4c89d2c 100644 ---- a/jslib/common/src/enums/planType.ts -+++ b/jslib/common/src/enums/planType.ts -@@ -1,14 +1,14 @@ - export enum PlanType { -- Free = 0, -- FamiliesAnnually2019 = 1, -- TeamsMonthly2019 = 2, -- TeamsAnnually2019 = 3, -- EnterpriseMonthly2019 = 4, -- EnterpriseAnnually2019 = 5, -- Custom = 6, -- FamiliesAnnually = 7, -- TeamsMonthly = 8, -- TeamsAnnually = 9, -- EnterpriseMonthly = 10, -- EnterpriseAnnually = 11, -+ Free = 0, -+ FamiliesAnnually2019 = 1, -+ TeamsMonthly2019 = 2, -+ TeamsAnnually2019 = 3, -+ EnterpriseMonthly2019 = 4, -+ EnterpriseAnnually2019 = 5, -+ Custom = 6, -+ FamiliesAnnually = 7, -+ TeamsMonthly = 8, -+ TeamsAnnually = 9, -+ EnterpriseMonthly = 10, -+ EnterpriseAnnually = 11, - } -diff --git a/jslib/common/src/enums/policyType.ts b/jslib/common/src/enums/policyType.ts -index 244ed4a1..02dce41e 100644 ---- a/jslib/common/src/enums/policyType.ts -+++ b/jslib/common/src/enums/policyType.ts -@@ -1,13 +1,13 @@ - export enum PolicyType { -- TwoFactorAuthentication = 0, // Requires users to have 2fa enabled -- MasterPassword = 1, // Sets minimum requirements for master password complexity -- PasswordGenerator = 2, // Sets minimum requirements/default type for generated passwords/passphrases -- SingleOrg = 3, // Allows users to only be apart of one organization -- RequireSso = 4, // Requires users to authenticate with SSO -- PersonalOwnership = 5, // Disables personal vault ownership for adding/cloning items -- DisableSend = 6, // Disables the ability to create and edit Bitwarden Sends -- SendOptions = 7, // Sets restrictions or defaults for Bitwarden Sends -- ResetPassword = 8, // Allows orgs to use reset password : also can enable auto-enrollment during invite flow -- MaximumVaultTimeout = 9, // Sets the maximum allowed vault timeout -- DisablePersonalVaultExport = 10, // Disable personal vault export -+ TwoFactorAuthentication = 0, // Requires users to have 2fa enabled -+ MasterPassword = 1, // Sets minimum requirements for master password complexity -+ PasswordGenerator = 2, // Sets minimum requirements/default type for generated passwords/passphrases -+ SingleOrg = 3, // Allows users to only be apart of one organization -+ RequireSso = 4, // Requires users to authenticate with SSO -+ PersonalOwnership = 5, // Disables personal vault ownership for adding/cloning items -+ DisableSend = 6, // Disables the ability to create and edit Bitwarden Sends -+ SendOptions = 7, // Sets restrictions or defaults for Bitwarden Sends -+ ResetPassword = 8, // Allows orgs to use reset password : also can enable auto-enrollment during invite flow -+ MaximumVaultTimeout = 9, // Sets the maximum allowed vault timeout -+ DisablePersonalVaultExport = 10, // Disable personal vault export - } -diff --git a/jslib/common/src/enums/productType.ts b/jslib/common/src/enums/productType.ts -index d8e2e0ae..50b836d1 100644 ---- a/jslib/common/src/enums/productType.ts -+++ b/jslib/common/src/enums/productType.ts -@@ -1,6 +1,6 @@ - export enum ProductType { -- Free = 0, -- Families = 1, -- Teams = 2, -- Enterprise = 3, -+ Free = 0, -+ Families = 1, -+ Teams = 2, -+ Enterprise = 3, - } -diff --git a/jslib/common/src/enums/providerUserStatusType.ts b/jslib/common/src/enums/providerUserStatusType.ts -index 8b0e55de..3b845551 100644 ---- a/jslib/common/src/enums/providerUserStatusType.ts -+++ b/jslib/common/src/enums/providerUserStatusType.ts -@@ -1,5 +1,5 @@ - export enum ProviderUserStatusType { -- Invited = 0, -- Accepted = 1, -- Confirmed = 2, -+ Invited = 0, -+ Accepted = 1, -+ Confirmed = 2, - } -diff --git a/jslib/common/src/enums/providerUserType.ts b/jslib/common/src/enums/providerUserType.ts -index 326fa0aa..00490adc 100644 ---- a/jslib/common/src/enums/providerUserType.ts -+++ b/jslib/common/src/enums/providerUserType.ts -@@ -1,4 +1,4 @@ - export enum ProviderUserType { -- ProviderAdmin = 0, -- ServiceUser = 1, -+ ProviderAdmin = 0, -+ ServiceUser = 1, - } -diff --git a/jslib/common/src/enums/secureNoteType.ts b/jslib/common/src/enums/secureNoteType.ts -index c7f3e44a..8015236d 100644 ---- a/jslib/common/src/enums/secureNoteType.ts -+++ b/jslib/common/src/enums/secureNoteType.ts -@@ -1,3 +1,3 @@ - export enum SecureNoteType { -- Generic = 0, -+ Generic = 0, - } -diff --git a/jslib/common/src/enums/sendType.ts b/jslib/common/src/enums/sendType.ts -index f5715d86..487930c9 100644 ---- a/jslib/common/src/enums/sendType.ts -+++ b/jslib/common/src/enums/sendType.ts -@@ -1,4 +1,4 @@ - export enum SendType { -- Text = 0, -- File = 1, -+ Text = 0, -+ File = 1, - } -diff --git a/jslib/common/src/enums/stateVersion.ts b/jslib/common/src/enums/stateVersion.ts -new file mode 100644 -index 00000000..aa45edbb ---- /dev/null -+++ b/jslib/common/src/enums/stateVersion.ts -@@ -0,0 +1,5 @@ -+export enum StateVersion { -+ One = 1, // Original flat key/value pair store -+ Two = 2, // Move to a typed State object -+ Latest = Two, -+} -diff --git a/jslib/common/src/enums/storageLocation.ts b/jslib/common/src/enums/storageLocation.ts -new file mode 100644 -index 00000000..46d50d1d ---- /dev/null -+++ b/jslib/common/src/enums/storageLocation.ts -@@ -0,0 +1,5 @@ -+export enum StorageLocation { -+ Both = "both", -+ Disk = "disk", -+ Memory = "memory", -+} -diff --git a/jslib/common/src/enums/themeType.ts b/jslib/common/src/enums/themeType.ts -index 40e7c2dd..8afca770 100644 ---- a/jslib/common/src/enums/themeType.ts -+++ b/jslib/common/src/enums/themeType.ts -@@ -1,7 +1,7 @@ - export enum ThemeType { -- System = 'system', -- Light = 'light', -- Dark = 'dark', -- Nord = 'nord', -- SolarizedDark = 'solarizedDark', -+ System = "system", -+ Light = "light", -+ Dark = "dark", -+ Nord = "nord", -+ SolarizedDark = "solarizedDark", - } -diff --git a/jslib/common/src/enums/transactionType.ts b/jslib/common/src/enums/transactionType.ts -index 68cce632..34731a2e 100644 ---- a/jslib/common/src/enums/transactionType.ts -+++ b/jslib/common/src/enums/transactionType.ts -@@ -1,7 +1,7 @@ - export enum TransactionType { -- Charge = 0, -- Credit = 1, -- PromotionalCredit = 2, -- ReferralCredit = 3, -- Refund = 4, -+ Charge = 0, -+ Credit = 1, -+ PromotionalCredit = 2, -+ ReferralCredit = 3, -+ Refund = 4, - } -diff --git a/jslib/common/src/enums/twoFactorProviderType.ts b/jslib/common/src/enums/twoFactorProviderType.ts -index c0de3f4a..a1708032 100644 ---- a/jslib/common/src/enums/twoFactorProviderType.ts -+++ b/jslib/common/src/enums/twoFactorProviderType.ts -@@ -1,10 +1,10 @@ - export enum TwoFactorProviderType { -- Authenticator = 0, -- Email = 1, -- Duo = 2, -- Yubikey = 3, -- U2f = 4, -- Remember = 5, -- OrganizationDuo = 6, -- WebAuthn = 7, -+ Authenticator = 0, -+ Email = 1, -+ Duo = 2, -+ Yubikey = 3, -+ U2f = 4, -+ Remember = 5, -+ OrganizationDuo = 6, -+ WebAuthn = 7, - } -diff --git a/jslib/common/src/enums/uriMatchType.ts b/jslib/common/src/enums/uriMatchType.ts -index e4c0ef48..4a4193ba 100644 ---- a/jslib/common/src/enums/uriMatchType.ts -+++ b/jslib/common/src/enums/uriMatchType.ts -@@ -1,8 +1,8 @@ - export enum UriMatchType { -- Domain = 0, -- Host = 1, -- StartsWith = 2, -- Exact = 3, -- RegularExpression = 4, -- Never = 5, -+ Domain = 0, -+ Host = 1, -+ StartsWith = 2, -+ Exact = 3, -+ RegularExpression = 4, -+ Never = 5, - } -diff --git a/jslib/common/src/enums/verificationType.ts b/jslib/common/src/enums/verificationType.ts -index 254da418..76a51ab7 100644 ---- a/jslib/common/src/enums/verificationType.ts -+++ b/jslib/common/src/enums/verificationType.ts -@@ -1,4 +1,4 @@ - export enum VerificationType { -- MasterPassword = 0, -- OTP = 1, -+ MasterPassword = 0, -+ OTP = 1, - } -diff --git a/jslib/common/src/factories/accountFactory.ts b/jslib/common/src/factories/accountFactory.ts -new file mode 100644 -index 00000000..1fe5aee3 ---- /dev/null -+++ b/jslib/common/src/factories/accountFactory.ts -@@ -0,0 +1,13 @@ -+import { Account } from "../models/domain/account"; -+ -+export class AccountFactory { -+ private accountConstructor: new (init: Partial) => T; -+ -+ constructor(accountConstructor: new (init: Partial) => T) { -+ this.accountConstructor = accountConstructor; -+ } -+ -+ create(args: Partial) { -+ return new this.accountConstructor(args); -+ } -+} -diff --git a/jslib/common/src/factories/globalStateFactory.ts b/jslib/common/src/factories/globalStateFactory.ts -new file mode 100644 -index 00000000..a2c25c46 ---- /dev/null -+++ b/jslib/common/src/factories/globalStateFactory.ts -@@ -0,0 +1,13 @@ -+import { GlobalState } from "../models/domain/globalState"; -+ -+export class GlobalStateFactory { -+ private globalStateConstructor: new (init: Partial) => T; -+ -+ constructor(globalStateConstructor: new (init: Partial) => T) { -+ this.globalStateConstructor = globalStateConstructor; -+ } -+ -+ create(args?: Partial) { -+ return new this.globalStateConstructor(args); -+ } -+} -diff --git a/jslib/common/src/factories/stateFactory.ts b/jslib/common/src/factories/stateFactory.ts -new file mode 100644 -index 00000000..5df194af ---- /dev/null -+++ b/jslib/common/src/factories/stateFactory.ts -@@ -0,0 +1,25 @@ -+import { Account } from "../models/domain/account"; -+import { GlobalState } from "../models/domain/globalState"; -+import { AccountFactory } from "./accountFactory"; -+import { GlobalStateFactory } from "./globalStateFactory"; -+ -+export class StateFactory { -+ private globalStateFactory: GlobalStateFactory; -+ private accountFactory: AccountFactory; -+ -+ constructor( -+ globalStateConstructor: new (init: Partial) => TGlobal, -+ accountConstructor: new (init: Partial) => TAccount -+ ) { -+ this.globalStateFactory = new GlobalStateFactory(globalStateConstructor); -+ this.accountFactory = new AccountFactory(accountConstructor); -+ } -+ -+ createGlobal(args: Partial): TGlobal { -+ return this.globalStateFactory.create(args); -+ } -+ -+ createAccount(args: Partial): TAccount { -+ return this.accountFactory.create(args); -+ } -+} -diff --git a/jslib/common/src/importers/ascendoCsvImporter.ts b/jslib/common/src/importers/ascendoCsvImporter.ts -index c8179dc6..aed1c3ab 100644 ---- a/jslib/common/src/importers/ascendoCsvImporter.ts -+++ b/jslib/common/src/importers/ascendoCsvImporter.ts -@@ -1,55 +1,59 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class AscendoCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, false); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, false); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } -+ -+ results.forEach((value) => { -+ if (value.length < 2) { -+ return; -+ } -+ -+ const cipher = this.initLoginCipher(); -+ cipher.notes = this.getValueOrDefault(value[value.length - 1]); -+ cipher.name = this.getValueOrDefault(value[0], "--"); -+ -+ if (value.length > 2 && value.length % 2 === 0) { -+ for (let i = 0; i < value.length - 2; i += 2) { -+ const val: string = value[i + 2]; -+ const field: string = value[i + 1]; -+ if (this.isNullOrWhitespace(val) || this.isNullOrWhitespace(field)) { -+ continue; -+ } -+ -+ const fieldLower = field.toLowerCase(); -+ if (cipher.login.password == null && this.passwordFieldNames.indexOf(fieldLower) > -1) { -+ cipher.login.password = this.getValueOrDefault(val); -+ } else if ( -+ cipher.login.username == null && -+ this.usernameFieldNames.indexOf(fieldLower) > -1 -+ ) { -+ cipher.login.username = this.getValueOrDefault(val); -+ } else if ( -+ (cipher.login.uris == null || cipher.login.uris.length === 0) && -+ this.uriFieldNames.indexOf(fieldLower) > -1 -+ ) { -+ cipher.login.uris = this.makeUriArray(val); -+ } else { -+ this.processKvp(cipher, field, val); -+ } - } -+ } - -- results.forEach(value => { -- if (value.length < 2) { -- return; -- } -- -- const cipher = this.initLoginCipher(); -- cipher.notes = this.getValueOrDefault(value[value.length - 1]); -- cipher.name = this.getValueOrDefault(value[0], '--'); -- -- if (value.length > 2 && (value.length % 2) === 0) { -- for (let i = 0; i < value.length - 2; i += 2) { -- const val: string = value[i + 2]; -- const field: string = value[i + 1]; -- if (this.isNullOrWhitespace(val) || this.isNullOrWhitespace(field)) { -- continue; -- } -- -- const fieldLower = field.toLowerCase(); -- if (cipher.login.password == null && this.passwordFieldNames.indexOf(fieldLower) > -1) { -- cipher.login.password = this.getValueOrDefault(val); -- } else if (cipher.login.username == null && -- this.usernameFieldNames.indexOf(fieldLower) > -1) { -- cipher.login.username = this.getValueOrDefault(val); -- } else if ((cipher.login.uris == null || cipher.login.uris.length === 0) && -- this.uriFieldNames.indexOf(fieldLower) > -1) { -- cipher.login.uris = this.makeUriArray(val); -- } else { -- this.processKvp(cipher, field, val); -- } -- } -- } -- -- this.convertToNoteIfNeeded(cipher); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -- -- result.success = true; -- return Promise.resolve(result); -- } -+ this.convertToNoteIfNeeded(cipher); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/avastCsvImporter.ts b/jslib/common/src/importers/avastCsvImporter.ts -index 5519b67a..9629fb2f 100644 ---- a/jslib/common/src/importers/avastCsvImporter.ts -+++ b/jslib/common/src/importers/avastCsvImporter.ts -@@ -1,28 +1,28 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class AvastCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- results.forEach(value => { -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value.name); -- cipher.login.uris = this.makeUriArray(value.web); -- cipher.login.password = this.getValueOrDefault(value.password); -- cipher.login.username = this.getValueOrDefault(value.login); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ results.forEach((value) => { -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(value.name); -+ cipher.login.uris = this.makeUriArray(value.web); -+ cipher.login.password = this.getValueOrDefault(value.password); -+ cipher.login.username = this.getValueOrDefault(value.login); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -- } -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/avastJsonImporter.ts b/jslib/common/src/importers/avastJsonImporter.ts -index 9f8d7752..fe853539 100644 ---- a/jslib/common/src/importers/avastJsonImporter.ts -+++ b/jslib/common/src/importers/avastJsonImporter.ts -@@ -1,69 +1,69 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - --import { CipherType } from '../enums/cipherType'; --import { SecureNoteType } from '../enums/secureNoteType'; -+import { CipherType } from "../enums/cipherType"; -+import { SecureNoteType } from "../enums/secureNoteType"; - - export class AvastJsonImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = JSON.parse(data); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = JSON.parse(data); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- if (results.logins != null) { -- results.logins.forEach((value: any) => { -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value.custName); -- cipher.notes = this.getValueOrDefault(value.note); -- cipher.login.uris = this.makeUriArray(value.url); -- cipher.login.password = this.getValueOrDefault(value.pwd); -- cipher.login.username = this.getValueOrDefault(value.loginName); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -- } -+ if (results.logins != null) { -+ results.logins.forEach((value: any) => { -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(value.custName); -+ cipher.notes = this.getValueOrDefault(value.note); -+ cipher.login.uris = this.makeUriArray(value.url); -+ cipher.login.password = this.getValueOrDefault(value.pwd); -+ cipher.login.username = this.getValueOrDefault(value.loginName); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); -+ } - -- if (results.notes != null) { -- results.notes.forEach((value: any) => { -- const cipher = this.initLoginCipher(); -- cipher.type = CipherType.SecureNote; -- cipher.secureNote.type = SecureNoteType.Generic; -- cipher.name = this.getValueOrDefault(value.label); -- cipher.notes = this.getValueOrDefault(value.text); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -- } -+ if (results.notes != null) { -+ results.notes.forEach((value: any) => { -+ const cipher = this.initLoginCipher(); -+ cipher.type = CipherType.SecureNote; -+ cipher.secureNote.type = SecureNoteType.Generic; -+ cipher.name = this.getValueOrDefault(value.label); -+ cipher.notes = this.getValueOrDefault(value.text); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); -+ } - -- if (results.cards != null) { -- results.cards.forEach((value: any) => { -- const cipher = this.initLoginCipher(); -- cipher.type = CipherType.Card; -- cipher.name = this.getValueOrDefault(value.custName); -- cipher.notes = this.getValueOrDefault(value.note); -- cipher.card.cardholderName = this.getValueOrDefault(value.holderName); -- cipher.card.number = this.getValueOrDefault(value.cardNumber); -- cipher.card.code = this.getValueOrDefault(value.cvv); -- cipher.card.brand = this.getCardBrand(cipher.card.number); -- if (value.expirationDate != null) { -- if (value.expirationDate.month != null) { -- cipher.card.expMonth = value.expirationDate.month + ''; -- } -- if (value.expirationDate.year != null) { -- cipher.card.expYear = value.expirationDate.year + ''; -- } -- } -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ if (results.cards != null) { -+ results.cards.forEach((value: any) => { -+ const cipher = this.initLoginCipher(); -+ cipher.type = CipherType.Card; -+ cipher.name = this.getValueOrDefault(value.custName); -+ cipher.notes = this.getValueOrDefault(value.note); -+ cipher.card.cardholderName = this.getValueOrDefault(value.holderName); -+ cipher.card.number = this.getValueOrDefault(value.cardNumber); -+ cipher.card.code = this.getValueOrDefault(value.cvv); -+ cipher.card.brand = this.getCardBrand(cipher.card.number); -+ if (value.expirationDate != null) { -+ if (value.expirationDate.month != null) { -+ cipher.card.expMonth = value.expirationDate.month + ""; -+ } -+ if (value.expirationDate.year != null) { -+ cipher.card.expYear = value.expirationDate.year + ""; -+ } - } -- -- result.success = true; -- return Promise.resolve(result); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - } -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/aviraCsvImporter.ts b/jslib/common/src/importers/aviraCsvImporter.ts -index 1547d7f2..fa983080 100644 ---- a/jslib/common/src/importers/aviraCsvImporter.ts -+++ b/jslib/common/src/importers/aviraCsvImporter.ts -@@ -1,36 +1,41 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class AviraCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- results.forEach(value => { -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value.name, -- this.getValueOrDefault(this.nameFromUrl(value.website), '--')); -- cipher.login.uris = this.makeUriArray(value.website); -- cipher.login.password = this.getValueOrDefault(value.password); -+ results.forEach((value) => { -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault( -+ value.name, -+ this.getValueOrDefault(this.nameFromUrl(value.website), "--") -+ ); -+ cipher.login.uris = this.makeUriArray(value.website); -+ cipher.login.password = this.getValueOrDefault(value.password); - -- if (this.isNullOrWhitespace(value.username) && !this.isNullOrWhitespace(value.secondary_username)) { -- cipher.login.username = value.secondary_username; -- } else { -- cipher.login.username = this.getValueOrDefault(value.username); -- cipher.notes = this.getValueOrDefault(value.secondary_username); -- } -+ if ( -+ this.isNullOrWhitespace(value.username) && -+ !this.isNullOrWhitespace(value.secondary_username) -+ ) { -+ cipher.login.username = value.secondary_username; -+ } else { -+ cipher.login.username = this.getValueOrDefault(value.username); -+ cipher.notes = this.getValueOrDefault(value.secondary_username); -+ } - -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -- } -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/baseImporter.ts b/jslib/common/src/importers/baseImporter.ts -index 194d5843..65253c71 100644 ---- a/jslib/common/src/importers/baseImporter.ts -+++ b/jslib/common/src/importers/baseImporter.ts -@@ -1,377 +1,457 @@ --import * as papa from 'papaparse'; -+import * as papa from "papaparse"; - --import { LogService } from '../abstractions/log.service'; -+import { LogService } from "../abstractions/log.service"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - --import { CipherView } from '../models/view/cipherView'; --import { CollectionView } from '../models/view/collectionView'; --import { LoginUriView } from '../models/view/loginUriView'; -+import { CipherView } from "../models/view/cipherView"; -+import { CollectionView } from "../models/view/collectionView"; -+import { LoginUriView } from "../models/view/loginUriView"; - --import { Utils } from '../misc/utils'; -+import { Utils } from "../misc/utils"; - --import { FieldView } from '../models/view/fieldView'; --import { FolderView } from '../models/view/folderView'; --import { LoginView } from '../models/view/loginView'; --import { SecureNoteView } from '../models/view/secureNoteView'; -+import { FieldView } from "../models/view/fieldView"; -+import { FolderView } from "../models/view/folderView"; -+import { LoginView } from "../models/view/loginView"; -+import { SecureNoteView } from "../models/view/secureNoteView"; - --import { CipherRepromptType } from '../enums/cipherRepromptType'; --import { CipherType } from '../enums/cipherType'; --import { FieldType } from '../enums/fieldType'; --import { SecureNoteType } from '../enums/secureNoteType'; -+import { CipherRepromptType } from "../enums/cipherRepromptType"; -+import { CipherType } from "../enums/cipherType"; -+import { FieldType } from "../enums/fieldType"; -+import { SecureNoteType } from "../enums/secureNoteType"; - --import { ConsoleLogService } from '../services/consoleLog.service'; -+import { ConsoleLogService } from "../services/consoleLog.service"; - - export abstract class BaseImporter { -- organizationId: string = null; -- -- protected logService: LogService = new ConsoleLogService(false); -- -- protected newLineRegex = /(?:\r\n|\r|\n)/; -- -- protected passwordFieldNames = [ -- 'password', 'pass word', 'passphrase', 'pass phrase', -- 'pass', 'code', 'code word', 'codeword', -- 'secret', 'secret word', 'personpwd', -- 'key', 'keyword', 'key word', 'keyphrase', 'key phrase', -- 'form_pw', 'wppassword', 'pin', 'pwd', 'pw', 'pword', 'passwd', -- 'p', 'serial', 'serial#', 'license key', 'reg #', -- -- // Non-English names -- 'passwort', -- ]; -- -- protected usernameFieldNames = [ -- 'user', 'name', 'user name', 'username', 'login name', -- 'email', 'e-mail', 'id', 'userid', 'user id', -- 'login', 'form_loginname', 'wpname', 'mail', -- 'loginid', 'login id', 'log', 'personlogin', -- 'first name', 'last name', 'card#', 'account #', -- 'member', 'member #', -- -- // Non-English names -- 'nom', 'benutzername', -- ]; -- -- protected notesFieldNames = [ -- 'note', 'notes', 'comment', 'comments', 'memo', -- 'description', 'free form', 'freeform', -- 'free text', 'freetext', 'free', -- -- // Non-English names -- 'kommentar', -- ]; -- -- protected uriFieldNames: string[] = [ -- 'url', 'hyper link', 'hyperlink', 'link', -- 'host', 'hostname', 'host name', 'server', 'address', -- 'hyper ref', 'href', 'web', 'website', 'web site', 'site', -- 'web-site', 'uri', -- -- // Non-English names -- 'ort', 'adresse', -- ]; -- -- protected parseCsvOptions = { -- encoding: 'UTF-8', -- skipEmptyLines: false, -- }; -- -- protected get organization() { -- return this.organizationId != null; -- } -- -- protected parseXml(data: string): Document { -- const parser = new DOMParser(); -- const doc = parser.parseFromString(data, 'application/xml'); -- return doc != null && doc.querySelector('parsererror') == null ? doc : null; -- } -- -- protected parseCsv(data: string, header: boolean, options: any = {}): any[] { -- const parseOptions = Object.assign({ header: header }, this.parseCsvOptions, options); -- data = this.splitNewLine(data).join('\n').trim(); -- const result = papa.parse(data, parseOptions); -- if (result.errors != null && result.errors.length > 0) { -- result.errors.forEach(e => { -- if (e.row != null) { -- // tslint:disable-next-line -- this.logService.warning('Error parsing row ' + e.row + ': ' + e.message); -- } -- }); -- } -- return result.data && result.data.length > 0 ? result.data : null; -+ organizationId: string = null; -+ -+ protected logService: LogService = new ConsoleLogService(false); -+ -+ protected newLineRegex = /(?:\r\n|\r|\n)/; -+ -+ protected passwordFieldNames = [ -+ "password", -+ "pass word", -+ "passphrase", -+ "pass phrase", -+ "pass", -+ "code", -+ "code word", -+ "codeword", -+ "secret", -+ "secret word", -+ "personpwd", -+ "key", -+ "keyword", -+ "key word", -+ "keyphrase", -+ "key phrase", -+ "form_pw", -+ "wppassword", -+ "pin", -+ "pwd", -+ "pw", -+ "pword", -+ "passwd", -+ "p", -+ "serial", -+ "serial#", -+ "license key", -+ "reg #", -+ -+ // Non-English names -+ "passwort", -+ ]; -+ -+ protected usernameFieldNames = [ -+ "user", -+ "name", -+ "user name", -+ "username", -+ "login name", -+ "email", -+ "e-mail", -+ "id", -+ "userid", -+ "user id", -+ "login", -+ "form_loginname", -+ "wpname", -+ "mail", -+ "loginid", -+ "login id", -+ "log", -+ "personlogin", -+ "first name", -+ "last name", -+ "card#", -+ "account #", -+ "member", -+ "member #", -+ -+ // Non-English names -+ "nom", -+ "benutzername", -+ ]; -+ -+ protected notesFieldNames = [ -+ "note", -+ "notes", -+ "comment", -+ "comments", -+ "memo", -+ "description", -+ "free form", -+ "freeform", -+ "free text", -+ "freetext", -+ "free", -+ -+ // Non-English names -+ "kommentar", -+ ]; -+ -+ protected uriFieldNames: string[] = [ -+ "url", -+ "hyper link", -+ "hyperlink", -+ "link", -+ "host", -+ "hostname", -+ "host name", -+ "server", -+ "address", -+ "hyper ref", -+ "href", -+ "web", -+ "website", -+ "web site", -+ "site", -+ "web-site", -+ "uri", -+ -+ // Non-English names -+ "ort", -+ "adresse", -+ ]; -+ -+ protected parseCsvOptions = { -+ encoding: "UTF-8", -+ skipEmptyLines: false, -+ }; -+ -+ protected get organization() { -+ return this.organizationId != null; -+ } -+ -+ protected parseXml(data: string): Document { -+ const parser = new DOMParser(); -+ const doc = parser.parseFromString(data, "application/xml"); -+ return doc != null && doc.querySelector("parsererror") == null ? doc : null; -+ } -+ -+ protected parseCsv(data: string, header: boolean, options: any = {}): any[] { -+ const parseOptions: papa.ParseConfig = Object.assign( -+ { header: header }, -+ this.parseCsvOptions, -+ options -+ ); -+ data = this.splitNewLine(data).join("\n").trim(); -+ const result = papa.parse(data, parseOptions); -+ if (result.errors != null && result.errors.length > 0) { -+ result.errors.forEach((e) => { -+ if (e.row != null) { -+ // tslint:disable-next-line -+ this.logService.warning("Error parsing row " + e.row + ": " + e.message); -+ } -+ }); - } -+ return result.data && result.data.length > 0 ? result.data : null; -+ } - -- protected parseSingleRowCsv(rowData: string) { -- if (this.isNullOrWhitespace(rowData)) { -- return null; -- } -- const parsedRow = this.parseCsv(rowData, false); -- if (parsedRow != null && parsedRow.length > 0 && parsedRow[0].length > 0) { -- return parsedRow[0]; -- } -- return null; -+ protected parseSingleRowCsv(rowData: string) { -+ if (this.isNullOrWhitespace(rowData)) { -+ return null; - } -+ const parsedRow = this.parseCsv(rowData, false); -+ if (parsedRow != null && parsedRow.length > 0 && parsedRow[0].length > 0) { -+ return parsedRow[0]; -+ } -+ return null; -+ } - -- protected makeUriArray(uri: string | string[]): LoginUriView[] { -- if (uri == null) { -- return null; -- } -- -- if (typeof uri === 'string') { -- const loginUri = new LoginUriView(); -- loginUri.uri = this.fixUri(uri); -- if (this.isNullOrWhitespace(loginUri.uri)) { -- return null; -- } -- loginUri.match = null; -- return [loginUri]; -- } -- -- if (uri.length > 0) { -- const returnArr: LoginUriView[] = []; -- uri.forEach(u => { -- const loginUri = new LoginUriView(); -- loginUri.uri = this.fixUri(u); -- if (this.isNullOrWhitespace(loginUri.uri)) { -- return; -- } -- loginUri.match = null; -- returnArr.push(loginUri); -- }); -- return returnArr.length === 0 ? null : returnArr; -- } -+ protected makeUriArray(uri: string | string[]): LoginUriView[] { -+ if (uri == null) { -+ return null; -+ } - -+ if (typeof uri === "string") { -+ const loginUri = new LoginUriView(); -+ loginUri.uri = this.fixUri(uri); -+ if (this.isNullOrWhitespace(loginUri.uri)) { - return null; -+ } -+ loginUri.match = null; -+ return [loginUri]; - } - -- protected fixUri(uri: string) { -- if (uri == null) { -- return null; -- } -- uri = uri.trim(); -- if (uri.indexOf('://') === -1 && uri.indexOf('.') >= 0) { -- uri = 'http://' + uri; -- } -- if (uri.length > 1000) { -- return uri.substring(0, 1000); -- } -- return uri; -+ if (uri.length > 0) { -+ const returnArr: LoginUriView[] = []; -+ uri.forEach((u) => { -+ const loginUri = new LoginUriView(); -+ loginUri.uri = this.fixUri(u); -+ if (this.isNullOrWhitespace(loginUri.uri)) { -+ return; -+ } -+ loginUri.match = null; -+ returnArr.push(loginUri); -+ }); -+ return returnArr.length === 0 ? null : returnArr; - } - -- protected nameFromUrl(url: string) { -- const hostname = Utils.getHostname(url); -- if (this.isNullOrWhitespace(hostname)) { -- return null; -- } -- return hostname.startsWith('www.') ? hostname.replace('www.', '') : hostname; -- } -+ return null; -+ } - -- protected isNullOrWhitespace(str: string): boolean { -- return Utils.isNullOrWhitespace(str); -+ protected fixUri(uri: string) { -+ if (uri == null) { -+ return null; - } -- -- protected getValueOrDefault(str: string, defaultValue: string = null): string { -- if (this.isNullOrWhitespace(str)) { -- return defaultValue; -- } -- return str; -+ uri = uri.trim(); -+ if (uri.indexOf("://") === -1 && uri.indexOf(".") >= 0) { -+ uri = "http://" + uri; - } -- -- protected splitNewLine(str: string): string[] { -- return str.split(this.newLineRegex); -+ if (uri.length > 1000) { -+ return uri.substring(0, 1000); - } -+ return uri; -+ } - -- // ref https://stackoverflow.com/a/5911300 -- protected getCardBrand(cardNum: string) { -- if (this.isNullOrWhitespace(cardNum)) { -- return null; -- } -- -- // Visa -- let re = new RegExp('^4'); -- if (cardNum.match(re) != null) { -- return 'Visa'; -- } -- -- // Mastercard -- // Updated for Mastercard 2017 BINs expansion -- if (/^(5[1-5][0-9]{14}|2(22[1-9][0-9]{12}|2[3-9][0-9]{13}|[3-6][0-9]{14}|7[0-1][0-9]{13}|720[0-9]{12}))$/ -- .test(cardNum)) { -- return 'Mastercard'; -- } -- -- // AMEX -- re = new RegExp('^3[47]'); -- if (cardNum.match(re) != null) { -- return 'Amex'; -- } -- -- // Discover -- re = new RegExp('^(6011|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5]|64[4-9])|65)'); -- if (cardNum.match(re) != null) { -- return 'Discover'; -- } -+ protected nameFromUrl(url: string) { -+ const hostname = Utils.getHostname(url); -+ if (this.isNullOrWhitespace(hostname)) { -+ return null; -+ } -+ return hostname.startsWith("www.") ? hostname.replace("www.", "") : hostname; -+ } - -- // Diners -- re = new RegExp('^36'); -- if (cardNum.match(re) != null) { -- return 'Diners Club'; -- } -+ protected isNullOrWhitespace(str: string): boolean { -+ return Utils.isNullOrWhitespace(str); -+ } - -- // Diners - Carte Blanche -- re = new RegExp('^30[0-5]'); -- if (cardNum.match(re) != null) { -- return 'Diners Club'; -- } -+ protected getValueOrDefault(str: string, defaultValue: string = null): string { -+ if (this.isNullOrWhitespace(str)) { -+ return defaultValue; -+ } -+ return str; -+ } - -- // JCB -- re = new RegExp('^35(2[89]|[3-8][0-9])'); -- if (cardNum.match(re) != null) { -- return 'JCB'; -- } -+ protected splitNewLine(str: string): string[] { -+ return str.split(this.newLineRegex); -+ } - -- // Visa Electron -- re = new RegExp('^(4026|417500|4508|4844|491(3|7))'); -- if (cardNum.match(re) != null) { -- return 'Visa'; -- } -+ // ref https://stackoverflow.com/a/5911300 -+ protected getCardBrand(cardNum: string) { -+ if (this.isNullOrWhitespace(cardNum)) { -+ return null; -+ } - -- return null; -+ // Visa -+ let re = new RegExp("^4"); -+ if (cardNum.match(re) != null) { -+ return "Visa"; - } - -- protected setCardExpiration(cipher: CipherView, expiration: string): boolean { -- if (!this.isNullOrWhitespace(expiration)) { -- expiration = expiration.replace(/\s/g, ''); -- const parts = expiration.split('/'); -- if (parts.length === 2) { -- let month: string = null; -- let year: string = null; -- if (parts[0].length === 1 || parts[0].length === 2) { -- month = parts[0]; -- if (month.length === 2 && month[0] === '0') { -- month = month.substr(1, 1); -- } -- } -- if (parts[1].length === 2 || parts[1].length === 4) { -- year = month.length === 2 ? '20' + parts[1] : parts[1]; -- } -- if (month != null && year != null) { -- cipher.card.expMonth = month; -- cipher.card.expYear = year; -- return true; -- } -- } -- } -- return false; -+ // Mastercard -+ // Updated for Mastercard 2017 BINs expansion -+ if ( -+ /^(5[1-5][0-9]{14}|2(22[1-9][0-9]{12}|2[3-9][0-9]{13}|[3-6][0-9]{14}|7[0-1][0-9]{13}|720[0-9]{12}))$/.test( -+ cardNum -+ ) -+ ) { -+ return "Mastercard"; - } - -- protected moveFoldersToCollections(result: ImportResult) { -- result.folderRelationships.forEach(r => result.collectionRelationships.push(r)); -- result.collections = result.folders.map(f => { -- const collection = new CollectionView(); -- collection.name = f.name; -- return collection; -- }); -- result.folderRelationships = []; -- result.folders = []; -+ // AMEX -+ re = new RegExp("^3[47]"); -+ if (cardNum.match(re) != null) { -+ return "Amex"; - } - -- protected querySelectorDirectChild(parentEl: Element, query: string) { -- const els = this.querySelectorAllDirectChild(parentEl, query); -- return els.length === 0 ? null : els[0]; -+ // Discover -+ re = new RegExp( -+ "^(6011|622(12[6-9]|1[3-9][0-9]|[2-8][0-9]{2}|9[0-1][0-9]|92[0-5]|64[4-9])|65)" -+ ); -+ if (cardNum.match(re) != null) { -+ return "Discover"; - } - -- protected querySelectorAllDirectChild(parentEl: Element, query: string) { -- return Array.from(parentEl.querySelectorAll(query)).filter(el => el.parentNode === parentEl); -+ // Diners -+ re = new RegExp("^36"); -+ if (cardNum.match(re) != null) { -+ return "Diners Club"; - } - -- protected initLoginCipher() { -- const cipher = new CipherView(); -- cipher.favorite = false; -- cipher.notes = ''; -- cipher.fields = []; -- cipher.login = new LoginView(); -- cipher.type = CipherType.Login; -- return cipher; -+ // Diners - Carte Blanche -+ re = new RegExp("^30[0-5]"); -+ if (cardNum.match(re) != null) { -+ return "Diners Club"; - } - -- protected cleanupCipher(cipher: CipherView) { -- if (cipher == null) { -- return; -- } -- if (cipher.type !== CipherType.Login) { -- cipher.login = null; -- } -- if (this.isNullOrWhitespace(cipher.name)) { -- cipher.name = '--'; -- } -- if (this.isNullOrWhitespace(cipher.notes)) { -- cipher.notes = null; -- } else { -- cipher.notes = cipher.notes.trim(); -- } -- if (cipher.fields != null && cipher.fields.length === 0) { -- cipher.fields = null; -- } -+ // JCB -+ re = new RegExp("^35(2[89]|[3-8][0-9])"); -+ if (cardNum.match(re) != null) { -+ return "JCB"; - } - -- protected processKvp(cipher: CipherView, key: string, value: string, type: FieldType = FieldType.Text) { -- if (this.isNullOrWhitespace(value)) { -- return; -- } -- if (this.isNullOrWhitespace(key)) { -- key = ''; -- } -- if (value.length > 200 || value.trim().search(this.newLineRegex) > -1) { -- if (cipher.notes == null) { -- cipher.notes = ''; -- } -- cipher.notes += (key + ': ' + this.splitNewLine(value).join('\n') + '\n'); -- } else { -- if (cipher.fields == null) { -- cipher.fields = []; -- } -- const field = new FieldView(); -- field.type = type; -- field.name = key; -- field.value = value; -- cipher.fields.push(field); -- } -+ // Visa Electron -+ re = new RegExp("^(4026|417500|4508|4844|491(3|7))"); -+ if (cardNum.match(re) != null) { -+ return "Visa"; - } - -- protected processFolder(result: ImportResult, folderName: string) { -- let folderIndex = result.folders.length; -- const hasFolder = !this.isNullOrWhitespace(folderName); -- let addFolder = hasFolder; -+ return null; -+ } -+ -+ protected setCardExpiration(cipher: CipherView, expiration: string): boolean { -+ if (!this.isNullOrWhitespace(expiration)) { -+ expiration = expiration.replace(/\s/g, ""); -+ const parts = expiration.split("/"); -+ if (parts.length === 2) { -+ let month: string = null; -+ let year: string = null; -+ if (parts[0].length === 1 || parts[0].length === 2) { -+ month = parts[0]; -+ if (month.length === 2 && month[0] === "0") { -+ month = month.substr(1, 1); -+ } -+ } -+ if (parts[1].length === 2 || parts[1].length === 4) { -+ year = month.length === 2 ? "20" + parts[1] : parts[1]; -+ } -+ if (month != null && year != null) { -+ cipher.card.expMonth = month; -+ cipher.card.expYear = year; -+ return true; -+ } -+ } -+ } -+ return false; -+ } -+ -+ protected moveFoldersToCollections(result: ImportResult) { -+ result.folderRelationships.forEach((r) => result.collectionRelationships.push(r)); -+ result.collections = result.folders.map((f) => { -+ const collection = new CollectionView(); -+ collection.name = f.name; -+ return collection; -+ }); -+ result.folderRelationships = []; -+ result.folders = []; -+ } -+ -+ protected querySelectorDirectChild(parentEl: Element, query: string) { -+ const els = this.querySelectorAllDirectChild(parentEl, query); -+ return els.length === 0 ? null : els[0]; -+ } -+ -+ protected querySelectorAllDirectChild(parentEl: Element, query: string) { -+ return Array.from(parentEl.querySelectorAll(query)).filter((el) => el.parentNode === parentEl); -+ } -+ -+ protected initLoginCipher() { -+ const cipher = new CipherView(); -+ cipher.favorite = false; -+ cipher.notes = ""; -+ cipher.fields = []; -+ cipher.login = new LoginView(); -+ cipher.type = CipherType.Login; -+ return cipher; -+ } -+ -+ protected cleanupCipher(cipher: CipherView) { -+ if (cipher == null) { -+ return; -+ } -+ if (cipher.type !== CipherType.Login) { -+ cipher.login = null; -+ } -+ if (this.isNullOrWhitespace(cipher.name)) { -+ cipher.name = "--"; -+ } -+ if (this.isNullOrWhitespace(cipher.notes)) { -+ cipher.notes = null; -+ } else { -+ cipher.notes = cipher.notes.trim(); -+ } -+ if (cipher.fields != null && cipher.fields.length === 0) { -+ cipher.fields = null; -+ } -+ } -+ -+ protected processKvp( -+ cipher: CipherView, -+ key: string, -+ value: string, -+ type: FieldType = FieldType.Text -+ ) { -+ if (this.isNullOrWhitespace(value)) { -+ return; -+ } -+ if (this.isNullOrWhitespace(key)) { -+ key = ""; -+ } -+ if (value.length > 200 || value.trim().search(this.newLineRegex) > -1) { -+ if (cipher.notes == null) { -+ cipher.notes = ""; -+ } -+ cipher.notes += key + ": " + this.splitNewLine(value).join("\n") + "\n"; -+ } else { -+ if (cipher.fields == null) { -+ cipher.fields = []; -+ } -+ const field = new FieldView(); -+ field.type = type; -+ field.name = key; -+ field.value = value; -+ cipher.fields.push(field); -+ } -+ } - -- if (hasFolder) { -- for (let i = 0; i < result.folders.length; i++) { -- if (result.folders[i].name === folderName) { -- addFolder = false; -- folderIndex = i; -- break; -- } -- } -- } -+ protected processFolder(result: ImportResult, folderName: string) { -+ let folderIndex = result.folders.length; -+ const hasFolder = !this.isNullOrWhitespace(folderName); -+ let addFolder = hasFolder; - -- if (addFolder) { -- const f = new FolderView(); -- f.name = folderName; -- result.folders.push(f); -- } -- if (hasFolder) { -- result.folderRelationships.push([result.ciphers.length, folderIndex]); -+ if (hasFolder) { -+ for (let i = 0; i < result.folders.length; i++) { -+ if (result.folders[i].name === folderName) { -+ addFolder = false; -+ folderIndex = i; -+ break; - } -+ } - } - -- protected convertToNoteIfNeeded(cipher: CipherView) { -- if (cipher.type === CipherType.Login && this.isNullOrWhitespace(cipher.login.username) && -- this.isNullOrWhitespace(cipher.login.password) && -- (cipher.login.uris == null || cipher.login.uris.length === 0)) { -- cipher.type = CipherType.SecureNote; -- cipher.secureNote = new SecureNoteView(); -- cipher.secureNote.type = SecureNoteType.Generic; -- } -+ if (addFolder) { -+ const f = new FolderView(); -+ f.name = folderName; -+ result.folders.push(f); -+ } -+ if (hasFolder) { -+ result.folderRelationships.push([result.ciphers.length, folderIndex]); -+ } -+ } -+ -+ protected convertToNoteIfNeeded(cipher: CipherView) { -+ if ( -+ cipher.type === CipherType.Login && -+ this.isNullOrWhitespace(cipher.login.username) && -+ this.isNullOrWhitespace(cipher.login.password) && -+ (cipher.login.uris == null || cipher.login.uris.length === 0) -+ ) { -+ cipher.type = CipherType.SecureNote; -+ cipher.secureNote = new SecureNoteView(); -+ cipher.secureNote.type = SecureNoteType.Generic; - } -+ } - } -diff --git a/jslib/common/src/importers/bitwardenCsvImporter.ts b/jslib/common/src/importers/bitwardenCsvImporter.ts -index f2ffeae5..b7ec11c3 100644 ---- a/jslib/common/src/importers/bitwardenCsvImporter.ts -+++ b/jslib/common/src/importers/bitwardenCsvImporter.ts -@@ -1,118 +1,122 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - --import { CipherView } from '../models/view/cipherView'; --import { CollectionView } from '../models/view/collectionView'; --import { FieldView } from '../models/view/fieldView'; --import { FolderView } from '../models/view/folderView'; --import { LoginView } from '../models/view/loginView'; --import { SecureNoteView } from '../models/view/secureNoteView'; -+import { CipherView } from "../models/view/cipherView"; -+import { CollectionView } from "../models/view/collectionView"; -+import { FieldView } from "../models/view/fieldView"; -+import { FolderView } from "../models/view/folderView"; -+import { LoginView } from "../models/view/loginView"; -+import { SecureNoteView } from "../models/view/secureNoteView"; - --import { CipherRepromptType } from '../enums/cipherRepromptType'; --import { CipherType } from '../enums/cipherType'; --import { FieldType } from '../enums/fieldType'; --import { SecureNoteType } from '../enums/secureNoteType'; -+import { CipherRepromptType } from "../enums/cipherRepromptType"; -+import { CipherType } from "../enums/cipherType"; -+import { FieldType } from "../enums/fieldType"; -+import { SecureNoteType } from "../enums/secureNoteType"; - - export class BitwardenCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -- -- results.forEach(value => { -- if (this.organization && !this.isNullOrWhitespace(value.collections)) { -- const collections = (value.collections as string).split(','); -- collections.forEach(col => { -- let addCollection = true; -- let collectionIndex = result.collections.length; -- -- for (let i = 0; i < result.collections.length; i++) { -- if (result.collections[i].name === col) { -- addCollection = false; -- collectionIndex = i; -- break; -- } -- } -- -- if (addCollection) { -- const collection = new CollectionView(); -- collection.name = col; -- result.collections.push(collection); -- } -- -- result.collectionRelationships.push([result.ciphers.length, collectionIndex]); -- }); -- } else if (!this.organization) { -- this.processFolder(result, value.folder); -- } -- -- const cipher = new CipherView(); -- cipher.favorite = !this.organization && this.getValueOrDefault(value.favorite, '0') !== '0' ? true : false; -- cipher.type = CipherType.Login; -- cipher.notes = this.getValueOrDefault(value.notes); -- cipher.name = this.getValueOrDefault(value.name, '--'); -- try { -- cipher.reprompt = parseInt(this.getValueOrDefault(value.reprompt, CipherRepromptType.None.toString()), 10); -- } catch (e) { -- // tslint:disable-next-line -- console.error('Unable to parse reprompt value', e); -- cipher.reprompt = CipherRepromptType.None; -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- if (!this.isNullOrWhitespace(value.fields)) { -- const fields = this.splitNewLine(value.fields); -- for (let i = 0; i < fields.length; i++) { -- if (this.isNullOrWhitespace(fields[i])) { -- continue; -- } -- -- const delimPosition = fields[i].lastIndexOf(': '); -- if (delimPosition === -1) { -- continue; -- } -- -- if (cipher.fields == null) { -- cipher.fields = []; -- } -- -- const field = new FieldView(); -- field.name = fields[i].substr(0, delimPosition); -- field.value = null; -- field.type = FieldType.Text; -- if (fields[i].length > (delimPosition + 2)) { -- field.value = fields[i].substr(delimPosition + 2); -- } -- cipher.fields.push(field); -- } -+ results.forEach((value) => { -+ if (this.organization && !this.isNullOrWhitespace(value.collections)) { -+ const collections = (value.collections as string).split(","); -+ collections.forEach((col) => { -+ let addCollection = true; -+ let collectionIndex = result.collections.length; -+ -+ for (let i = 0; i < result.collections.length; i++) { -+ if (result.collections[i].name === col) { -+ addCollection = false; -+ collectionIndex = i; -+ break; - } -+ } - -- const valueType = value.type != null ? value.type.toLowerCase() : null; -- switch (valueType) { -- case 'note': -- cipher.type = CipherType.SecureNote; -- cipher.secureNote = new SecureNoteView(); -- cipher.secureNote.type = SecureNoteType.Generic; -- break; -- default: -- cipher.type = CipherType.Login; -- cipher.login = new LoginView(); -- cipher.login.totp = this.getValueOrDefault(value.login_totp || value.totp); -- cipher.login.username = this.getValueOrDefault(value.login_username || value.username); -- cipher.login.password = this.getValueOrDefault(value.login_password || value.password); -- const uris = this.parseSingleRowCsv(value.login_uri || value.uri); -- cipher.login.uris = this.makeUriArray(uris); -- break; -- } -+ if (addCollection) { -+ const collection = new CollectionView(); -+ collection.name = col; -+ result.collections.push(collection); -+ } - -- result.ciphers.push(cipher); -+ result.collectionRelationships.push([result.ciphers.length, collectionIndex]); - }); -- -- result.success = true; -- return Promise.resolve(result); -- } -+ } else if (!this.organization) { -+ this.processFolder(result, value.folder); -+ } -+ -+ const cipher = new CipherView(); -+ cipher.favorite = -+ !this.organization && this.getValueOrDefault(value.favorite, "0") !== "0" ? true : false; -+ cipher.type = CipherType.Login; -+ cipher.notes = this.getValueOrDefault(value.notes); -+ cipher.name = this.getValueOrDefault(value.name, "--"); -+ try { -+ cipher.reprompt = parseInt( -+ this.getValueOrDefault(value.reprompt, CipherRepromptType.None.toString()), -+ 10 -+ ); -+ } catch (e) { -+ // tslint:disable-next-line -+ console.error("Unable to parse reprompt value", e); -+ cipher.reprompt = CipherRepromptType.None; -+ } -+ -+ if (!this.isNullOrWhitespace(value.fields)) { -+ const fields = this.splitNewLine(value.fields); -+ for (let i = 0; i < fields.length; i++) { -+ if (this.isNullOrWhitespace(fields[i])) { -+ continue; -+ } -+ -+ const delimPosition = fields[i].lastIndexOf(": "); -+ if (delimPosition === -1) { -+ continue; -+ } -+ -+ if (cipher.fields == null) { -+ cipher.fields = []; -+ } -+ -+ const field = new FieldView(); -+ field.name = fields[i].substr(0, delimPosition); -+ field.value = null; -+ field.type = FieldType.Text; -+ if (fields[i].length > delimPosition + 2) { -+ field.value = fields[i].substr(delimPosition + 2); -+ } -+ cipher.fields.push(field); -+ } -+ } -+ -+ const valueType = value.type != null ? value.type.toLowerCase() : null; -+ switch (valueType) { -+ case "note": -+ cipher.type = CipherType.SecureNote; -+ cipher.secureNote = new SecureNoteView(); -+ cipher.secureNote.type = SecureNoteType.Generic; -+ break; -+ default: -+ cipher.type = CipherType.Login; -+ cipher.login = new LoginView(); -+ cipher.login.totp = this.getValueOrDefault(value.login_totp || value.totp); -+ cipher.login.username = this.getValueOrDefault(value.login_username || value.username); -+ cipher.login.password = this.getValueOrDefault(value.login_password || value.password); -+ const uris = this.parseSingleRowCsv(value.login_uri || value.uri); -+ cipher.login.uris = this.makeUriArray(uris); -+ break; -+ } -+ -+ result.ciphers.push(cipher); -+ }); -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/bitwardenJsonImporter.ts b/jslib/common/src/importers/bitwardenJsonImporter.ts -index 195708c0..bef77860 100644 ---- a/jslib/common/src/importers/bitwardenJsonImporter.ts -+++ b/jslib/common/src/importers/bitwardenJsonImporter.ts -@@ -1,159 +1,174 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { EncString } from '../models/domain/encString'; --import { ImportResult } from '../models/domain/importResult'; -+import { EncString } from "../models/domain/encString"; -+import { ImportResult } from "../models/domain/importResult"; - --import { CipherWithIds } from '../models/export/cipherWithIds'; --import { CollectionWithId } from '../models/export/collectionWithId'; --import { FolderWithId } from '../models/export/folderWithId'; -+import { CipherWithIds } from "../models/export/cipherWithIds"; -+import { CollectionWithId } from "../models/export/collectionWithId"; -+import { FolderWithId } from "../models/export/folderWithId"; - --import { CryptoService } from '../abstractions/crypto.service'; --import { I18nService } from '../abstractions/i18n.service'; -+import { CryptoService } from "../abstractions/crypto.service"; -+import { I18nService } from "../abstractions/i18n.service"; - - export class BitwardenJsonImporter extends BaseImporter implements Importer { -- private results: any; -- private result: ImportResult; -- -- constructor(private cryptoService: CryptoService, private i18nService: I18nService) { -- super(); -+ private results: any; -+ private result: ImportResult; -+ -+ constructor(private cryptoService: CryptoService, private i18nService: I18nService) { -+ super(); -+ } -+ -+ async parse(data: string): Promise { -+ this.result = new ImportResult(); -+ this.results = JSON.parse(data); -+ if (this.results == null || this.results.items == null || this.results.items.length === 0) { -+ this.result.success = false; -+ return this.result; - } - -- async parse(data: string): Promise { -- this.result = new ImportResult(); -- this.results = JSON.parse(data); -- if (this.results == null || this.results.items == null || this.results.items.length === 0) { -- this.result.success = false; -- return this.result; -- } -- -- if (this.results.encrypted) { -- await this.parseEncrypted(); -- } else { -- this.parseDecrypted(); -- } -+ if (this.results.encrypted) { -+ await this.parseEncrypted(); -+ } else { -+ this.parseDecrypted(); -+ } - -- return this.result; -+ return this.result; -+ } -+ -+ private async parseEncrypted() { -+ if (this.results.encKeyValidation_DO_NOT_EDIT != null) { -+ const orgKey = await this.cryptoService.getOrgKey(this.organizationId); -+ const encKeyValidation = new EncString(this.results.encKeyValidation_DO_NOT_EDIT); -+ const encKeyValidationDecrypt = await this.cryptoService.decryptToUtf8( -+ encKeyValidation, -+ orgKey -+ ); -+ if (encKeyValidationDecrypt === null) { -+ this.result.success = false; -+ this.result.errorMessage = this.i18nService.t("importEncKeyError"); -+ return; -+ } - } - -- private async parseEncrypted() { -- if (this.results.encKeyValidation_DO_NOT_EDIT != null) { -- const orgKey = await this.cryptoService.getOrgKey(this.organizationId); -- const encKeyValidation = new EncString(this.results.encKeyValidation_DO_NOT_EDIT); -- const encKeyValidationDecrypt = await this.cryptoService.decryptToUtf8(encKeyValidation, orgKey); -- if (encKeyValidationDecrypt === null) { -- this.result.success = false; -- this.result.errorMessage = this.i18nService.t('importEncKeyError'); -- return; -- } -+ const groupingsMap = new Map(); -+ -+ if (this.organization && this.results.collections != null) { -+ for (const c of this.results.collections as CollectionWithId[]) { -+ const collection = CollectionWithId.toDomain(c); -+ if (collection != null) { -+ collection.id = null; -+ collection.organizationId = this.organizationId; -+ const view = await collection.decrypt(); -+ groupingsMap.set(c.id, this.result.collections.length); -+ this.result.collections.push(view); - } -- -- const groupingsMap = new Map(); -- -- if (this.organization && this.results.collections != null) { -- for (const c of this.results.collections as CollectionWithId[]) { -- const collection = CollectionWithId.toDomain(c); -- if (collection != null) { -- collection.id = null; -- collection.organizationId = this.organizationId; -- const view = await collection.decrypt(); -- groupingsMap.set(c.id, this.result.collections.length); -- this.result.collections.push(view); -- } -- } -- } else if (!this.organization && this.results.folders != null) { -- for (const f of this.results.folders as FolderWithId[]) { -- const folder = FolderWithId.toDomain(f); -- if (folder != null) { -- folder.id = null; -- const view = await folder.decrypt(); -- groupingsMap.set(f.id, this.result.folders.length); -- this.result.folders.push(view); -- } -- } -+ } -+ } else if (!this.organization && this.results.folders != null) { -+ for (const f of this.results.folders as FolderWithId[]) { -+ const folder = FolderWithId.toDomain(f); -+ if (folder != null) { -+ folder.id = null; -+ const view = await folder.decrypt(); -+ groupingsMap.set(f.id, this.result.folders.length); -+ this.result.folders.push(view); - } -+ } -+ } - -- for (const c of this.results.items as CipherWithIds[]) { -- const cipher = CipherWithIds.toDomain(c); -- // reset ids incase they were set for some reason -- cipher.id = null; -- cipher.folderId = null; -- cipher.organizationId = this.organizationId; -- cipher.collectionIds = null; -- -- // make sure password history is limited -- if (cipher.passwordHistory != null && cipher.passwordHistory.length > 5) { -- cipher.passwordHistory = cipher.passwordHistory.slice(0, 5); -- } -- -- if (!this.organization && c.folderId != null && groupingsMap.has(c.folderId)) { -- this.result.folderRelationships.push([this.result.ciphers.length, groupingsMap.get(c.folderId)]); -- } else if (this.organization && c.collectionIds != null) { -- c.collectionIds.forEach(cId => { -- if (groupingsMap.has(cId)) { -- this.result.collectionRelationships.push([this.result.ciphers.length, groupingsMap.get(cId)]); -- } -- }); -- } -- -- const view = await cipher.decrypt(); -- this.cleanupCipher(view); -- this.result.ciphers.push(view); -- } -+ for (const c of this.results.items as CipherWithIds[]) { -+ const cipher = CipherWithIds.toDomain(c); -+ // reset ids incase they were set for some reason -+ cipher.id = null; -+ cipher.folderId = null; -+ cipher.organizationId = this.organizationId; -+ cipher.collectionIds = null; -+ -+ // make sure password history is limited -+ if (cipher.passwordHistory != null && cipher.passwordHistory.length > 5) { -+ cipher.passwordHistory = cipher.passwordHistory.slice(0, 5); -+ } -+ -+ if (!this.organization && c.folderId != null && groupingsMap.has(c.folderId)) { -+ this.result.folderRelationships.push([ -+ this.result.ciphers.length, -+ groupingsMap.get(c.folderId), -+ ]); -+ } else if (this.organization && c.collectionIds != null) { -+ c.collectionIds.forEach((cId) => { -+ if (groupingsMap.has(cId)) { -+ this.result.collectionRelationships.push([ -+ this.result.ciphers.length, -+ groupingsMap.get(cId), -+ ]); -+ } -+ }); -+ } - -- this.result.success = true; -+ const view = await cipher.decrypt(); -+ this.cleanupCipher(view); -+ this.result.ciphers.push(view); - } - -- private parseDecrypted() { -- const groupingsMap = new Map(); -- if (this.organization && this.results.collections != null) { -- this.results.collections.forEach((c: CollectionWithId) => { -- const collection = CollectionWithId.toView(c); -- if (collection != null) { -- collection.id = null; -- collection.organizationId = null; -- groupingsMap.set(c.id, this.result.collections.length); -- this.result.collections.push(collection); -- } -- }); -- } else if (!this.organization && this.results.folders != null) { -- this.results.folders.forEach((f: FolderWithId) => { -- const folder = FolderWithId.toView(f); -- if (folder != null) { -- folder.id = null; -- groupingsMap.set(f.id, this.result.folders.length); -- this.result.folders.push(folder); -- } -- }); -+ this.result.success = true; -+ } -+ -+ private parseDecrypted() { -+ const groupingsMap = new Map(); -+ if (this.organization && this.results.collections != null) { -+ this.results.collections.forEach((c: CollectionWithId) => { -+ const collection = CollectionWithId.toView(c); -+ if (collection != null) { -+ collection.id = null; -+ collection.organizationId = null; -+ groupingsMap.set(c.id, this.result.collections.length); -+ this.result.collections.push(collection); -+ } -+ }); -+ } else if (!this.organization && this.results.folders != null) { -+ this.results.folders.forEach((f: FolderWithId) => { -+ const folder = FolderWithId.toView(f); -+ if (folder != null) { -+ folder.id = null; -+ groupingsMap.set(f.id, this.result.folders.length); -+ this.result.folders.push(folder); - } -+ }); -+ } - -- this.results.items.forEach((c: CipherWithIds) => { -- const cipher = CipherWithIds.toView(c); -- // reset ids incase they were set for some reason -- cipher.id = null; -- cipher.folderId = null; -- cipher.organizationId = null; -- cipher.collectionIds = null; -- -- // make sure password history is limited -- if (cipher.passwordHistory != null && cipher.passwordHistory.length > 5) { -- cipher.passwordHistory = cipher.passwordHistory.slice(0, 5); -- } -- -- if (!this.organization && c.folderId != null && groupingsMap.has(c.folderId)) { -- this.result.folderRelationships.push([this.result.ciphers.length, groupingsMap.get(c.folderId)]); -- } else if (this.organization && c.collectionIds != null) { -- c.collectionIds.forEach(cId => { -- if (groupingsMap.has(cId)) { -- this.result.collectionRelationships.push([this.result.ciphers.length, groupingsMap.get(cId)]); -- } -- }); -- } -- -- this.cleanupCipher(cipher); -- this.result.ciphers.push(cipher); -+ this.results.items.forEach((c: CipherWithIds) => { -+ const cipher = CipherWithIds.toView(c); -+ // reset ids incase they were set for some reason -+ cipher.id = null; -+ cipher.folderId = null; -+ cipher.organizationId = null; -+ cipher.collectionIds = null; -+ -+ // make sure password history is limited -+ if (cipher.passwordHistory != null && cipher.passwordHistory.length > 5) { -+ cipher.passwordHistory = cipher.passwordHistory.slice(0, 5); -+ } -+ -+ if (!this.organization && c.folderId != null && groupingsMap.has(c.folderId)) { -+ this.result.folderRelationships.push([ -+ this.result.ciphers.length, -+ groupingsMap.get(c.folderId), -+ ]); -+ } else if (this.organization && c.collectionIds != null) { -+ c.collectionIds.forEach((cId) => { -+ if (groupingsMap.has(cId)) { -+ this.result.collectionRelationships.push([ -+ this.result.ciphers.length, -+ groupingsMap.get(cId), -+ ]); -+ } - }); -+ } - -- this.result.success = true; -- } -+ this.cleanupCipher(cipher); -+ this.result.ciphers.push(cipher); -+ }); -+ -+ this.result.success = true; -+ } - } -diff --git a/jslib/common/src/importers/blackBerryCsvImporter.ts b/jslib/common/src/importers/blackBerryCsvImporter.ts -index 1e111348..f54593b0 100644 ---- a/jslib/common/src/importers/blackBerryCsvImporter.ts -+++ b/jslib/common/src/importers/blackBerryCsvImporter.ts -@@ -1,36 +1,36 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class BlackBerryCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- results.forEach(value => { -- if (value.grouping === 'list') { -- return; -- } -- const cipher = this.initLoginCipher(); -- cipher.favorite = value.fav === '1'; -- cipher.name = this.getValueOrDefault(value.name); -- cipher.notes = this.getValueOrDefault(value.extra); -- if (value.grouping !== 'note') { -- cipher.login.uris = this.makeUriArray(value.url); -- cipher.login.password = this.getValueOrDefault(value.password); -- cipher.login.username = this.getValueOrDefault(value.username); -- } -- this.convertToNoteIfNeeded(cipher); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ results.forEach((value) => { -+ if (value.grouping === "list") { -+ return; -+ } -+ const cipher = this.initLoginCipher(); -+ cipher.favorite = value.fav === "1"; -+ cipher.name = this.getValueOrDefault(value.name); -+ cipher.notes = this.getValueOrDefault(value.extra); -+ if (value.grouping !== "note") { -+ cipher.login.uris = this.makeUriArray(value.url); -+ cipher.login.password = this.getValueOrDefault(value.password); -+ cipher.login.username = this.getValueOrDefault(value.username); -+ } -+ this.convertToNoteIfNeeded(cipher); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -- } -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/blurCsvImporter.ts b/jslib/common/src/importers/blurCsvImporter.ts -index 3f36e166..449ccb32 100644 ---- a/jslib/common/src/importers/blurCsvImporter.ts -+++ b/jslib/common/src/importers/blurCsvImporter.ts -@@ -1,39 +1,41 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class BlurCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- results.forEach(value => { -- if (value.label === 'null') { -- value.label = null; -- } -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value.label, -- this.getValueOrDefault(this.nameFromUrl(value.domain), '--')); -- cipher.login.uris = this.makeUriArray(value.domain); -- cipher.login.password = this.getValueOrDefault(value.password); -+ results.forEach((value) => { -+ if (value.label === "null") { -+ value.label = null; -+ } -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault( -+ value.label, -+ this.getValueOrDefault(this.nameFromUrl(value.domain), "--") -+ ); -+ cipher.login.uris = this.makeUriArray(value.domain); -+ cipher.login.password = this.getValueOrDefault(value.password); - -- if (this.isNullOrWhitespace(value.email) && !this.isNullOrWhitespace(value.username)) { -- cipher.login.username = value.username; -- } else { -- cipher.login.username = this.getValueOrDefault(value.email); -- cipher.notes = this.getValueOrDefault(value.username); -- } -+ if (this.isNullOrWhitespace(value.email) && !this.isNullOrWhitespace(value.username)) { -+ cipher.login.username = value.username; -+ } else { -+ cipher.login.username = this.getValueOrDefault(value.email); -+ cipher.notes = this.getValueOrDefault(value.username); -+ } - -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -- } -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/buttercupCsvImporter.ts b/jslib/common/src/importers/buttercupCsvImporter.ts -index bd9e5553..4961adbf 100644 ---- a/jslib/common/src/importers/buttercupCsvImporter.ts -+++ b/jslib/common/src/importers/buttercupCsvImporter.ts -@@ -1,51 +1,49 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - --const OfficialProps = [ -- '!group_id', '!group_name', 'title', 'username', 'password', 'URL', 'id', --]; -+const OfficialProps = ["!group_id", "!group_name", "title", "username", "password", "URL", "id"]; - - export class ButtercupCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- results.forEach(value => { -- this.processFolder(result, this.getValueOrDefault(value['!group_name'])); -- -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value.title, '--'); -- cipher.login.username = this.getValueOrDefault(value.username); -- cipher.login.password = this.getValueOrDefault(value.password); -- cipher.login.uris = this.makeUriArray(value.URL); -- -- let processingCustomFields = false; -- for (const prop in value) { -- if (value.hasOwnProperty(prop)) { -- if (!processingCustomFields && OfficialProps.indexOf(prop) === -1) { -- processingCustomFields = true; -- } -- if (processingCustomFields) { -- this.processKvp(cipher, prop, value[prop]); -- } -- } -- } -- -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -- -- if (this.organization) { -- this.moveFoldersToCollections(result); -+ results.forEach((value) => { -+ this.processFolder(result, this.getValueOrDefault(value["!group_name"])); -+ -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(value.title, "--"); -+ cipher.login.username = this.getValueOrDefault(value.username); -+ cipher.login.password = this.getValueOrDefault(value.password); -+ cipher.login.uris = this.makeUriArray(value.URL); -+ -+ let processingCustomFields = false; -+ for (const prop in value) { -+ if (value.hasOwnProperty(prop)) { -+ if (!processingCustomFields && OfficialProps.indexOf(prop) === -1) { -+ processingCustomFields = true; -+ } -+ if (processingCustomFields) { -+ this.processKvp(cipher, prop, value[prop]); -+ } - } -+ } - -- result.success = true; -- return Promise.resolve(result); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); -+ -+ if (this.organization) { -+ this.moveFoldersToCollections(result); - } -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/chromeCsvImporter.ts b/jslib/common/src/importers/chromeCsvImporter.ts -index 3580ec5c..41fbc251 100644 ---- a/jslib/common/src/importers/chromeCsvImporter.ts -+++ b/jslib/common/src/importers/chromeCsvImporter.ts -@@ -1,28 +1,28 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class ChromeCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- results.forEach(value => { -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value.name, '--'); -- cipher.login.username = this.getValueOrDefault(value.username); -- cipher.login.password = this.getValueOrDefault(value.password); -- cipher.login.uris = this.makeUriArray(value.url); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ results.forEach((value) => { -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(value.name, "--"); -+ cipher.login.username = this.getValueOrDefault(value.username); -+ cipher.login.password = this.getValueOrDefault(value.password); -+ cipher.login.uris = this.makeUriArray(value.url); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -- } -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/clipperzHtmlImporter.ts b/jslib/common/src/importers/clipperzHtmlImporter.ts -index a772ae00..8646b267 100644 ---- a/jslib/common/src/importers/clipperzHtmlImporter.ts -+++ b/jslib/common/src/importers/clipperzHtmlImporter.ts -@@ -1,79 +1,86 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class ClipperzHtmlImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const doc = this.parseXml(data); -- if (doc == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const doc = this.parseXml(data); -+ if (doc == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- const textarea = doc.querySelector('textarea'); -- if (textarea == null || this.isNullOrWhitespace(textarea.textContent)) { -- result.errorMessage = 'Missing textarea.'; -- result.success = false; -- return Promise.resolve(result); -- } -+ const textarea = doc.querySelector("textarea"); -+ if (textarea == null || this.isNullOrWhitespace(textarea.textContent)) { -+ result.errorMessage = "Missing textarea."; -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- const entries = JSON.parse(textarea.textContent); -- entries.forEach((entry: any) => { -- const cipher = this.initLoginCipher(); -- if (!this.isNullOrWhitespace(entry.label)) { -- cipher.name = entry.label.split(' ')[0]; -- } -- if (entry.data != null && !this.isNullOrWhitespace(entry.data.notes)) { -- cipher.notes = entry.data.notes.split('\\n').join('\n'); -- } -+ const entries = JSON.parse(textarea.textContent); -+ entries.forEach((entry: any) => { -+ const cipher = this.initLoginCipher(); -+ if (!this.isNullOrWhitespace(entry.label)) { -+ cipher.name = entry.label.split(" ")[0]; -+ } -+ if (entry.data != null && !this.isNullOrWhitespace(entry.data.notes)) { -+ cipher.notes = entry.data.notes.split("\\n").join("\n"); -+ } - -- if (entry.currentVersion != null && entry.currentVersion.fields != null) { -- for (const property in entry.currentVersion.fields) { -- if (!entry.currentVersion.fields.hasOwnProperty(property)) { -- continue; -- } -+ if (entry.currentVersion != null && entry.currentVersion.fields != null) { -+ for (const property in entry.currentVersion.fields) { -+ if (!entry.currentVersion.fields.hasOwnProperty(property)) { -+ continue; -+ } - -- const field = entry.currentVersion.fields[property]; -- const actionType = field.actionType != null ? field.actionType.toLowerCase() : null; -- switch (actionType) { -- case 'password': -- cipher.login.password = this.getValueOrDefault(field.value); -- break; -- case 'email': -- case 'username': -- case 'user': -- case 'name': -- cipher.login.username = this.getValueOrDefault(field.value); -- break; -- case 'url': -- cipher.login.uris = this.makeUriArray(field.value); -- break; -- default: -- const labelLower = field.label != null ? field.label.toLowerCase() : null; -- if (cipher.login.password == null && this.passwordFieldNames.indexOf(labelLower) > -1) { -- cipher.login.password = this.getValueOrDefault(field.value); -- } else if (cipher.login.username == null && -- this.usernameFieldNames.indexOf(labelLower) > -1) { -- cipher.login.username = this.getValueOrDefault(field.value); -- } else if ((cipher.login.uris == null || cipher.login.uris.length === 0) && -- this.uriFieldNames.indexOf(labelLower) > -1) { -- cipher.login.uris = this.makeUriArray(field.value); -- } else { -- this.processKvp(cipher, field.label, field.value); -- } -- break; -- } -- } -- } -+ const field = entry.currentVersion.fields[property]; -+ const actionType = field.actionType != null ? field.actionType.toLowerCase() : null; -+ switch (actionType) { -+ case "password": -+ cipher.login.password = this.getValueOrDefault(field.value); -+ break; -+ case "email": -+ case "username": -+ case "user": -+ case "name": -+ cipher.login.username = this.getValueOrDefault(field.value); -+ break; -+ case "url": -+ cipher.login.uris = this.makeUriArray(field.value); -+ break; -+ default: -+ const labelLower = field.label != null ? field.label.toLowerCase() : null; -+ if ( -+ cipher.login.password == null && -+ this.passwordFieldNames.indexOf(labelLower) > -1 -+ ) { -+ cipher.login.password = this.getValueOrDefault(field.value); -+ } else if ( -+ cipher.login.username == null && -+ this.usernameFieldNames.indexOf(labelLower) > -1 -+ ) { -+ cipher.login.username = this.getValueOrDefault(field.value); -+ } else if ( -+ (cipher.login.uris == null || cipher.login.uris.length === 0) && -+ this.uriFieldNames.indexOf(labelLower) > -1 -+ ) { -+ cipher.login.uris = this.makeUriArray(field.value); -+ } else { -+ this.processKvp(cipher, field.label, field.value); -+ } -+ break; -+ } -+ } -+ } - -- this.convertToNoteIfNeeded(cipher); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ this.convertToNoteIfNeeded(cipher); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -- } -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/codebookCsvImporter.ts b/jslib/common/src/importers/codebookCsvImporter.ts -index fdc563d2..4d7aeab8 100644 ---- a/jslib/common/src/importers/codebookCsvImporter.ts -+++ b/jslib/common/src/importers/codebookCsvImporter.ts -@@ -1,47 +1,47 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class CodebookCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -- -- results.forEach(value => { -- this.processFolder(result, this.getValueOrDefault(value.Category)); -- -- const cipher = this.initLoginCipher(); -- cipher.favorite = this.getValueOrDefault(value.Favorite) === 'True'; -- cipher.name = this.getValueOrDefault(value.Entry, '--'); -- cipher.notes = this.getValueOrDefault(value.Note); -- cipher.login.username = this.getValueOrDefault(value.Username, value.Email); -- cipher.login.password = this.getValueOrDefault(value.Password); -- cipher.login.totp = this.getValueOrDefault(value.TOTP); -- cipher.login.uris = this.makeUriArray(value.Website); -- -- if (!this.isNullOrWhitespace(value.Username)) { -- this.processKvp(cipher, 'Email', value.Email); -- } -- this.processKvp(cipher, 'Phone', value.Phone); -- this.processKvp(cipher, 'PIN', value.PIN); -- this.processKvp(cipher, 'Account', value.Account); -- this.processKvp(cipher, 'Date', value.Date); -- -- this.convertToNoteIfNeeded(cipher); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -- -- if (this.organization) { -- this.moveFoldersToCollections(result); -- } -- -- result.success = true; -- return Promise.resolve(result); -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); - } -+ -+ results.forEach((value) => { -+ this.processFolder(result, this.getValueOrDefault(value.Category)); -+ -+ const cipher = this.initLoginCipher(); -+ cipher.favorite = this.getValueOrDefault(value.Favorite) === "True"; -+ cipher.name = this.getValueOrDefault(value.Entry, "--"); -+ cipher.notes = this.getValueOrDefault(value.Note); -+ cipher.login.username = this.getValueOrDefault(value.Username, value.Email); -+ cipher.login.password = this.getValueOrDefault(value.Password); -+ cipher.login.totp = this.getValueOrDefault(value.TOTP); -+ cipher.login.uris = this.makeUriArray(value.Website); -+ -+ if (!this.isNullOrWhitespace(value.Username)) { -+ this.processKvp(cipher, "Email", value.Email); -+ } -+ this.processKvp(cipher, "Phone", value.Phone); -+ this.processKvp(cipher, "PIN", value.PIN); -+ this.processKvp(cipher, "Account", value.Account); -+ this.processKvp(cipher, "Date", value.Date); -+ -+ this.convertToNoteIfNeeded(cipher); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); -+ -+ if (this.organization) { -+ this.moveFoldersToCollections(result); -+ } -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/dashlaneJsonImporter.ts b/jslib/common/src/importers/dashlaneJsonImporter.ts -index dd8d7bb6..b3dff318 100644 ---- a/jslib/common/src/importers/dashlaneJsonImporter.ts -+++ b/jslib/common/src/importers/dashlaneJsonImporter.ts -@@ -1,162 +1,172 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -- --import { ImportResult } from '../models/domain/importResult'; -- --import { CardView } from '../models/view/cardView'; --import { CipherView } from '../models/view/cipherView'; --import { IdentityView } from '../models/view/identityView'; --import { SecureNoteView } from '../models/view/secureNoteView'; -- --import { CipherType } from '../enums/cipherType'; --import { SecureNoteType } from '../enums/secureNoteType'; -- --const HandledResults = new Set(['ADDRESS', 'AUTHENTIFIANT', 'BANKSTATEMENT', 'IDCARD', 'IDENTITY', -- 'PAYMENTMEANS_CREDITCARD', 'PAYMENTMEAN_PAYPAL', 'EMAIL']); -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; -+ -+import { ImportResult } from "../models/domain/importResult"; -+ -+import { CardView } from "../models/view/cardView"; -+import { CipherView } from "../models/view/cipherView"; -+import { IdentityView } from "../models/view/identityView"; -+import { SecureNoteView } from "../models/view/secureNoteView"; -+ -+import { CipherType } from "../enums/cipherType"; -+import { SecureNoteType } from "../enums/secureNoteType"; -+ -+const HandledResults = new Set([ -+ "ADDRESS", -+ "AUTHENTIFIANT", -+ "BANKSTATEMENT", -+ "IDCARD", -+ "IDENTITY", -+ "PAYMENTMEANS_CREDITCARD", -+ "PAYMENTMEAN_PAYPAL", -+ "EMAIL", -+]); - - export class DashlaneJsonImporter extends BaseImporter implements Importer { -- private result: ImportResult; -- -- parse(data: string): Promise { -- this.result = new ImportResult(); -- const results = JSON.parse(data); -- if (results == null || results.length === 0) { -- this.result.success = false; -- return Promise.resolve(this.result); -- } -- -- if (results.ADDRESS != null) { -- this.processAddress(results.ADDRESS); -- } -- if (results.AUTHENTIFIANT != null) { -- this.processAuth(results.AUTHENTIFIANT); -- } -- if (results.BANKSTATEMENT != null) { -- this.processNote(results.BANKSTATEMENT, 'BankAccountName'); -- } -- if (results.IDCARD != null) { -- this.processNote(results.IDCARD, 'Fullname'); -- } -- if (results.PAYMENTMEANS_CREDITCARD != null) { -- this.processCard(results.PAYMENTMEANS_CREDITCARD); -- } -- if (results.IDENTITY != null) { -- this.processIdentity(results.IDENTITY); -- } -- -- for (const key in results) { -- if (results.hasOwnProperty(key) && !HandledResults.has(key)) { -- this.processNote(results[key], null, 'Generic Note'); -- } -- } -- -- this.result.success = true; -- return Promise.resolve(this.result); -+ private result: ImportResult; -+ -+ parse(data: string): Promise { -+ this.result = new ImportResult(); -+ const results = JSON.parse(data); -+ if (results == null || results.length === 0) { -+ this.result.success = false; -+ return Promise.resolve(this.result); - } - -- private processAuth(results: any[]) { -- results.forEach((credential: any) => { -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(credential.title); -- -- cipher.login.username = this.getValueOrDefault(credential.login, -- this.getValueOrDefault(credential.secondaryLogin)); -- if (this.isNullOrWhitespace(cipher.login.username)) { -- cipher.login.username = this.getValueOrDefault(credential.email); -- } else if (!this.isNullOrWhitespace(credential.email)) { -- cipher.notes = ('Email: ' + credential.email + '\n'); -- } -- -- cipher.login.password = this.getValueOrDefault(credential.password); -- cipher.login.uris = this.makeUriArray(credential.domain); -- cipher.notes += this.getValueOrDefault(credential.note, ''); -- -- this.convertToNoteIfNeeded(cipher); -- this.cleanupCipher(cipher); -- this.result.ciphers.push(cipher); -- }); -+ if (results.ADDRESS != null) { -+ this.processAddress(results.ADDRESS); - } -- -- private processIdentity(results: any[]) { -- results.forEach((obj: any) => { -- const cipher = new CipherView(); -- cipher.identity = new IdentityView(); -- cipher.type = CipherType.Identity; -- cipher.name = this.getValueOrDefault(obj.fullName, ''); -- const nameParts = cipher.name.split(' '); -- if (nameParts.length > 0) { -- cipher.identity.firstName = this.getValueOrDefault(nameParts[0]); -- } -- if (nameParts.length === 2) { -- cipher.identity.lastName = this.getValueOrDefault(nameParts[1]); -- } else if (nameParts.length === 3) { -- cipher.identity.middleName = this.getValueOrDefault(nameParts[1]); -- cipher.identity.lastName = this.getValueOrDefault(nameParts[2]); -- } -- cipher.identity.username = this.getValueOrDefault(obj.pseudo); -- this.cleanupCipher(cipher); -- this.result.ciphers.push(cipher); -- }); -+ if (results.AUTHENTIFIANT != null) { -+ this.processAuth(results.AUTHENTIFIANT); - } -- -- private processAddress(results: any[]) { -- results.forEach((obj: any) => { -- const cipher = new CipherView(); -- cipher.identity = new IdentityView(); -- cipher.type = CipherType.Identity; -- cipher.name = this.getValueOrDefault(obj.addressName); -- cipher.identity.address1 = this.getValueOrDefault(obj.addressFull); -- cipher.identity.city = this.getValueOrDefault(obj.city); -- cipher.identity.state = this.getValueOrDefault(obj.state); -- cipher.identity.postalCode = this.getValueOrDefault(obj.zipcode); -- cipher.identity.country = this.getValueOrDefault(obj.country); -- if (cipher.identity.country != null) { -- cipher.identity.country = cipher.identity.country.toUpperCase(); -- } -- this.cleanupCipher(cipher); -- this.result.ciphers.push(cipher); -- }); -+ if (results.BANKSTATEMENT != null) { -+ this.processNote(results.BANKSTATEMENT, "BankAccountName"); - } -- -- private processCard(results: any[]) { -- results.forEach((obj: any) => { -- const cipher = new CipherView(); -- cipher.card = new CardView(); -- cipher.type = CipherType.Card; -- cipher.name = this.getValueOrDefault(obj.bank); -- cipher.card.number = this.getValueOrDefault(obj.cardNumber); -- cipher.card.brand = this.getCardBrand(cipher.card.number); -- cipher.card.cardholderName = this.getValueOrDefault(obj.owner); -- if (!this.isNullOrWhitespace(cipher.card.brand)) { -- if (this.isNullOrWhitespace(cipher.name)) { -- cipher.name = cipher.card.brand; -- } else { -- cipher.name += (' - ' + cipher.card.brand); -- } -- } -- this.cleanupCipher(cipher); -- this.result.ciphers.push(cipher); -- }); -+ if (results.IDCARD != null) { -+ this.processNote(results.IDCARD, "Fullname"); -+ } -+ if (results.PAYMENTMEANS_CREDITCARD != null) { -+ this.processCard(results.PAYMENTMEANS_CREDITCARD); -+ } -+ if (results.IDENTITY != null) { -+ this.processIdentity(results.IDENTITY); - } - -- private processNote(results: any[], nameProperty: string, name: string = null) { -- results.forEach((obj: any) => { -- const cipher = new CipherView(); -- cipher.secureNote = new SecureNoteView(); -- cipher.type = CipherType.SecureNote; -- cipher.secureNote.type = SecureNoteType.Generic; -- if (name != null) { -- cipher.name = name; -- } else { -- cipher.name = this.getValueOrDefault(obj[nameProperty]); -- } -- for (const key in obj) { -- if (obj.hasOwnProperty(key) && key !== nameProperty) { -- this.processKvp(cipher, key, obj[key].toString()); -- } -- } -- this.cleanupCipher(cipher); -- this.result.ciphers.push(cipher); -- }); -+ for (const key in results) { -+ if (results.hasOwnProperty(key) && !HandledResults.has(key)) { -+ this.processNote(results[key], null, "Generic Note"); -+ } - } -+ -+ this.result.success = true; -+ return Promise.resolve(this.result); -+ } -+ -+ private processAuth(results: any[]) { -+ results.forEach((credential: any) => { -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(credential.title); -+ -+ cipher.login.username = this.getValueOrDefault( -+ credential.login, -+ this.getValueOrDefault(credential.secondaryLogin) -+ ); -+ if (this.isNullOrWhitespace(cipher.login.username)) { -+ cipher.login.username = this.getValueOrDefault(credential.email); -+ } else if (!this.isNullOrWhitespace(credential.email)) { -+ cipher.notes = "Email: " + credential.email + "\n"; -+ } -+ -+ cipher.login.password = this.getValueOrDefault(credential.password); -+ cipher.login.uris = this.makeUriArray(credential.domain); -+ cipher.notes += this.getValueOrDefault(credential.note, ""); -+ -+ this.convertToNoteIfNeeded(cipher); -+ this.cleanupCipher(cipher); -+ this.result.ciphers.push(cipher); -+ }); -+ } -+ -+ private processIdentity(results: any[]) { -+ results.forEach((obj: any) => { -+ const cipher = new CipherView(); -+ cipher.identity = new IdentityView(); -+ cipher.type = CipherType.Identity; -+ cipher.name = this.getValueOrDefault(obj.fullName, ""); -+ const nameParts = cipher.name.split(" "); -+ if (nameParts.length > 0) { -+ cipher.identity.firstName = this.getValueOrDefault(nameParts[0]); -+ } -+ if (nameParts.length === 2) { -+ cipher.identity.lastName = this.getValueOrDefault(nameParts[1]); -+ } else if (nameParts.length === 3) { -+ cipher.identity.middleName = this.getValueOrDefault(nameParts[1]); -+ cipher.identity.lastName = this.getValueOrDefault(nameParts[2]); -+ } -+ cipher.identity.username = this.getValueOrDefault(obj.pseudo); -+ this.cleanupCipher(cipher); -+ this.result.ciphers.push(cipher); -+ }); -+ } -+ -+ private processAddress(results: any[]) { -+ results.forEach((obj: any) => { -+ const cipher = new CipherView(); -+ cipher.identity = new IdentityView(); -+ cipher.type = CipherType.Identity; -+ cipher.name = this.getValueOrDefault(obj.addressName); -+ cipher.identity.address1 = this.getValueOrDefault(obj.addressFull); -+ cipher.identity.city = this.getValueOrDefault(obj.city); -+ cipher.identity.state = this.getValueOrDefault(obj.state); -+ cipher.identity.postalCode = this.getValueOrDefault(obj.zipcode); -+ cipher.identity.country = this.getValueOrDefault(obj.country); -+ if (cipher.identity.country != null) { -+ cipher.identity.country = cipher.identity.country.toUpperCase(); -+ } -+ this.cleanupCipher(cipher); -+ this.result.ciphers.push(cipher); -+ }); -+ } -+ -+ private processCard(results: any[]) { -+ results.forEach((obj: any) => { -+ const cipher = new CipherView(); -+ cipher.card = new CardView(); -+ cipher.type = CipherType.Card; -+ cipher.name = this.getValueOrDefault(obj.bank); -+ cipher.card.number = this.getValueOrDefault(obj.cardNumber); -+ cipher.card.brand = this.getCardBrand(cipher.card.number); -+ cipher.card.cardholderName = this.getValueOrDefault(obj.owner); -+ if (!this.isNullOrWhitespace(cipher.card.brand)) { -+ if (this.isNullOrWhitespace(cipher.name)) { -+ cipher.name = cipher.card.brand; -+ } else { -+ cipher.name += " - " + cipher.card.brand; -+ } -+ } -+ this.cleanupCipher(cipher); -+ this.result.ciphers.push(cipher); -+ }); -+ } -+ -+ private processNote(results: any[], nameProperty: string, name: string = null) { -+ results.forEach((obj: any) => { -+ const cipher = new CipherView(); -+ cipher.secureNote = new SecureNoteView(); -+ cipher.type = CipherType.SecureNote; -+ cipher.secureNote.type = SecureNoteType.Generic; -+ if (name != null) { -+ cipher.name = name; -+ } else { -+ cipher.name = this.getValueOrDefault(obj[nameProperty]); -+ } -+ for (const key in obj) { -+ if (obj.hasOwnProperty(key) && key !== nameProperty) { -+ this.processKvp(cipher, key, obj[key].toString()); -+ } -+ } -+ this.cleanupCipher(cipher); -+ this.result.ciphers.push(cipher); -+ }); -+ } - } -diff --git a/jslib/common/src/importers/encryptrCsvImporter.ts b/jslib/common/src/importers/encryptrCsvImporter.ts -index adad9aa7..84f55e09 100644 ---- a/jslib/common/src/importers/encryptrCsvImporter.ts -+++ b/jslib/common/src/importers/encryptrCsvImporter.ts -@@ -1,62 +1,62 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - --import { CardView } from '../models/view/cardView'; -+import { CardView } from "../models/view/cardView"; - --import { CipherType } from '../enums/cipherType'; -+import { CipherType } from "../enums/cipherType"; - - export class EncryptrCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } -+ -+ results.forEach((value) => { -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(value.Label, "--"); -+ cipher.notes = this.getValueOrDefault(value.Notes); -+ const text = this.getValueOrDefault(value.Text); -+ if (!this.isNullOrWhitespace(text)) { -+ if (this.isNullOrWhitespace(cipher.notes)) { -+ cipher.notes = text; -+ } else { -+ cipher.notes += "\n\n" + text; -+ } -+ } -+ -+ const type = value["Entry Type"]; -+ if (type === "Password") { -+ cipher.login.username = this.getValueOrDefault(value.Username); -+ cipher.login.password = this.getValueOrDefault(value.Password); -+ cipher.login.uris = this.makeUriArray(value["Site URL"]); -+ } else if (type === "Credit Card") { -+ cipher.type = CipherType.Card; -+ cipher.card = new CardView(); -+ cipher.card.cardholderName = this.getValueOrDefault(value["Name on card"]); -+ cipher.card.number = this.getValueOrDefault(value["Card Number"]); -+ cipher.card.brand = this.getCardBrand(cipher.card.number); -+ cipher.card.code = this.getValueOrDefault(value.CVV); -+ const expiry = this.getValueOrDefault(value.Expiry); -+ if (!this.isNullOrWhitespace(expiry)) { -+ const expParts = expiry.split("/"); -+ if (expParts.length > 1) { -+ cipher.card.expMonth = parseInt(expParts[0], null).toString(); -+ cipher.card.expYear = (2000 + parseInt(expParts[1], null)).toString(); -+ } - } -+ } - -- results.forEach(value => { -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value.Label, '--'); -- cipher.notes = this.getValueOrDefault(value.Notes); -- const text = this.getValueOrDefault(value.Text); -- if (!this.isNullOrWhitespace(text)) { -- if (this.isNullOrWhitespace(cipher.notes)) { -- cipher.notes = text; -- } else { -- cipher.notes += ('\n\n' + text); -- } -- } -- -- const type = value['Entry Type']; -- if (type === 'Password') { -- cipher.login.username = this.getValueOrDefault(value.Username); -- cipher.login.password = this.getValueOrDefault(value.Password); -- cipher.login.uris = this.makeUriArray(value['Site URL']); -- } else if (type === 'Credit Card') { -- cipher.type = CipherType.Card; -- cipher.card = new CardView(); -- cipher.card.cardholderName = this.getValueOrDefault(value['Name on card']); -- cipher.card.number = this.getValueOrDefault(value['Card Number']); -- cipher.card.brand = this.getCardBrand(cipher.card.number); -- cipher.card.code = this.getValueOrDefault(value.CVV); -- const expiry = this.getValueOrDefault(value.Expiry); -- if (!this.isNullOrWhitespace(expiry)) { -- const expParts = expiry.split('/'); -- if (expParts.length > 1) { -- cipher.card.expMonth = parseInt(expParts[0], null).toString(); -- cipher.card.expYear = (2000 + parseInt(expParts[1], null)).toString(); -- } -- } -- } -- -- this.convertToNoteIfNeeded(cipher); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -- -- result.success = true; -- return Promise.resolve(result); -- } -+ this.convertToNoteIfNeeded(cipher); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/enpassCsvImporter.ts b/jslib/common/src/importers/enpassCsvImporter.ts -index 0b421d96..5f9ad54e 100644 ---- a/jslib/common/src/importers/enpassCsvImporter.ts -+++ b/jslib/common/src/importers/enpassCsvImporter.ts -@@ -1,112 +1,135 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - --import { CipherType } from '../enums/cipherType'; --import { SecureNoteType } from '../enums/secureNoteType'; -+import { CipherType } from "../enums/cipherType"; -+import { SecureNoteType } from "../enums/secureNoteType"; - --import { CardView } from '../models/view/cardView'; --import { SecureNoteView } from '../models/view/secureNoteView'; -+import { CardView } from "../models/view/cardView"; -+import { SecureNoteView } from "../models/view/secureNoteView"; - - export class EnpassCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, false); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -- -- let firstRow = true; -- results.forEach(value => { -- if (value.length < 2 || (firstRow && (value[0] === 'Title' || value[0] === 'title'))) { -- firstRow = false; -- return; -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, false); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- const cipher = this.initLoginCipher(); -- cipher.notes = this.getValueOrDefault(value[value.length - 1]); -- cipher.name = this.getValueOrDefault(value[0], '--'); -+ let firstRow = true; -+ results.forEach((value) => { -+ if (value.length < 2 || (firstRow && (value[0] === "Title" || value[0] === "title"))) { -+ firstRow = false; -+ return; -+ } - -- if (value.length === 2 || (!this.containsField(value, 'username') && -- !this.containsField(value, 'password') && !this.containsField(value, 'email') && -- !this.containsField(value, 'url'))) { -- cipher.type = CipherType.SecureNote; -- cipher.secureNote = new SecureNoteView(); -- cipher.secureNote.type = SecureNoteType.Generic; -- } -+ const cipher = this.initLoginCipher(); -+ cipher.notes = this.getValueOrDefault(value[value.length - 1]); -+ cipher.name = this.getValueOrDefault(value[0], "--"); - -- if (this.containsField(value, 'cardholder') && this.containsField(value, 'number') && -- this.containsField(value, 'expiry date')) { -- cipher.type = CipherType.Card; -- cipher.card = new CardView(); -- } -+ if ( -+ value.length === 2 || -+ (!this.containsField(value, "username") && -+ !this.containsField(value, "password") && -+ !this.containsField(value, "email") && -+ !this.containsField(value, "url")) -+ ) { -+ cipher.type = CipherType.SecureNote; -+ cipher.secureNote = new SecureNoteView(); -+ cipher.secureNote.type = SecureNoteType.Generic; -+ } - -- if (value.length > 2 && (value.length % 2) === 0) { -- for (let i = 0; i < value.length - 2; i += 2) { -- const fieldValue: string = value[i + 2]; -- if (this.isNullOrWhitespace(fieldValue)) { -- continue; -- } -+ if ( -+ this.containsField(value, "cardholder") && -+ this.containsField(value, "number") && -+ this.containsField(value, "expiry date") -+ ) { -+ cipher.type = CipherType.Card; -+ cipher.card = new CardView(); -+ } - -- const fieldName: string = value[i + 1]; -- const fieldNameLower = fieldName.toLowerCase(); -+ if (value.length > 2 && value.length % 2 === 0) { -+ for (let i = 0; i < value.length - 2; i += 2) { -+ const fieldValue: string = value[i + 2]; -+ if (this.isNullOrWhitespace(fieldValue)) { -+ continue; -+ } - -- if (cipher.type === CipherType.Login) { -- if (fieldNameLower === 'url' && (cipher.login.uris == null || cipher.login.uris.length === 0)) { -- cipher.login.uris = this.makeUriArray(fieldValue); -- continue; -- } else if ((fieldNameLower === 'username' || fieldNameLower === 'email') && -- this.isNullOrWhitespace(cipher.login.username)) { -- cipher.login.username = fieldValue; -- continue; -- } else if (fieldNameLower === 'password' && this.isNullOrWhitespace(cipher.login.password)) { -- cipher.login.password = fieldValue; -- continue; -- } else if (fieldNameLower === 'totp' && this.isNullOrWhitespace(cipher.login.totp)) { -- cipher.login.totp = fieldValue; -- continue; -- } -- } else if (cipher.type === CipherType.Card) { -- if (fieldNameLower === 'cardholder' && this.isNullOrWhitespace(cipher.card.cardholderName)) { -- cipher.card.cardholderName = fieldValue; -- continue; -- } else if (fieldNameLower === 'number' && this.isNullOrWhitespace(cipher.card.number)) { -- cipher.card.number = fieldValue; -- cipher.card.brand = this.getCardBrand(fieldValue); -- continue; -- } else if (fieldNameLower === 'cvc' && this.isNullOrWhitespace(cipher.card.code)) { -- cipher.card.code = fieldValue; -- continue; -- } else if (fieldNameLower === 'expiry date' && this.isNullOrWhitespace(cipher.card.expMonth) && -- this.isNullOrWhitespace(cipher.card.expYear)) { -- if (this.setCardExpiration(cipher, fieldValue)) { -- continue; -- } -- } else if (fieldNameLower === 'type') { -- // Skip since brand was determined from number above -- continue; -- } -- } -+ const fieldName: string = value[i + 1]; -+ const fieldNameLower = fieldName.toLowerCase(); - -- this.processKvp(cipher, fieldName, fieldValue); -- } -+ if (cipher.type === CipherType.Login) { -+ if ( -+ fieldNameLower === "url" && -+ (cipher.login.uris == null || cipher.login.uris.length === 0) -+ ) { -+ cipher.login.uris = this.makeUriArray(fieldValue); -+ continue; -+ } else if ( -+ (fieldNameLower === "username" || fieldNameLower === "email") && -+ this.isNullOrWhitespace(cipher.login.username) -+ ) { -+ cipher.login.username = fieldValue; -+ continue; -+ } else if ( -+ fieldNameLower === "password" && -+ this.isNullOrWhitespace(cipher.login.password) -+ ) { -+ cipher.login.password = fieldValue; -+ continue; -+ } else if (fieldNameLower === "totp" && this.isNullOrWhitespace(cipher.login.totp)) { -+ cipher.login.totp = fieldValue; -+ continue; - } -+ } else if (cipher.type === CipherType.Card) { -+ if ( -+ fieldNameLower === "cardholder" && -+ this.isNullOrWhitespace(cipher.card.cardholderName) -+ ) { -+ cipher.card.cardholderName = fieldValue; -+ continue; -+ } else if (fieldNameLower === "number" && this.isNullOrWhitespace(cipher.card.number)) { -+ cipher.card.number = fieldValue; -+ cipher.card.brand = this.getCardBrand(fieldValue); -+ continue; -+ } else if (fieldNameLower === "cvc" && this.isNullOrWhitespace(cipher.card.code)) { -+ cipher.card.code = fieldValue; -+ continue; -+ } else if ( -+ fieldNameLower === "expiry date" && -+ this.isNullOrWhitespace(cipher.card.expMonth) && -+ this.isNullOrWhitespace(cipher.card.expYear) -+ ) { -+ if (this.setCardExpiration(cipher, fieldValue)) { -+ continue; -+ } -+ } else if (fieldNameLower === "type") { -+ // Skip since brand was determined from number above -+ continue; -+ } -+ } - -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ this.processKvp(cipher, fieldName, fieldValue); -+ } -+ } - -- result.success = true; -- return Promise.resolve(result); -- } -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- private containsField(fields: any[], name: string) { -- if (fields == null || name == null) { -- return false; -- } -- return fields.filter(f => !this.isNullOrWhitespace(f) && -- f.toLowerCase() === name.toLowerCase()).length > 0; -+ result.success = true; -+ return Promise.resolve(result); -+ } -+ -+ private containsField(fields: any[], name: string) { -+ if (fields == null || name == null) { -+ return false; - } -+ return ( -+ fields.filter((f) => !this.isNullOrWhitespace(f) && f.toLowerCase() === name.toLowerCase()) -+ .length > 0 -+ ); -+ } - } -diff --git a/jslib/common/src/importers/enpassJsonImporter.ts b/jslib/common/src/importers/enpassJsonImporter.ts -index 1b325224..631fbc51 100644 ---- a/jslib/common/src/importers/enpassJsonImporter.ts -+++ b/jslib/common/src/importers/enpassJsonImporter.ts -@@ -1,163 +1,193 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - --import { CardView } from '../models/view/cardView'; --import { CipherView } from '../models/view/cipherView'; --import { FolderView } from '../models/view/folderView'; -+import { CardView } from "../models/view/cardView"; -+import { CipherView } from "../models/view/cipherView"; -+import { FolderView } from "../models/view/folderView"; - --import { CipherType } from '../enums/cipherType'; --import { FieldType } from '../enums/fieldType'; -+import { CipherType } from "../enums/cipherType"; -+import { FieldType } from "../enums/fieldType"; - - export class EnpassJsonImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = JSON.parse(data); -- if (results == null || results.items == null || results.items.length === 0) { -- result.success = false; -- return Promise.resolve(result); -- } -- -- const foldersMap = new Map(); -- const foldersIndexMap = new Map(); -- const folderTree = this.buildFolderTree(results.folders); -- this.flattenFolderTree(null, folderTree, foldersMap); -- foldersMap.forEach((val, key) => { -- foldersIndexMap.set(key, result.folders.length); -- const f = new FolderView(); -- f.name = val; -- result.folders.push(f); -- }); -- -- results.items.forEach((item: any) => { -- if (item.folders != null && item.folders.length > 0 && foldersIndexMap.has(item.folders[0])) { -- result.folderRelationships.push([result.ciphers.length, foldersIndexMap.get(item.folders[0])]); -- } -- -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(item.title); -- cipher.favorite = item.favorite > 0; -- -- if (item.template_type != null && item.fields != null && item.fields.length > 0) { -- if (item.template_type.indexOf('login.') === 0 || item.template_type.indexOf('password.') === 0) { -- this.processLogin(cipher, item.fields); -- } else if (item.template_type.indexOf('creditcard.') === 0) { -- this.processCard(cipher, item.fields); -- } else if (item.template_type.indexOf('identity.') < 0 && -- item.fields.some((f: any) => f.type === 'password' && !this.isNullOrWhitespace(f.value))) { -- this.processLogin(cipher, item.fields); -- } else { -- this.processNote(cipher, item.fields); -- } -- } -- -- cipher.notes += ('\n' + this.getValueOrDefault(item.note, '')); -- this.convertToNoteIfNeeded(cipher); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -- -- result.success = true; -- return Promise.resolve(result); -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = JSON.parse(data); -+ if (results == null || results.items == null || results.items.length === 0) { -+ result.success = false; -+ return Promise.resolve(result); - } - -- private processLogin(cipher: CipherView, fields: any[]) { -- const urls: string[] = []; -- fields.forEach((field: any) => { -- if (this.isNullOrWhitespace(field.value) || field.type === 'section') { -- return; -- } -- -- if ((field.type === 'username' || field.type === 'email') && -- this.isNullOrWhitespace(cipher.login.username)) { -- cipher.login.username = field.value; -- } else if (field.type === 'password' && this.isNullOrWhitespace(cipher.login.password)) { -- cipher.login.password = field.value; -- } else if (field.type === 'totp' && this.isNullOrWhitespace(cipher.login.totp)) { -- cipher.login.totp = field.value; -- } else if (field.type === 'url') { -- urls.push(field.value); -- } else { -- this.processKvp(cipher, field.label, field.value, -- field.sensitive === 1 ? FieldType.Hidden : FieldType.Text); -- } -- }); -- cipher.login.uris = this.makeUriArray(urls); -+ const foldersMap = new Map(); -+ const foldersIndexMap = new Map(); -+ const folderTree = this.buildFolderTree(results.folders); -+ this.flattenFolderTree(null, folderTree, foldersMap); -+ foldersMap.forEach((val, key) => { -+ foldersIndexMap.set(key, result.folders.length); -+ const f = new FolderView(); -+ f.name = val; -+ result.folders.push(f); -+ }); -+ -+ results.items.forEach((item: any) => { -+ if (item.folders != null && item.folders.length > 0 && foldersIndexMap.has(item.folders[0])) { -+ result.folderRelationships.push([ -+ result.ciphers.length, -+ foldersIndexMap.get(item.folders[0]), -+ ]); -+ } -+ -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(item.title); -+ cipher.favorite = item.favorite > 0; -+ -+ if (item.template_type != null && item.fields != null && item.fields.length > 0) { -+ if ( -+ item.template_type.indexOf("login.") === 0 || -+ item.template_type.indexOf("password.") === 0 -+ ) { -+ this.processLogin(cipher, item.fields); -+ } else if (item.template_type.indexOf("creditcard.") === 0) { -+ this.processCard(cipher, item.fields); -+ } else if ( -+ item.template_type.indexOf("identity.") < 0 && -+ item.fields.some((f: any) => f.type === "password" && !this.isNullOrWhitespace(f.value)) -+ ) { -+ this.processLogin(cipher, item.fields); -+ } else { -+ this.processNote(cipher, item.fields); -+ } -+ } -+ -+ cipher.notes += "\n" + this.getValueOrDefault(item.note, ""); -+ this.convertToNoteIfNeeded(cipher); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } -+ -+ private processLogin(cipher: CipherView, fields: any[]) { -+ const urls: string[] = []; -+ fields.forEach((field: any) => { -+ if (this.isNullOrWhitespace(field.value) || field.type === "section") { -+ return; -+ } -+ -+ if ( -+ (field.type === "username" || field.type === "email") && -+ this.isNullOrWhitespace(cipher.login.username) -+ ) { -+ cipher.login.username = field.value; -+ } else if (field.type === "password" && this.isNullOrWhitespace(cipher.login.password)) { -+ cipher.login.password = field.value; -+ } else if (field.type === "totp" && this.isNullOrWhitespace(cipher.login.totp)) { -+ cipher.login.totp = field.value; -+ } else if (field.type === "url") { -+ urls.push(field.value); -+ } else { -+ this.processKvp( -+ cipher, -+ field.label, -+ field.value, -+ field.sensitive === 1 ? FieldType.Hidden : FieldType.Text -+ ); -+ } -+ }); -+ cipher.login.uris = this.makeUriArray(urls); -+ } -+ -+ private processCard(cipher: CipherView, fields: any[]) { -+ cipher.card = new CardView(); -+ cipher.type = CipherType.Card; -+ fields.forEach((field: any) => { -+ if ( -+ this.isNullOrWhitespace(field.value) || -+ field.type === "section" || -+ field.type === "ccType" -+ ) { -+ return; -+ } -+ -+ if (field.type === "ccName" && this.isNullOrWhitespace(cipher.card.cardholderName)) { -+ cipher.card.cardholderName = field.value; -+ } else if (field.type === "ccNumber" && this.isNullOrWhitespace(cipher.card.number)) { -+ cipher.card.number = field.value; -+ cipher.card.brand = this.getCardBrand(cipher.card.number); -+ } else if (field.type === "ccCvc" && this.isNullOrWhitespace(cipher.card.code)) { -+ cipher.card.code = field.value; -+ } else if (field.type === "ccExpiry" && this.isNullOrWhitespace(cipher.card.expYear)) { -+ if (!this.setCardExpiration(cipher, field.value)) { -+ this.processKvp( -+ cipher, -+ field.label, -+ field.value, -+ field.sensitive === 1 ? FieldType.Hidden : FieldType.Text -+ ); -+ } -+ } else { -+ this.processKvp( -+ cipher, -+ field.label, -+ field.value, -+ field.sensitive === 1 ? FieldType.Hidden : FieldType.Text -+ ); -+ } -+ }); -+ } -+ -+ private processNote(cipher: CipherView, fields: any[]) { -+ fields.forEach((field: any) => { -+ if (this.isNullOrWhitespace(field.value) || field.type === "section") { -+ return; -+ } -+ this.processKvp( -+ cipher, -+ field.label, -+ field.value, -+ field.sensitive === 1 ? FieldType.Hidden : FieldType.Text -+ ); -+ }); -+ } -+ -+ private buildFolderTree(folders: any[]): any[] { -+ if (folders == null) { -+ return []; - } -- -- private processCard(cipher: CipherView, fields: any[]) { -- cipher.card = new CardView(); -- cipher.type = CipherType.Card; -- fields.forEach((field: any) => { -- if (this.isNullOrWhitespace(field.value) || field.type === 'section' || field.type === 'ccType') { -- return; -- } -- -- if (field.type === 'ccName' && this.isNullOrWhitespace(cipher.card.cardholderName)) { -- cipher.card.cardholderName = field.value; -- } else if (field.type === 'ccNumber' && this.isNullOrWhitespace(cipher.card.number)) { -- cipher.card.number = field.value; -- cipher.card.brand = this.getCardBrand(cipher.card.number); -- } else if (field.type === 'ccCvc' && this.isNullOrWhitespace(cipher.card.code)) { -- cipher.card.code = field.value; -- } else if (field.type === 'ccExpiry' && this.isNullOrWhitespace(cipher.card.expYear)) { -- if (!this.setCardExpiration(cipher, field.value)) { -- this.processKvp(cipher, field.label, field.value, -- field.sensitive === 1 ? FieldType.Hidden : FieldType.Text); -- } -- } else { -- this.processKvp(cipher, field.label, field.value, -- field.sensitive === 1 ? FieldType.Hidden : FieldType.Text); -- } -- }); -+ const folderTree: any[] = []; -+ const map = new Map([]); -+ folders.forEach((obj: any) => { -+ map.set(obj.uuid, obj); -+ obj.children = []; -+ }); -+ folders.forEach((obj: any) => { -+ if (obj.parent_uuid != null && obj.parent_uuid !== "" && map.has(obj.parent_uuid)) { -+ map.get(obj.parent_uuid).children.push(obj); -+ } else { -+ folderTree.push(obj); -+ } -+ }); -+ return folderTree; -+ } -+ -+ private flattenFolderTree(titlePrefix: string, tree: any[], map: Map) { -+ if (tree == null) { -+ return; - } -- -- private processNote(cipher: CipherView, fields: any[]) { -- fields.forEach((field: any) => { -- if (this.isNullOrWhitespace(field.value) || field.type === 'section') { -- return; -- } -- this.processKvp(cipher, field.label, field.value, -- field.sensitive === 1 ? FieldType.Hidden : FieldType.Text); -- }); -- } -- -- private buildFolderTree(folders: any[]): any[] { -- if (folders == null) { -- return []; -+ tree.forEach((f: any) => { -+ if (f.title != null && f.title.trim() !== "") { -+ let title = f.title.trim(); -+ if (titlePrefix != null && titlePrefix.trim() !== "") { -+ title = titlePrefix + "/" + title; - } -- const folderTree: any[] = []; -- const map = new Map([]); -- folders.forEach((obj: any) => { -- map.set(obj.uuid, obj); -- obj.children = []; -- }); -- folders.forEach((obj: any) => { -- if (obj.parent_uuid != null && obj.parent_uuid !== '' && map.has(obj.parent_uuid)) { -- map.get(obj.parent_uuid).children.push(obj); -- } else { -- folderTree.push(obj); -- } -- }); -- return folderTree; -- } -- -- private flattenFolderTree(titlePrefix: string, tree: any[], map: Map) { -- if (tree == null) { -- return; -+ map.set(f.uuid, title); -+ if (f.children != null && f.children.length !== 0) { -+ this.flattenFolderTree(title, f.children, map); - } -- tree.forEach((f: any) => { -- if (f.title != null && f.title.trim() !== '') { -- let title = f.title.trim(); -- if (titlePrefix != null && titlePrefix.trim() !== '') { -- title = titlePrefix + '/' + title; -- } -- map.set(f.uuid, title); -- if (f.children != null && f.children.length !== 0) { -- this.flattenFolderTree(title, f.children, map); -- } -- } -- }); -- } -+ } -+ }); -+ } - } -diff --git a/jslib/common/src/importers/firefoxCsvImporter.ts b/jslib/common/src/importers/firefoxCsvImporter.ts -index df82cf97..3fc97a63 100644 ---- a/jslib/common/src/importers/firefoxCsvImporter.ts -+++ b/jslib/common/src/importers/firefoxCsvImporter.ts -@@ -1,31 +1,33 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class FirefoxCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- results.filter(value => { -- return value.url !== 'chrome://FirefoxAccounts'; -- }).forEach(value => { -- const cipher = this.initLoginCipher(); -- const url = this.getValueOrDefault(value.url, this.getValueOrDefault(value.hostname)); -- cipher.name = this.getValueOrDefault(this.nameFromUrl(url), '--'); -- cipher.login.username = this.getValueOrDefault(value.username); -- cipher.login.password = this.getValueOrDefault(value.password); -- cipher.login.uris = this.makeUriArray(url); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ results -+ .filter((value) => { -+ return value.url !== "chrome://FirefoxAccounts"; -+ }) -+ .forEach((value) => { -+ const cipher = this.initLoginCipher(); -+ const url = this.getValueOrDefault(value.url, this.getValueOrDefault(value.hostname)); -+ cipher.name = this.getValueOrDefault(this.nameFromUrl(url), "--"); -+ cipher.login.username = this.getValueOrDefault(value.username); -+ cipher.login.password = this.getValueOrDefault(value.password); -+ cipher.login.uris = this.makeUriArray(url); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -- } -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/fsecureFskImporter.ts b/jslib/common/src/importers/fsecureFskImporter.ts -index 1aac8932..91564fe1 100644 ---- a/jslib/common/src/importers/fsecureFskImporter.ts -+++ b/jslib/common/src/importers/fsecureFskImporter.ts -@@ -1,60 +1,60 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - --import { CardView } from '../models/view/cardView'; -+import { CardView } from "../models/view/cardView"; - --import { CipherType } from '../enums/cipherType'; -+import { CipherType } from "../enums/cipherType"; - - export class FSecureFskImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = JSON.parse(data); -- if (results == null || results.data == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = JSON.parse(data); -+ if (results == null || results.data == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- for (const key in results.data) { -- if (!results.data.hasOwnProperty(key)) { -- continue; -- } -- -- const value = results.data[key]; -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value.service); -- cipher.notes = this.getValueOrDefault(value.notes); -- -- if (value.style === 'website') { -- cipher.login.username = this.getValueOrDefault(value.username); -- cipher.login.password = this.getValueOrDefault(value.password); -- cipher.login.uris = this.makeUriArray(value.url); -- } else if (value.style === 'creditcard') { -- cipher.type = CipherType.Card; -- cipher.card = new CardView(); -- cipher.card.cardholderName = this.getValueOrDefault(value.username); -- cipher.card.number = this.getValueOrDefault(value.creditNumber); -- cipher.card.brand = this.getCardBrand(cipher.card.number); -- cipher.card.code = this.getValueOrDefault(value.creditCvv); -- if (!this.isNullOrWhitespace(value.creditExpiry)) { -- if (!this.setCardExpiration(cipher, value.creditExpiry)) { -- this.processKvp(cipher, 'Expiration', value.creditExpiry); -- } -- } -- if (!this.isNullOrWhitespace(value.password)) { -- this.processKvp(cipher, 'PIN', value.password); -- } -- } else { -- continue; -- } -- -- this.convertToNoteIfNeeded(cipher); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -+ for (const key in results.data) { -+ if (!results.data.hasOwnProperty(key)) { -+ continue; -+ } -+ -+ const value = results.data[key]; -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(value.service); -+ cipher.notes = this.getValueOrDefault(value.notes); -+ -+ if (value.style === "website" || value.style === "globe") { -+ cipher.login.username = this.getValueOrDefault(value.username); -+ cipher.login.password = this.getValueOrDefault(value.password); -+ cipher.login.uris = this.makeUriArray(value.url); -+ } else if (value.style === "creditcard") { -+ cipher.type = CipherType.Card; -+ cipher.card = new CardView(); -+ cipher.card.cardholderName = this.getValueOrDefault(value.username); -+ cipher.card.number = this.getValueOrDefault(value.creditNumber); -+ cipher.card.brand = this.getCardBrand(cipher.card.number); -+ cipher.card.code = this.getValueOrDefault(value.creditCvv); -+ if (!this.isNullOrWhitespace(value.creditExpiry)) { -+ if (!this.setCardExpiration(cipher, value.creditExpiry)) { -+ this.processKvp(cipher, "Expiration", value.creditExpiry); -+ } - } -+ if (!this.isNullOrWhitespace(value.password)) { -+ this.processKvp(cipher, "PIN", value.password); -+ } -+ } else { -+ continue; -+ } - -- result.success = true; -- return Promise.resolve(result); -+ this.convertToNoteIfNeeded(cipher); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); - } -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/gnomeJsonImporter.ts b/jslib/common/src/importers/gnomeJsonImporter.ts -index 23ebfc83..98dc79b5 100644 ---- a/jslib/common/src/importers/gnomeJsonImporter.ts -+++ b/jslib/common/src/importers/gnomeJsonImporter.ts -@@ -1,60 +1,71 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class GnomeJsonImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = JSON.parse(data); -- if (results == null || Object.keys(results).length === 0) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = JSON.parse(data); -+ if (results == null || Object.keys(results).length === 0) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- for (const keyRing in results) { -- if (!results.hasOwnProperty(keyRing) || this.isNullOrWhitespace(keyRing) || -- results[keyRing].length === 0) { -- continue; -- } -+ for (const keyRing in results) { -+ if ( -+ !results.hasOwnProperty(keyRing) || -+ this.isNullOrWhitespace(keyRing) || -+ results[keyRing].length === 0 -+ ) { -+ continue; -+ } - -- results[keyRing].forEach((value: any) => { -- if (this.isNullOrWhitespace(value.display_name) || value.display_name.indexOf('http') !== 0) { -- return; -- } -- -- this.processFolder(result, keyRing); -- const cipher = this.initLoginCipher(); -- cipher.name = value.display_name.replace('http://', '').replace('https://', ''); -- if (cipher.name.length > 30) { -- cipher.name = cipher.name.substring(0, 30); -- } -- cipher.login.password = this.getValueOrDefault(value.secret); -- cipher.login.uris = this.makeUriArray(value.display_name); -- -- if (value.attributes != null) { -- cipher.login.username = value.attributes != null ? -- this.getValueOrDefault(value.attributes.username_value) : null; -- for (const attr in value.attributes) { -- if (!value.attributes.hasOwnProperty(attr) || attr === 'username_value' || -- attr === 'xdg:schema') { -- continue; -- } -- this.processKvp(cipher, attr, value.attributes[attr]); -- } -- } -- -- this.convertToNoteIfNeeded(cipher); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ results[keyRing].forEach((value: any) => { -+ if ( -+ this.isNullOrWhitespace(value.display_name) || -+ value.display_name.indexOf("http") !== 0 -+ ) { -+ return; - } - -- if (this.organization) { -- this.moveFoldersToCollections(result); -+ this.processFolder(result, keyRing); -+ const cipher = this.initLoginCipher(); -+ cipher.name = value.display_name.replace("http://", "").replace("https://", ""); -+ if (cipher.name.length > 30) { -+ cipher.name = cipher.name.substring(0, 30); - } -+ cipher.login.password = this.getValueOrDefault(value.secret); -+ cipher.login.uris = this.makeUriArray(value.display_name); - -- result.success = true; -- return Promise.resolve(result); -+ if (value.attributes != null) { -+ cipher.login.username = -+ value.attributes != null -+ ? this.getValueOrDefault(value.attributes.username_value) -+ : null; -+ for (const attr in value.attributes) { -+ if ( -+ !value.attributes.hasOwnProperty(attr) || -+ attr === "username_value" || -+ attr === "xdg:schema" -+ ) { -+ continue; -+ } -+ this.processKvp(cipher, attr, value.attributes[attr]); -+ } -+ } -+ -+ this.convertToNoteIfNeeded(cipher); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); -+ } -+ -+ if (this.organization) { -+ this.moveFoldersToCollections(result); - } -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/importer.ts b/jslib/common/src/importers/importer.ts -index 487dc184..a62cc6c1 100644 ---- a/jslib/common/src/importers/importer.ts -+++ b/jslib/common/src/importers/importer.ts -@@ -1,6 +1,6 @@ --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export interface Importer { -- organizationId: string; -- parse(data: string): Promise; -+ organizationId: string; -+ parse(data: string): Promise; - } -diff --git a/jslib/common/src/importers/kasperskyTxtImporter.ts b/jslib/common/src/importers/kasperskyTxtImporter.ts -index efaf9228..b4745e1d 100644 ---- a/jslib/common/src/importers/kasperskyTxtImporter.ts -+++ b/jslib/common/src/importers/kasperskyTxtImporter.ts -@@ -1,124 +1,124 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - --const NotesHeader = 'Notes\n\n'; --const ApplicationsHeader = 'Applications\n\n'; --const WebsitesHeader = 'Websites\n\n'; --const Delimiter = '\n---\n'; -+const NotesHeader = "Notes\n\n"; -+const ApplicationsHeader = "Applications\n\n"; -+const WebsitesHeader = "Websites\n\n"; -+const Delimiter = "\n---\n"; - - export class KasperskyTxtImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -+ parse(data: string): Promise { -+ const result = new ImportResult(); - -- let notesData: string; -- let applicationsData: string; -- let websitesData: string; -- let workingData = this.splitNewLine(data).join('\n'); -+ let notesData: string; -+ let applicationsData: string; -+ let websitesData: string; -+ let workingData = this.splitNewLine(data).join("\n"); - -- if (workingData.indexOf(NotesHeader) !== -1) { -- const parts = workingData.split(NotesHeader); -- if (parts.length > 1) { -- workingData = parts[0]; -- notesData = parts[1]; -- } -- } -- if (workingData.indexOf(ApplicationsHeader) !== -1) { -- const parts = workingData.split(ApplicationsHeader); -- if (parts.length > 1) { -- workingData = parts[0]; -- applicationsData = parts[1]; -- } -- } -- if (workingData.indexOf(WebsitesHeader) === 0) { -- const parts = workingData.split(WebsitesHeader); -- if (parts.length > 1) { -- workingData = parts[0]; -- websitesData = parts[1]; -- } -- } -+ if (workingData.indexOf(NotesHeader) !== -1) { -+ const parts = workingData.split(NotesHeader); -+ if (parts.length > 1) { -+ workingData = parts[0]; -+ notesData = parts[1]; -+ } -+ } -+ if (workingData.indexOf(ApplicationsHeader) !== -1) { -+ const parts = workingData.split(ApplicationsHeader); -+ if (parts.length > 1) { -+ workingData = parts[0]; -+ applicationsData = parts[1]; -+ } -+ } -+ if (workingData.indexOf(WebsitesHeader) === 0) { -+ const parts = workingData.split(WebsitesHeader); -+ if (parts.length > 1) { -+ workingData = parts[0]; -+ websitesData = parts[1]; -+ } -+ } - -- const notes = this.parseDataCategory(notesData); -- const applications = this.parseDataCategory(applicationsData); -- const websites = this.parseDataCategory(websitesData); -+ const notes = this.parseDataCategory(notesData); -+ const applications = this.parseDataCategory(applicationsData); -+ const websites = this.parseDataCategory(websitesData); - -- notes.forEach(n => { -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(n.get('Name')); -- cipher.notes = this.getValueOrDefault(n.get('Text')); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ notes.forEach((n) => { -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(n.get("Name")); -+ cipher.notes = this.getValueOrDefault(n.get("Text")); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- websites.concat(applications).forEach(w => { -- const cipher = this.initLoginCipher(); -- const nameKey = w.has('Website name') ? 'Website name' : 'Application'; -- cipher.name = this.getValueOrDefault(w.get(nameKey), ''); -- if (!this.isNullOrWhitespace(w.get('Login name'))) { -- if (!this.isNullOrWhitespace(cipher.name)) { -- cipher.name += ': '; -- } -- cipher.name += w.get('Login name'); -- } -- cipher.notes = this.getValueOrDefault(w.get('Comment')); -- if (w.has('Website URL')) { -- cipher.login.uris = this.makeUriArray(w.get('Website URL')); -- } -- cipher.login.username = this.getValueOrDefault(w.get('Login')); -- cipher.login.password = this.getValueOrDefault(w.get('Password')); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ websites.concat(applications).forEach((w) => { -+ const cipher = this.initLoginCipher(); -+ const nameKey = w.has("Website name") ? "Website name" : "Application"; -+ cipher.name = this.getValueOrDefault(w.get(nameKey), ""); -+ if (!this.isNullOrWhitespace(w.get("Login name"))) { -+ if (!this.isNullOrWhitespace(cipher.name)) { -+ cipher.name += ": "; -+ } -+ cipher.name += w.get("Login name"); -+ } -+ cipher.notes = this.getValueOrDefault(w.get("Comment")); -+ if (w.has("Website URL")) { -+ cipher.login.uris = this.makeUriArray(w.get("Website URL")); -+ } -+ cipher.login.username = this.getValueOrDefault(w.get("Login")); -+ cipher.login.password = this.getValueOrDefault(w.get("Password")); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -- } -+ result.success = true; -+ return Promise.resolve(result); -+ } - -- private parseDataCategory(data: string): Map[] { -- if (this.isNullOrWhitespace(data) || data.indexOf(Delimiter) === -1) { -- return []; -- } -- const items: Map[] = []; -- data.split(Delimiter).forEach(p => { -- if (p.indexOf('\n') === -1) { -- return; -- } -- const item = new Map(); -- let itemComment: string; -- let itemCommentKey: string; -- p.split('\n').forEach(l => { -- if (itemComment != null) { -- itemComment += ('\n' + l); -- return; -- } -- const colonIndex = l.indexOf(':'); -- let key: string; -- let val: string; -- if (colonIndex === -1) { -- return; -- } else { -- key = l.substring(0, colonIndex); -- if (l.length > colonIndex + 1) { -- val = l.substring(colonIndex + 2); -- } -- } -- if (key != null) { -- item.set(key, val); -- } -- if (key === 'Comment' || key === 'Text') { -- itemComment = val; -- itemCommentKey = key; -- } -- }); -- if (itemComment != null && itemCommentKey != null) { -- item.set(itemCommentKey, itemComment); -- } -- if (item.size === 0) { -- return; -- } -- items.push(item); -- }); -- return items; -+ private parseDataCategory(data: string): Map[] { -+ if (this.isNullOrWhitespace(data) || data.indexOf(Delimiter) === -1) { -+ return []; - } -+ const items: Map[] = []; -+ data.split(Delimiter).forEach((p) => { -+ if (p.indexOf("\n") === -1) { -+ return; -+ } -+ const item = new Map(); -+ let itemComment: string; -+ let itemCommentKey: string; -+ p.split("\n").forEach((l) => { -+ if (itemComment != null) { -+ itemComment += "\n" + l; -+ return; -+ } -+ const colonIndex = l.indexOf(":"); -+ let key: string; -+ let val: string; -+ if (colonIndex === -1) { -+ return; -+ } else { -+ key = l.substring(0, colonIndex); -+ if (l.length > colonIndex + 1) { -+ val = l.substring(colonIndex + 2); -+ } -+ } -+ if (key != null) { -+ item.set(key, val); -+ } -+ if (key === "Comment" || key === "Text") { -+ itemComment = val; -+ itemCommentKey = key; -+ } -+ }); -+ if (itemComment != null && itemCommentKey != null) { -+ item.set(itemCommentKey, itemComment); -+ } -+ if (item.size === 0) { -+ return; -+ } -+ items.push(item); -+ }); -+ return items; -+ } - } -diff --git a/jslib/common/src/importers/keepass2XmlImporter.ts b/jslib/common/src/importers/keepass2XmlImporter.ts -index c91a925f..f6a61cf8 100644 ---- a/jslib/common/src/importers/keepass2XmlImporter.ts -+++ b/jslib/common/src/importers/keepass2XmlImporter.ts -@@ -1,100 +1,103 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { FieldType } from '../enums/fieldType'; -+import { FieldType } from "../enums/fieldType"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - --import { FolderView } from '../models/view/folderView'; -+import { FolderView } from "../models/view/folderView"; - - export class KeePass2XmlImporter extends BaseImporter implements Importer { -- result = new ImportResult(); -+ result = new ImportResult(); - -- parse(data: string): Promise { -- const doc = this.parseXml(data); -- if (doc == null) { -- this.result.success = false; -- return Promise.resolve(this.result); -- } -+ parse(data: string): Promise { -+ const doc = this.parseXml(data); -+ if (doc == null) { -+ this.result.success = false; -+ return Promise.resolve(this.result); -+ } - -- const rootGroup = doc.querySelector('KeePassFile > Root > Group'); -- if (rootGroup == null) { -- this.result.errorMessage = 'Missing `KeePassFile > Root > Group` node.'; -- this.result.success = false; -- return Promise.resolve(this.result); -- } -+ const rootGroup = doc.querySelector("KeePassFile > Root > Group"); -+ if (rootGroup == null) { -+ this.result.errorMessage = "Missing `KeePassFile > Root > Group` node."; -+ this.result.success = false; -+ return Promise.resolve(this.result); -+ } - -- this.traverse(rootGroup, true, ''); -+ this.traverse(rootGroup, true, ""); - -- if (this.organization) { -- this.moveFoldersToCollections(this.result); -- } -+ if (this.organization) { -+ this.moveFoldersToCollections(this.result); -+ } - -- this.result.success = true; -- return Promise.resolve(this.result); -+ this.result.success = true; -+ return Promise.resolve(this.result); -+ } -+ -+ traverse(node: Element, isRootNode: boolean, groupPrefixName: string) { -+ const folderIndex = this.result.folders.length; -+ let groupName = groupPrefixName; -+ -+ if (!isRootNode) { -+ if (groupName !== "") { -+ groupName += "/"; -+ } -+ const nameEl = this.querySelectorDirectChild(node, "Name"); -+ groupName += nameEl == null ? "-" : nameEl.textContent; -+ const folder = new FolderView(); -+ folder.name = groupName; -+ this.result.folders.push(folder); - } - -- traverse(node: Element, isRootNode: boolean, groupPrefixName: string) { -- const folderIndex = this.result.folders.length; -- let groupName = groupPrefixName; -- -- if (!isRootNode) { -- if (groupName !== '') { -- groupName += '/'; -- } -- const nameEl = this.querySelectorDirectChild(node, 'Name'); -- groupName += nameEl == null ? '-' : nameEl.textContent; -- const folder = new FolderView(); -- folder.name = groupName; -- this.result.folders.push(folder); -+ this.querySelectorAllDirectChild(node, "Entry").forEach((entry) => { -+ const cipherIndex = this.result.ciphers.length; -+ -+ const cipher = this.initLoginCipher(); -+ this.querySelectorAllDirectChild(entry, "String").forEach((entryString) => { -+ const valueEl = this.querySelectorDirectChild(entryString, "Value"); -+ const value = valueEl != null ? valueEl.textContent : null; -+ if (this.isNullOrWhitespace(value)) { -+ return; -+ } -+ const keyEl = this.querySelectorDirectChild(entryString, "Key"); -+ const key = keyEl != null ? keyEl.textContent : null; -+ -+ if (key === "URL") { -+ cipher.login.uris = this.makeUriArray(value); -+ } else if (key === "UserName") { -+ cipher.login.username = value; -+ } else if (key === "Password") { -+ cipher.login.password = value; -+ } else if (key === "otp") { -+ cipher.login.totp = value.replace("key=", ""); -+ } else if (key === "Title") { -+ cipher.name = value; -+ } else if (key === "Notes") { -+ cipher.notes += value + "\n"; -+ } else { -+ let type = FieldType.Text; -+ const attrs = valueEl.attributes as any; -+ if ( -+ attrs.length > 0 && -+ attrs.ProtectInMemory != null && -+ attrs.ProtectInMemory.value === "True" -+ ) { -+ type = FieldType.Hidden; -+ } -+ this.processKvp(cipher, key, value, type); - } -+ }); - -- this.querySelectorAllDirectChild(node, 'Entry').forEach(entry => { -- const cipherIndex = this.result.ciphers.length; -- -- const cipher = this.initLoginCipher(); -- this.querySelectorAllDirectChild(entry, 'String').forEach(entryString => { -- const valueEl = this.querySelectorDirectChild(entryString, 'Value'); -- const value = valueEl != null ? valueEl.textContent : null; -- if (this.isNullOrWhitespace(value)) { -- return; -- } -- const keyEl = this.querySelectorDirectChild(entryString, 'Key'); -- const key = keyEl != null ? keyEl.textContent : null; -- -- if (key === 'URL') { -- cipher.login.uris = this.makeUriArray(value); -- } else if (key === 'UserName') { -- cipher.login.username = value; -- } else if (key === 'Password') { -- cipher.login.password = value; -- } else if (key === 'otp') { -- cipher.login.totp = value.replace('key=', ''); -- } else if (key === 'Title') { -- cipher.name = value; -- } else if (key === 'Notes') { -- cipher.notes += (value + '\n'); -- } else { -- let type = FieldType.Text; -- const attrs = (valueEl.attributes as any); -- if (attrs.length > 0 && attrs.ProtectInMemory != null && -- attrs.ProtectInMemory.value === 'True') { -- type = FieldType.Hidden; -- } -- this.processKvp(cipher, key, value, type); -- } -- }); -- -- this.cleanupCipher(cipher); -- this.result.ciphers.push(cipher); -- -- if (!isRootNode) { -- this.result.folderRelationships.push([cipherIndex, folderIndex]); -- } -- }); -- -- this.querySelectorAllDirectChild(node, 'Group').forEach(group => { -- this.traverse(group, false, groupName); -- }); -- } -+ this.cleanupCipher(cipher); -+ this.result.ciphers.push(cipher); -+ -+ if (!isRootNode) { -+ this.result.folderRelationships.push([cipherIndex, folderIndex]); -+ } -+ }); -+ -+ this.querySelectorAllDirectChild(node, "Group").forEach((group) => { -+ this.traverse(group, false, groupName); -+ }); -+ } - } -diff --git a/jslib/common/src/importers/keepassxCsvImporter.ts b/jslib/common/src/importers/keepassxCsvImporter.ts -index 6845c8e8..6c5812bd 100644 ---- a/jslib/common/src/importers/keepassxCsvImporter.ts -+++ b/jslib/common/src/importers/keepassxCsvImporter.ts -@@ -1,42 +1,44 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class KeePassXCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -- -- results.forEach(value => { -- if (this.isNullOrWhitespace(value.Title)) { -- return; -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- value.Group = !this.isNullOrWhitespace(value.Group) && value.Group.startsWith('Root/') ? -- value.Group.replace('Root/', '') : value.Group; -- const groupName = !this.isNullOrWhitespace(value.Group) ? value.Group : null; -- this.processFolder(result, groupName); -+ results.forEach((value) => { -+ if (this.isNullOrWhitespace(value.Title)) { -+ return; -+ } - -- const cipher = this.initLoginCipher(); -- cipher.notes = this.getValueOrDefault(value.Notes); -- cipher.name = this.getValueOrDefault(value.Title, '--'); -- cipher.login.username = this.getValueOrDefault(value.Username); -- cipher.login.password = this.getValueOrDefault(value.Password); -- cipher.login.uris = this.makeUriArray(value.URL); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ value.Group = -+ !this.isNullOrWhitespace(value.Group) && value.Group.startsWith("Root/") -+ ? value.Group.replace("Root/", "") -+ : value.Group; -+ const groupName = !this.isNullOrWhitespace(value.Group) ? value.Group : null; -+ this.processFolder(result, groupName); - -- if (this.organization) { -- this.moveFoldersToCollections(result); -- } -+ const cipher = this.initLoginCipher(); -+ cipher.notes = this.getValueOrDefault(value.Notes); -+ cipher.name = this.getValueOrDefault(value.Title, "--"); -+ cipher.login.username = this.getValueOrDefault(value.Username); -+ cipher.login.password = this.getValueOrDefault(value.Password); -+ cipher.login.uris = this.makeUriArray(value.URL); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -+ if (this.organization) { -+ this.moveFoldersToCollections(result); - } -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/keeperCsvImporter.ts b/jslib/common/src/importers/keeperCsvImporter.ts -deleted file mode 100644 -index e10b8f33..00000000 ---- a/jslib/common/src/importers/keeperCsvImporter.ts -+++ /dev/null -@@ -1,48 +0,0 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -- --import { ImportResult } from '../models/domain/importResult'; -- --import { FolderView } from '../models/view/folderView'; -- --export class KeeperCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, false); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -- -- results.forEach(value => { -- if (value.length < 6) { -- return; -- } -- -- this.processFolder(result, value[0]); -- const cipher = this.initLoginCipher(); -- cipher.notes = this.getValueOrDefault(value[5]) + '\n'; -- cipher.name = this.getValueOrDefault(value[1], '--'); -- cipher.login.username = this.getValueOrDefault(value[2]); -- cipher.login.password = this.getValueOrDefault(value[3]); -- cipher.login.uris = this.makeUriArray(value[4]); -- -- if (value.length > 7) { -- // we have some custom fields. -- for (let i = 7; i < value.length; i = i + 2) { -- this.processKvp(cipher, value[i], value[i + 1]); -- } -- } -- -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -- -- if (this.organization) { -- this.moveFoldersToCollections(result); -- } -- -- result.success = true; -- return Promise.resolve(result); -- } --} -diff --git a/jslib/common/src/importers/keeperImporters/keeperCsvImporter.ts b/jslib/common/src/importers/keeperImporters/keeperCsvImporter.ts -new file mode 100644 -index 00000000..56861b79 ---- /dev/null -+++ b/jslib/common/src/importers/keeperImporters/keeperCsvImporter.ts -@@ -0,0 +1,48 @@ -+import { BaseImporter } from "../baseImporter"; -+import { Importer } from "../importer"; -+ -+import { ImportResult } from "../../models/domain/importResult"; -+ -+import { FolderView } from "../../models/view/folderView"; -+ -+export class KeeperCsvImporter extends BaseImporter implements Importer { -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, false); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } -+ -+ results.forEach((value) => { -+ if (value.length < 6) { -+ return; -+ } -+ -+ this.processFolder(result, value[0]); -+ const cipher = this.initLoginCipher(); -+ cipher.notes = this.getValueOrDefault(value[5]) + "\n"; -+ cipher.name = this.getValueOrDefault(value[1], "--"); -+ cipher.login.username = this.getValueOrDefault(value[2]); -+ cipher.login.password = this.getValueOrDefault(value[3]); -+ cipher.login.uris = this.makeUriArray(value[4]); -+ -+ if (value.length > 7) { -+ // we have some custom fields. -+ for (let i = 7; i < value.length; i = i + 2) { -+ this.processKvp(cipher, value[i], value[i + 1]); -+ } -+ } -+ -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); -+ -+ if (this.organization) { -+ this.moveFoldersToCollections(result); -+ } -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } -+} -diff --git a/jslib/common/src/importers/keeperImporters/keeperJsonImporter.ts b/jslib/common/src/importers/keeperImporters/keeperJsonImporter.ts -new file mode 100644 -index 00000000..47e41114 ---- /dev/null -+++ b/jslib/common/src/importers/keeperImporters/keeperJsonImporter.ts -@@ -0,0 +1,70 @@ -+import { BaseImporter } from "../baseImporter"; -+import { Importer } from "../importer"; -+ -+import { ImportResult } from "../../models/domain/importResult"; -+ -+import { KeeperJsonExport, RecordsEntity } from "./types/keeperJsonTypes"; -+ -+export class KeeperJsonImporter extends BaseImporter implements Importer { -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const keeperExport: KeeperJsonExport = JSON.parse(data); -+ if (keeperExport == null || keeperExport.records == null || keeperExport.records.length === 0) { -+ result.success = false; -+ return Promise.resolve(result); -+ } -+ -+ keeperExport.records.forEach((record) => { -+ this.parseFolders(result, record); -+ -+ const cipher = this.initLoginCipher(); -+ cipher.name = record.title; -+ cipher.login.username = record.login; -+ cipher.login.password = record.password; -+ -+ cipher.login.uris = this.makeUriArray(record.login_url); -+ cipher.notes = record.notes; -+ -+ if (record.custom_fields != null) { -+ let customfieldKeys = Object.keys(record.custom_fields); -+ if (record.custom_fields["TFC:Keeper"] != null) { -+ customfieldKeys = customfieldKeys.filter((item) => item !== "TFC:Keeper"); -+ cipher.login.totp = record.custom_fields["TFC:Keeper"]; -+ } -+ -+ customfieldKeys.forEach((key) => { -+ this.processKvp(cipher, key, record.custom_fields[key]); -+ }); -+ } -+ -+ this.convertToNoteIfNeeded(cipher); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); -+ -+ if (this.organization) { -+ this.moveFoldersToCollections(result); -+ } -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } -+ -+ private parseFolders(result: ImportResult, record: RecordsEntity) { -+ if (record.folders == null || record.folders.length === 0) { -+ return; -+ } -+ -+ record.folders.forEach((item) => { -+ if (item.folder != null) { -+ this.processFolder(result, item.folder); -+ return; -+ } -+ -+ if (item.shared_folder != null) { -+ this.processFolder(result, item.shared_folder); -+ return; -+ } -+ }); -+ } -+} -diff --git a/jslib/common/src/importers/keeperImporters/types/keeperJsonTypes.ts b/jslib/common/src/importers/keeperImporters/types/keeperJsonTypes.ts -new file mode 100644 -index 00000000..1f6d2ea4 ---- /dev/null -+++ b/jslib/common/src/importers/keeperImporters/types/keeperJsonTypes.ts -@@ -0,0 +1,41 @@ -+export interface KeeperJsonExport { -+ shared_folders?: SharedFoldersEntity[] | null; -+ records?: RecordsEntity[] | null; -+} -+ -+export interface SharedFoldersEntity { -+ path: string; -+ manage_users: boolean; -+ manage_records: boolean; -+ can_edit: boolean; -+ can_share: boolean; -+ permissions?: PermissionsEntity[] | null; -+} -+ -+export interface PermissionsEntity { -+ uid?: string | null; -+ manage_users: boolean; -+ manage_records: boolean; -+ name?: string | null; -+} -+ -+export interface RecordsEntity { -+ title: string; -+ login: string; -+ password: string; -+ login_url: string; -+ notes?: string; -+ custom_fields?: CustomFields; -+ folders?: FoldersEntity[] | null; -+} -+ -+export type CustomFields = { -+ [key: string]: string | null; -+}; -+ -+export interface FoldersEntity { -+ folder?: string | null; -+ shared_folder?: string | null; -+ can_edit?: boolean | null; -+ can_share?: boolean | null; -+} -diff --git a/jslib/common/src/importers/lastpassCsvImporter.ts b/jslib/common/src/importers/lastpassCsvImporter.ts -index 3f8b2a92..2b8f0a4b 100644 ---- a/jslib/common/src/importers/lastpassCsvImporter.ts -+++ b/jslib/common/src/importers/lastpassCsvImporter.ts -@@ -1,276 +1,284 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - --import { CardView } from '../models/view/cardView'; --import { CipherView } from '../models/view/cipherView'; --import { FolderView } from '../models/view/folderView'; --import { IdentityView } from '../models/view/identityView'; --import { LoginView } from '../models/view/loginView'; --import { SecureNoteView } from '../models/view/secureNoteView'; -+import { CardView } from "../models/view/cardView"; -+import { CipherView } from "../models/view/cipherView"; -+import { FolderView } from "../models/view/folderView"; -+import { IdentityView } from "../models/view/identityView"; -+import { LoginView } from "../models/view/loginView"; -+import { SecureNoteView } from "../models/view/secureNoteView"; - --import { CipherType } from '../enums/cipherType'; --import { SecureNoteType } from '../enums/secureNoteType'; -+import { CipherType } from "../enums/cipherType"; -+import { SecureNoteType } from "../enums/secureNoteType"; - - export class LastPassCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- results.forEach((value, index) => { -- const cipherIndex = result.ciphers.length; -- let folderIndex = result.folders.length; -- let grouping = value.grouping; -- if (grouping != null) { -- grouping = grouping.replace(/\\/g, '/').replace(/[\x00-\x1F\x7F-\x9F]/g, ''); -- } -- const hasFolder = this.getValueOrDefault(grouping, '(none)') !== '(none)'; -- let addFolder = hasFolder; -+ results.forEach((value, index) => { -+ const cipherIndex = result.ciphers.length; -+ let folderIndex = result.folders.length; -+ let grouping = value.grouping; -+ if (grouping != null) { -+ grouping = grouping.replace(/\\/g, "/").replace(/[\x00-\x1F\x7F-\x9F]/g, ""); -+ } -+ const hasFolder = this.getValueOrDefault(grouping, "(none)") !== "(none)"; -+ let addFolder = hasFolder; - -- if (hasFolder) { -- for (let i = 0; i < result.folders.length; i++) { -- if (result.folders[i].name === grouping) { -- addFolder = false; -- folderIndex = i; -- break; -- } -- } -- } -+ if (hasFolder) { -+ for (let i = 0; i < result.folders.length; i++) { -+ if (result.folders[i].name === grouping) { -+ addFolder = false; -+ folderIndex = i; -+ break; -+ } -+ } -+ } - -- const cipher = this.buildBaseCipher(value); -- if (cipher.type === CipherType.Login) { -- cipher.notes = this.getValueOrDefault(value.extra); -- cipher.login = new LoginView(); -- cipher.login.uris = this.makeUriArray(value.url); -- cipher.login.username = this.getValueOrDefault(value.username); -- cipher.login.password = this.getValueOrDefault(value.password); -- cipher.login.totp = this.getValueOrDefault(value.totp); -- } else if (cipher.type === CipherType.SecureNote) { -- this.parseSecureNote(value, cipher); -- } else if (cipher.type === CipherType.Card) { -- cipher.card = this.parseCard(value); -- cipher.notes = this.getValueOrDefault(value.notes); -- } else if (cipher.type === CipherType.Identity) { -- cipher.identity = this.parseIdentity(value); -- cipher.notes = this.getValueOrDefault(value.notes); -- if (!this.isNullOrWhitespace(value.ccnum)) { -- // there is a card on this identity too -- const cardCipher = this.buildBaseCipher(value); -- cardCipher.identity = null; -- cardCipher.type = CipherType.Card; -- cardCipher.card = this.parseCard(value); -- result.ciphers.push(cardCipher); -- } -- } -+ const cipher = this.buildBaseCipher(value); -+ if (cipher.type === CipherType.Login) { -+ cipher.notes = this.getValueOrDefault(value.extra); -+ cipher.login = new LoginView(); -+ cipher.login.uris = this.makeUriArray(value.url); -+ cipher.login.username = this.getValueOrDefault(value.username); -+ cipher.login.password = this.getValueOrDefault(value.password); -+ cipher.login.totp = this.getValueOrDefault(value.totp); -+ } else if (cipher.type === CipherType.SecureNote) { -+ this.parseSecureNote(value, cipher); -+ } else if (cipher.type === CipherType.Card) { -+ cipher.card = this.parseCard(value); -+ cipher.notes = this.getValueOrDefault(value.notes); -+ } else if (cipher.type === CipherType.Identity) { -+ cipher.identity = this.parseIdentity(value); -+ cipher.notes = this.getValueOrDefault(value.notes); -+ if (!this.isNullOrWhitespace(value.ccnum)) { -+ // there is a card on this identity too -+ const cardCipher = this.buildBaseCipher(value); -+ cardCipher.identity = null; -+ cardCipher.type = CipherType.Card; -+ cardCipher.card = this.parseCard(value); -+ result.ciphers.push(cardCipher); -+ } -+ } - -- result.ciphers.push(cipher); -+ result.ciphers.push(cipher); - -- if (addFolder) { -- const f = new FolderView(); -- f.name = grouping; -- result.folders.push(f); -- } -- if (hasFolder) { -- result.folderRelationships.push([cipherIndex, folderIndex]); -- } -- }); -+ if (addFolder) { -+ const f = new FolderView(); -+ f.name = grouping; -+ result.folders.push(f); -+ } -+ if (hasFolder) { -+ result.folderRelationships.push([cipherIndex, folderIndex]); -+ } -+ }); - -- if (this.organization) { -- this.moveFoldersToCollections(result); -- } -- -- result.success = true; -- return Promise.resolve(result); -+ if (this.organization) { -+ this.moveFoldersToCollections(result); - } - -- private buildBaseCipher(value: any) { -- const cipher = new CipherView(); -- if (value.hasOwnProperty('profilename') && value.hasOwnProperty('profilelanguage')) { -- // form fill -- cipher.favorite = false; -- cipher.name = this.getValueOrDefault(value.profilename, '--'); -- cipher.type = CipherType.Card; -+ result.success = true; -+ return Promise.resolve(result); -+ } - -- if (!this.isNullOrWhitespace(value.title) || !this.isNullOrWhitespace(value.firstname) || -- !this.isNullOrWhitespace(value.lastname) || !this.isNullOrWhitespace(value.address1) || -- !this.isNullOrWhitespace(value.phone) || !this.isNullOrWhitespace(value.username) || -- !this.isNullOrWhitespace(value.email)) { -- cipher.type = CipherType.Identity; -- } -- } else { -- // site or secure note -- cipher.favorite = !this.organization && this.getValueOrDefault(value.fav, '0') === '1'; -- cipher.name = this.getValueOrDefault(value.name, '--'); -- cipher.type = value.url === 'http://sn' ? CipherType.SecureNote : CipherType.Login; -- } -- return cipher; -+ private buildBaseCipher(value: any) { -+ const cipher = new CipherView(); -+ if (value.hasOwnProperty("profilename") && value.hasOwnProperty("profilelanguage")) { -+ // form fill -+ cipher.favorite = false; -+ cipher.name = this.getValueOrDefault(value.profilename, "--"); -+ cipher.type = CipherType.Card; -+ -+ if ( -+ !this.isNullOrWhitespace(value.title) || -+ !this.isNullOrWhitespace(value.firstname) || -+ !this.isNullOrWhitespace(value.lastname) || -+ !this.isNullOrWhitespace(value.address1) || -+ !this.isNullOrWhitespace(value.phone) || -+ !this.isNullOrWhitespace(value.username) || -+ !this.isNullOrWhitespace(value.email) -+ ) { -+ cipher.type = CipherType.Identity; -+ } -+ } else { -+ // site or secure note -+ cipher.favorite = !this.organization && this.getValueOrDefault(value.fav, "0") === "1"; -+ cipher.name = this.getValueOrDefault(value.name, "--"); -+ cipher.type = value.url === "http://sn" ? CipherType.SecureNote : CipherType.Login; - } -+ return cipher; -+ } - -- private parseCard(value: any): CardView { -- const card = new CardView(); -- card.cardholderName = this.getValueOrDefault(value.ccname); -- card.number = this.getValueOrDefault(value.ccnum); -- card.code = this.getValueOrDefault(value.cccsc); -- card.brand = this.getCardBrand(value.ccnum); -+ private parseCard(value: any): CardView { -+ const card = new CardView(); -+ card.cardholderName = this.getValueOrDefault(value.ccname); -+ card.number = this.getValueOrDefault(value.ccnum); -+ card.code = this.getValueOrDefault(value.cccsc); -+ card.brand = this.getCardBrand(value.ccnum); - -- if (!this.isNullOrWhitespace(value.ccexp) && value.ccexp.indexOf('-') > -1) { -- const ccexpParts = (value.ccexp as string).split('-'); -- if (ccexpParts.length > 1) { -- card.expYear = ccexpParts[0]; -- card.expMonth = ccexpParts[1]; -- if (card.expMonth.length === 2 && card.expMonth[0] === '0') { -- card.expMonth = card.expMonth[1]; -- } -- } -+ if (!this.isNullOrWhitespace(value.ccexp) && value.ccexp.indexOf("-") > -1) { -+ const ccexpParts = (value.ccexp as string).split("-"); -+ if (ccexpParts.length > 1) { -+ card.expYear = ccexpParts[0]; -+ card.expMonth = ccexpParts[1]; -+ if (card.expMonth.length === 2 && card.expMonth[0] === "0") { -+ card.expMonth = card.expMonth[1]; - } -- -- return card; -+ } - } - -- private parseIdentity(value: any): IdentityView { -- const identity = new IdentityView(); -- identity.title = this.getValueOrDefault(value.title); -- identity.firstName = this.getValueOrDefault(value.firstname); -- identity.middleName = this.getValueOrDefault(value.middlename); -- identity.lastName = this.getValueOrDefault(value.lastname); -- identity.username = this.getValueOrDefault(value.username); -- identity.company = this.getValueOrDefault(value.company); -- identity.ssn = this.getValueOrDefault(value.ssn); -- identity.address1 = this.getValueOrDefault(value.address1); -- identity.address2 = this.getValueOrDefault(value.address2); -- identity.address3 = this.getValueOrDefault(value.address3); -- identity.city = this.getValueOrDefault(value.city); -- identity.state = this.getValueOrDefault(value.state); -- identity.postalCode = this.getValueOrDefault(value.zip); -- identity.country = this.getValueOrDefault(value.country); -- identity.email = this.getValueOrDefault(value.email); -- identity.phone = this.getValueOrDefault(value.phone); -+ return card; -+ } - -- if (!this.isNullOrWhitespace(identity.title)) { -- identity.title = identity.title.charAt(0).toUpperCase() + identity.title.slice(1); -- } -+ private parseIdentity(value: any): IdentityView { -+ const identity = new IdentityView(); -+ identity.title = this.getValueOrDefault(value.title); -+ identity.firstName = this.getValueOrDefault(value.firstname); -+ identity.middleName = this.getValueOrDefault(value.middlename); -+ identity.lastName = this.getValueOrDefault(value.lastname); -+ identity.username = this.getValueOrDefault(value.username); -+ identity.company = this.getValueOrDefault(value.company); -+ identity.ssn = this.getValueOrDefault(value.ssn); -+ identity.address1 = this.getValueOrDefault(value.address1); -+ identity.address2 = this.getValueOrDefault(value.address2); -+ identity.address3 = this.getValueOrDefault(value.address3); -+ identity.city = this.getValueOrDefault(value.city); -+ identity.state = this.getValueOrDefault(value.state); -+ identity.postalCode = this.getValueOrDefault(value.zip); -+ identity.country = this.getValueOrDefault(value.country); -+ identity.email = this.getValueOrDefault(value.email); -+ identity.phone = this.getValueOrDefault(value.phone); - -- return identity; -+ if (!this.isNullOrWhitespace(identity.title)) { -+ identity.title = identity.title.charAt(0).toUpperCase() + identity.title.slice(1); - } - -- private parseSecureNote(value: any, cipher: CipherView) { -- const extraParts = this.splitNewLine(value.extra); -- let processedNote = false; -+ return identity; -+ } - -- if (extraParts.length) { -- const typeParts = extraParts[0].split(':'); -- if (typeParts.length > 1 && typeParts[0] === 'NoteType' && -- (typeParts[1] === 'Credit Card' || typeParts[1] === 'Address')) { -- if (typeParts[1] === 'Credit Card') { -- const mappedData = this.parseSecureNoteMapping(cipher, extraParts, { -- 'Number': 'number', -- 'Name on Card': 'cardholderName', -- 'Security Code': 'code', -- // LP provides date in a format like 'June,2020' -- // Store in expMonth, then parse and modify -- 'Expiration Date': 'expMonth', -- }); -+ private parseSecureNote(value: any, cipher: CipherView) { -+ const extraParts = this.splitNewLine(value.extra); -+ let processedNote = false; - -- if (this.isNullOrWhitespace(mappedData.expMonth) || mappedData.expMonth === ',') { -- // No expiration data -- mappedData.expMonth = undefined; -- } else { -- const [monthString, year] = mappedData.expMonth.split(','); -- // Parse month name into number -- if (!this.isNullOrWhitespace(monthString)) { -- const month = new Date(Date.parse(monthString.trim() + ' 1, 2012')).getMonth() + 1; -- if (isNaN(month)) { -- mappedData.expMonth = undefined; -- } else { -- mappedData.expMonth = month.toString(); -- } -- } else { -- mappedData.expMonth = undefined; -- } -- if (!this.isNullOrWhitespace(year)) { -- mappedData.expYear = year; -- } -- } -+ if (extraParts.length) { -+ const typeParts = extraParts[0].split(":"); -+ if ( -+ typeParts.length > 1 && -+ typeParts[0] === "NoteType" && -+ (typeParts[1] === "Credit Card" || typeParts[1] === "Address") -+ ) { -+ if (typeParts[1] === "Credit Card") { -+ const mappedData = this.parseSecureNoteMapping(cipher, extraParts, { -+ Number: "number", -+ "Name on Card": "cardholderName", -+ "Security Code": "code", -+ // LP provides date in a format like 'June,2020' -+ // Store in expMonth, then parse and modify -+ "Expiration Date": "expMonth", -+ }); - -- cipher.type = CipherType.Card; -- cipher.card = mappedData; -- } else if (typeParts[1] === 'Address') { -- const mappedData = this.parseSecureNoteMapping(cipher, extraParts, { -- 'Title': 'title', -- 'First Name': 'firstName', -- 'Last Name': 'lastName', -- 'Middle Name': 'middleName', -- 'Company': 'company', -- 'Address 1': 'address1', -- 'Address 2': 'address2', -- 'Address 3': 'address3', -- 'City / Town': 'city', -- 'State': 'state', -- 'Zip / Postal Code': 'postalCode', -- 'Country': 'country', -- 'Email Address': 'email', -- 'Username': 'username', -- }); -- cipher.type = CipherType.Identity; -- cipher.identity = mappedData; -- } -- processedNote = true; -+ if (this.isNullOrWhitespace(mappedData.expMonth) || mappedData.expMonth === ",") { -+ // No expiration data -+ mappedData.expMonth = undefined; -+ } else { -+ const [monthString, year] = mappedData.expMonth.split(","); -+ // Parse month name into number -+ if (!this.isNullOrWhitespace(monthString)) { -+ const month = new Date(Date.parse(monthString.trim() + " 1, 2012")).getMonth() + 1; -+ if (isNaN(month)) { -+ mappedData.expMonth = undefined; -+ } else { -+ mappedData.expMonth = month.toString(); -+ } -+ } else { -+ mappedData.expMonth = undefined; - } -- } -+ if (!this.isNullOrWhitespace(year)) { -+ mappedData.expYear = year; -+ } -+ } - -- if (!processedNote) { -- cipher.secureNote = new SecureNoteView(); -- cipher.secureNote.type = SecureNoteType.Generic; -- cipher.notes = this.getValueOrDefault(value.extra); -+ cipher.type = CipherType.Card; -+ cipher.card = mappedData; -+ } else if (typeParts[1] === "Address") { -+ const mappedData = this.parseSecureNoteMapping(cipher, extraParts, { -+ Title: "title", -+ "First Name": "firstName", -+ "Last Name": "lastName", -+ "Middle Name": "middleName", -+ Company: "company", -+ "Address 1": "address1", -+ "Address 2": "address2", -+ "Address 3": "address3", -+ "City / Town": "city", -+ State: "state", -+ "Zip / Postal Code": "postalCode", -+ Country: "country", -+ "Email Address": "email", -+ Username: "username", -+ }); -+ cipher.type = CipherType.Identity; -+ cipher.identity = mappedData; - } -+ processedNote = true; -+ } - } - -- private parseSecureNoteMapping(cipher: CipherView, extraParts: string[], map: any): T { -- const dataObj: any = {}; -+ if (!processedNote) { -+ cipher.secureNote = new SecureNoteView(); -+ cipher.secureNote.type = SecureNoteType.Generic; -+ cipher.notes = this.getValueOrDefault(value.extra); -+ } -+ } - -- let processingNotes = false; -- extraParts.forEach(extraPart => { -- let key: string = null; -- let val: string = null; -- if (!processingNotes) { -- if (this.isNullOrWhitespace(extraPart)) { -- return; -- } -- const colonIndex = extraPart.indexOf(':'); -- if (colonIndex === -1) { -- key = extraPart; -- } else { -- key = extraPart.substring(0, colonIndex); -- if (extraPart.length > colonIndex) { -- val = extraPart.substring(colonIndex + 1); -- } -- } -- if (this.isNullOrWhitespace(key) || this.isNullOrWhitespace(val) || key === 'NoteType') { -- return; -- } -- } -+ private parseSecureNoteMapping(cipher: CipherView, extraParts: string[], map: any): T { -+ const dataObj: any = {}; - -- if (processingNotes) { -- cipher.notes += ('\n' + extraPart); -- } else if (key === 'Notes') { -- if (!this.isNullOrWhitespace(cipher.notes)) { -- cipher.notes += ('\n' + val); -- } else { -- cipher.notes = val; -- } -- processingNotes = true; -- } else if (map.hasOwnProperty(key)) { -- dataObj[map[key]] = val; -- } else { -- this.processKvp(cipher, key, val); -- } -- }); -+ let processingNotes = false; -+ extraParts.forEach((extraPart) => { -+ let key: string = null; -+ let val: string = null; -+ if (!processingNotes) { -+ if (this.isNullOrWhitespace(extraPart)) { -+ return; -+ } -+ const colonIndex = extraPart.indexOf(":"); -+ if (colonIndex === -1) { -+ key = extraPart; -+ } else { -+ key = extraPart.substring(0, colonIndex); -+ if (extraPart.length > colonIndex) { -+ val = extraPart.substring(colonIndex + 1); -+ } -+ } -+ if (this.isNullOrWhitespace(key) || this.isNullOrWhitespace(val) || key === "NoteType") { -+ return; -+ } -+ } - -- return dataObj; -- } -+ if (processingNotes) { -+ cipher.notes += "\n" + extraPart; -+ } else if (key === "Notes") { -+ if (!this.isNullOrWhitespace(cipher.notes)) { -+ cipher.notes += "\n" + val; -+ } else { -+ cipher.notes = val; -+ } -+ processingNotes = true; -+ } else if (map.hasOwnProperty(key)) { -+ dataObj[map[key]] = val; -+ } else { -+ this.processKvp(cipher, key, val); -+ } -+ }); -+ -+ return dataObj; -+ } - } -diff --git a/jslib/common/src/importers/logMeOnceCsvImporter.ts b/jslib/common/src/importers/logMeOnceCsvImporter.ts -index f6e3c5db..611058a4 100644 ---- a/jslib/common/src/importers/logMeOnceCsvImporter.ts -+++ b/jslib/common/src/importers/logMeOnceCsvImporter.ts -@@ -1,31 +1,31 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class LogMeOnceCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, false); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, false); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- results.forEach(value => { -- if (value.length < 4) { -- return; -- } -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value[0], '--'); -- cipher.login.username = this.getValueOrDefault(value[2]); -- cipher.login.password = this.getValueOrDefault(value[3]); -- cipher.login.uris = this.makeUriArray(value[1]); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ results.forEach((value) => { -+ if (value.length < 4) { -+ return; -+ } -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(value[0], "--"); -+ cipher.login.username = this.getValueOrDefault(value[2]); -+ cipher.login.password = this.getValueOrDefault(value[3]); -+ cipher.login.uris = this.makeUriArray(value[1]); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -- } -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/meldiumCsvImporter.ts b/jslib/common/src/importers/meldiumCsvImporter.ts -index 0b7ed11d..89d2e0bd 100644 ---- a/jslib/common/src/importers/meldiumCsvImporter.ts -+++ b/jslib/common/src/importers/meldiumCsvImporter.ts -@@ -1,29 +1,29 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class MeldiumCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- results.forEach(value => { -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value.DisplayName, '--'); -- cipher.notes = this.getValueOrDefault(value.Notes); -- cipher.login.username = this.getValueOrDefault(value.UserName); -- cipher.login.password = this.getValueOrDefault(value.Password); -- cipher.login.uris = this.makeUriArray(value.Url); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ results.forEach((value) => { -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(value.DisplayName, "--"); -+ cipher.notes = this.getValueOrDefault(value.Notes); -+ cipher.login.username = this.getValueOrDefault(value.UserName); -+ cipher.login.password = this.getValueOrDefault(value.Password); -+ cipher.login.uris = this.makeUriArray(value.Url); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -- } -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/msecureCsvImporter.ts b/jslib/common/src/importers/msecureCsvImporter.ts -index 248b5380..4ae3ca90 100644 ---- a/jslib/common/src/importers/msecureCsvImporter.ts -+++ b/jslib/common/src/importers/msecureCsvImporter.ts -@@ -1,62 +1,63 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - --import { CipherType } from '../enums/cipherType'; --import { SecureNoteType } from '../enums/secureNoteType'; -+import { CipherType } from "../enums/cipherType"; -+import { SecureNoteType } from "../enums/secureNoteType"; - --import { SecureNoteView } from '../models/view/secureNoteView'; -+import { SecureNoteView } from "../models/view/secureNoteView"; - - export class MSecureCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, false); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, false); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- results.forEach(value => { -- if (value.length < 3) { -- return; -- } -- -- const folderName = this.getValueOrDefault(value[0], 'Unassigned') !== 'Unassigned' ? value[0] : null; -- this.processFolder(result, folderName); -- -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value[2], '--'); -- -- if (value[1] === 'Web Logins' || value[1] === 'Login') { -- cipher.login.uris = this.makeUriArray(value[4]); -- cipher.login.username = this.getValueOrDefault(value[5]); -- cipher.login.password = this.getValueOrDefault(value[6]); -- cipher.notes = !this.isNullOrWhitespace(value[3]) ? value[3].split('\\n').join('\n') : null; -- } else if (value.length > 3) { -- cipher.type = CipherType.SecureNote; -- cipher.secureNote = new SecureNoteView(); -- cipher.secureNote.type = SecureNoteType.Generic; -- for (let i = 3; i < value.length; i++) { -- if (!this.isNullOrWhitespace(value[i])) { -- cipher.notes += (value[i] + '\n'); -- } -- } -- } -- -- if (!this.isNullOrWhitespace(value[1]) && cipher.type !== CipherType.Login) { -- cipher.name = value[1] + ': ' + cipher.name; -- } -- -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -- -- if (this.organization) { -- this.moveFoldersToCollections(result); -+ results.forEach((value) => { -+ if (value.length < 3) { -+ return; -+ } -+ -+ const folderName = -+ this.getValueOrDefault(value[0], "Unassigned") !== "Unassigned" ? value[0] : null; -+ this.processFolder(result, folderName); -+ -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(value[2], "--"); -+ -+ if (value[1] === "Web Logins" || value[1] === "Login") { -+ cipher.login.uris = this.makeUriArray(value[4]); -+ cipher.login.username = this.getValueOrDefault(value[5]); -+ cipher.login.password = this.getValueOrDefault(value[6]); -+ cipher.notes = !this.isNullOrWhitespace(value[3]) ? value[3].split("\\n").join("\n") : null; -+ } else if (value.length > 3) { -+ cipher.type = CipherType.SecureNote; -+ cipher.secureNote = new SecureNoteView(); -+ cipher.secureNote.type = SecureNoteType.Generic; -+ for (let i = 3; i < value.length; i++) { -+ if (!this.isNullOrWhitespace(value[i])) { -+ cipher.notes += value[i] + "\n"; -+ } - } -+ } -+ -+ if (!this.isNullOrWhitespace(value[1]) && cipher.type !== CipherType.Login) { -+ cipher.name = value[1] + ": " + cipher.name; -+ } - -- result.success = true; -- return Promise.resolve(result); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); -+ -+ if (this.organization) { -+ this.moveFoldersToCollections(result); - } -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/mykiCsvImporter.ts b/jslib/common/src/importers/mykiCsvImporter.ts -index e206ae1a..da440ad6 100644 ---- a/jslib/common/src/importers/mykiCsvImporter.ts -+++ b/jslib/common/src/importers/mykiCsvImporter.ts -@@ -1,76 +1,76 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { CipherType } from '../enums/cipherType'; --import { SecureNoteType } from '../enums/secureNoteType'; -+import { CipherType } from "../enums/cipherType"; -+import { SecureNoteType } from "../enums/secureNoteType"; - --import { CardView } from '../models/view/cardView'; --import { IdentityView } from '../models/view/identityView'; --import { SecureNoteView } from '../models/view/secureNoteView'; -+import { CardView } from "../models/view/cardView"; -+import { IdentityView } from "../models/view/identityView"; -+import { SecureNoteView } from "../models/view/secureNoteView"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class MykiCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- results.forEach(value => { -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value.nickname, '--'); -- cipher.notes = this.getValueOrDefault(value.additionalInfo); -+ results.forEach((value) => { -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(value.nickname, "--"); -+ cipher.notes = this.getValueOrDefault(value.additionalInfo); - -- if (value.url !== undefined) { -- // Accounts -- cipher.login.uris = this.makeUriArray(value.url); -- cipher.login.username = this.getValueOrDefault(value.username); -- cipher.login.password = this.getValueOrDefault(value.password); -- cipher.login.totp = this.getValueOrDefault(value.twoFactAuthToken); -- } else if (value.cardNumber !== undefined) { -- // Cards -- cipher.card = new CardView(); -- cipher.type = CipherType.Card; -- cipher.card.cardholderName = this.getValueOrDefault(value.cardName); -- cipher.card.number = this.getValueOrDefault(value.cardNumber); -- cipher.card.brand = this.getCardBrand(cipher.card.number); -- cipher.card.expMonth = this.getValueOrDefault(value.exp_month); -- cipher.card.expYear = this.getValueOrDefault(value.exp_year); -- cipher.card.code = this.getValueOrDefault(value.cvv); -- } else if (value.firstName !== undefined) { -- // Identities -- cipher.identity = new IdentityView(); -- cipher.type = CipherType.Identity; -- cipher.identity.title = this.getValueOrDefault(value.title); -- cipher.identity.firstName = this.getValueOrDefault(value.firstName); -- cipher.identity.middleName = this.getValueOrDefault(value.middleName); -- cipher.identity.lastName = this.getValueOrDefault(value.lastName); -- cipher.identity.phone = this.getValueOrDefault(value.number); -- cipher.identity.email = this.getValueOrDefault(value.email); -- cipher.identity.address1 = this.getValueOrDefault(value.firstAddressLine); -- cipher.identity.address2 = this.getValueOrDefault(value.secondAddressLine); -- cipher.identity.city = this.getValueOrDefault(value.city); -- cipher.identity.country = this.getValueOrDefault(value.country); -- cipher.identity.postalCode = this.getValueOrDefault(value.zipCode); -- } else if (value.content !== undefined) { -- // Notes -- cipher.secureNote = new SecureNoteView(); -- cipher.type = CipherType.SecureNote; -- cipher.secureNote.type = SecureNoteType.Generic; -- cipher.name = this.getValueOrDefault(value.title, '--'); -- cipher.notes = this.getValueOrDefault(value.content); -- } else { -- return; -- } -+ if (value.url !== undefined) { -+ // Accounts -+ cipher.login.uris = this.makeUriArray(value.url); -+ cipher.login.username = this.getValueOrDefault(value.username); -+ cipher.login.password = this.getValueOrDefault(value.password); -+ cipher.login.totp = this.getValueOrDefault(value.twoFactAuthToken); -+ } else if (value.cardNumber !== undefined) { -+ // Cards -+ cipher.card = new CardView(); -+ cipher.type = CipherType.Card; -+ cipher.card.cardholderName = this.getValueOrDefault(value.cardName); -+ cipher.card.number = this.getValueOrDefault(value.cardNumber); -+ cipher.card.brand = this.getCardBrand(cipher.card.number); -+ cipher.card.expMonth = this.getValueOrDefault(value.exp_month); -+ cipher.card.expYear = this.getValueOrDefault(value.exp_year); -+ cipher.card.code = this.getValueOrDefault(value.cvv); -+ } else if (value.firstName !== undefined) { -+ // Identities -+ cipher.identity = new IdentityView(); -+ cipher.type = CipherType.Identity; -+ cipher.identity.title = this.getValueOrDefault(value.title); -+ cipher.identity.firstName = this.getValueOrDefault(value.firstName); -+ cipher.identity.middleName = this.getValueOrDefault(value.middleName); -+ cipher.identity.lastName = this.getValueOrDefault(value.lastName); -+ cipher.identity.phone = this.getValueOrDefault(value.number); -+ cipher.identity.email = this.getValueOrDefault(value.email); -+ cipher.identity.address1 = this.getValueOrDefault(value.firstAddressLine); -+ cipher.identity.address2 = this.getValueOrDefault(value.secondAddressLine); -+ cipher.identity.city = this.getValueOrDefault(value.city); -+ cipher.identity.country = this.getValueOrDefault(value.country); -+ cipher.identity.postalCode = this.getValueOrDefault(value.zipCode); -+ } else if (value.content !== undefined) { -+ // Notes -+ cipher.secureNote = new SecureNoteView(); -+ cipher.type = CipherType.SecureNote; -+ cipher.secureNote.type = SecureNoteType.Generic; -+ cipher.name = this.getValueOrDefault(value.title, "--"); -+ cipher.notes = this.getValueOrDefault(value.content); -+ } else { -+ return; -+ } - -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -- } -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/nordpassCsvImporter.ts b/jslib/common/src/importers/nordpassCsvImporter.ts -index 39ef4475..45e97edf 100644 ---- a/jslib/common/src/importers/nordpassCsvImporter.ts -+++ b/jslib/common/src/importers/nordpassCsvImporter.ts -@@ -1,149 +1,146 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - --import { CipherView } from '../models/view/cipherView'; --import { LoginView } from '../models/view/loginView'; -+import { CipherView } from "../models/view/cipherView"; -+import { LoginView } from "../models/view/loginView"; - --import { CipherType } from '../enums/cipherType'; --import { SecureNoteType } from '../enums/secureNoteType'; -+import { CipherType } from "../enums/cipherType"; -+import { SecureNoteType } from "../enums/secureNoteType"; - - type nodePassCsvParsed = { -- name: string; -- url: string; -- username: string; -- password: string; -- note: string; -- cardholdername: string; -- cardnumber: string; -- cvc: string; -- expirydate: string; -- zipcode: string; -- folder: string; -- full_name: string; -- phone_number: string; -- email: string; -- address1: string; -- address2: string; -- city: string; -- country: string; -- state: string; -+ name: string; -+ url: string; -+ username: string; -+ password: string; -+ note: string; -+ cardholdername: string; -+ cardnumber: string; -+ cvc: string; -+ expirydate: string; -+ zipcode: string; -+ folder: string; -+ full_name: string; -+ phone_number: string; -+ email: string; -+ address1: string; -+ address2: string; -+ city: string; -+ country: string; -+ state: string; - }; - - export class NordPassCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results: nodePassCsvParsed[] = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -- -- results.forEach(record => { -- -- const recordType = this.evaluateType(record); -- if (recordType === undefined) { -- return; -- } -- -- if (!this.organization) { -- this.processFolder(result, record.folder); -- } -- -- const cipher = new CipherView(); -- cipher.name = this.getValueOrDefault(record.name, '--'); -- cipher.notes = this.getValueOrDefault(record.note); -- -- switch (recordType) { -- case CipherType.Login: -- cipher.type = CipherType.Login; -- cipher.login = new LoginView(); -- cipher.login.username = this.getValueOrDefault(record.username); -- cipher.login.password = this.getValueOrDefault(record.password); -- cipher.login.uris = this.makeUriArray(record.url); -- break; -- case CipherType.Card: -- cipher.type = CipherType.Card; -- cipher.card.cardholderName = this.getValueOrDefault(record.cardholdername); -- cipher.card.number = this.getValueOrDefault(record.cardnumber); -- cipher.card.code = this.getValueOrDefault(record.cvc); -- cipher.card.brand = this.getCardBrand(cipher.card.number); -- this.setCardExpiration(cipher, record.expirydate); -- break; -- -- case CipherType.Identity: -- cipher.type = CipherType.Identity; -- -- this.processName(cipher, this.getValueOrDefault(record.full_name)); -- cipher.identity.address1 = this.getValueOrDefault(record.address1); -- cipher.identity.address2 = this.getValueOrDefault(record.address2); -- cipher.identity.city = this.getValueOrDefault(record.city); -- cipher.identity.state = this.getValueOrDefault(record.state); -- cipher.identity.postalCode = this.getValueOrDefault(record.zipcode); -- cipher.identity.country = this.getValueOrDefault(record.country); -- if (cipher.identity.country != null) { -- cipher.identity.country = cipher.identity.country.toUpperCase(); -- } -- cipher.identity.email = this.getValueOrDefault(record.email); -- cipher.identity.phone = this.getValueOrDefault(record.phone_number); -- break; -- case CipherType.SecureNote: -- cipher.type = CipherType.SecureNote; -- cipher.secureNote.type = SecureNoteType.Generic; -- break; -- default: -- break; -- } -- -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -- -- if (this.organization) { -- this.moveFoldersToCollections(result); -- } -- -- result.success = true; -- return Promise.resolve(result); -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results: nodePassCsvParsed[] = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); - } - -- private evaluateType(record: nodePassCsvParsed): CipherType { -+ results.forEach((record) => { -+ const recordType = this.evaluateType(record); -+ if (recordType === undefined) { -+ return; -+ } -+ -+ if (!this.organization) { -+ this.processFolder(result, record.folder); -+ } -+ -+ const cipher = new CipherView(); -+ cipher.name = this.getValueOrDefault(record.name, "--"); -+ cipher.notes = this.getValueOrDefault(record.note); -+ -+ switch (recordType) { -+ case CipherType.Login: -+ cipher.type = CipherType.Login; -+ cipher.login = new LoginView(); -+ cipher.login.username = this.getValueOrDefault(record.username); -+ cipher.login.password = this.getValueOrDefault(record.password); -+ cipher.login.uris = this.makeUriArray(record.url); -+ break; -+ case CipherType.Card: -+ cipher.type = CipherType.Card; -+ cipher.card.cardholderName = this.getValueOrDefault(record.cardholdername); -+ cipher.card.number = this.getValueOrDefault(record.cardnumber); -+ cipher.card.code = this.getValueOrDefault(record.cvc); -+ cipher.card.brand = this.getCardBrand(cipher.card.number); -+ this.setCardExpiration(cipher, record.expirydate); -+ break; -+ -+ case CipherType.Identity: -+ cipher.type = CipherType.Identity; -+ -+ this.processName(cipher, this.getValueOrDefault(record.full_name)); -+ cipher.identity.address1 = this.getValueOrDefault(record.address1); -+ cipher.identity.address2 = this.getValueOrDefault(record.address2); -+ cipher.identity.city = this.getValueOrDefault(record.city); -+ cipher.identity.state = this.getValueOrDefault(record.state); -+ cipher.identity.postalCode = this.getValueOrDefault(record.zipcode); -+ cipher.identity.country = this.getValueOrDefault(record.country); -+ if (cipher.identity.country != null) { -+ cipher.identity.country = cipher.identity.country.toUpperCase(); -+ } -+ cipher.identity.email = this.getValueOrDefault(record.email); -+ cipher.identity.phone = this.getValueOrDefault(record.phone_number); -+ break; -+ case CipherType.SecureNote: -+ cipher.type = CipherType.SecureNote; -+ cipher.secureNote.type = SecureNoteType.Generic; -+ break; -+ default: -+ break; -+ } -+ -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); -+ -+ if (this.organization) { -+ this.moveFoldersToCollections(result); -+ } -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } -+ -+ private evaluateType(record: nodePassCsvParsed): CipherType { -+ if (!this.isNullOrWhitespace(record.username)) { -+ return CipherType.Login; -+ } - -- if (!this.isNullOrWhitespace(record.username)) { -- return CipherType.Login; -- } -+ if (!this.isNullOrWhitespace(record.cardnumber)) { -+ return CipherType.Card; -+ } - -- if (!this.isNullOrWhitespace(record.cardnumber)) { -- return CipherType.Card; -- } -+ if (!this.isNullOrWhitespace(record.full_name)) { -+ return CipherType.Identity; -+ } - -- if (!this.isNullOrWhitespace(record.full_name)) { -- return CipherType.Identity; -- } -+ if (!this.isNullOrWhitespace(record.note)) { -+ return CipherType.SecureNote; -+ } - -- if (!this.isNullOrWhitespace(record.note)) { -- return CipherType.SecureNote; -- } -+ return undefined; -+ } - -- return undefined; -+ private processName(cipher: CipherView, fullName: string) { -+ if (this.isNullOrWhitespace(fullName)) { -+ return; - } - -- private processName(cipher: CipherView, fullName: string) { -- -- if (this.isNullOrWhitespace(fullName)) { -- return; -- } -- -- const nameParts = fullName.split(' '); -- if (nameParts.length > 0) { -- cipher.identity.firstName = this.getValueOrDefault(nameParts[0]); -- } -- if (nameParts.length === 2) { -- cipher.identity.lastName = this.getValueOrDefault(nameParts[1]); -- } else if (nameParts.length >= 3) { -- cipher.identity.middleName = this.getValueOrDefault(nameParts[1]); -- cipher.identity.lastName = nameParts.slice(2, nameParts.length).join(' '); -- } -+ const nameParts = fullName.split(" "); -+ if (nameParts.length > 0) { -+ cipher.identity.firstName = this.getValueOrDefault(nameParts[0]); -+ } -+ if (nameParts.length === 2) { -+ cipher.identity.lastName = this.getValueOrDefault(nameParts[1]); -+ } else if (nameParts.length >= 3) { -+ cipher.identity.middleName = this.getValueOrDefault(nameParts[1]); -+ cipher.identity.lastName = nameParts.slice(2, nameParts.length).join(" "); - } -+ } - } -diff --git a/jslib/common/src/importers/onepasswordImporters/cipherImportContext.ts b/jslib/common/src/importers/onepasswordImporters/cipherImportContext.ts -index 66d82231..560f5c01 100644 ---- a/jslib/common/src/importers/onepasswordImporters/cipherImportContext.ts -+++ b/jslib/common/src/importers/onepasswordImporters/cipherImportContext.ts -@@ -1,8 +1,8 @@ --import { CipherView } from '../../models/view/cipherView'; -+import { CipherView } from "../../models/view/cipherView"; - - export class CipherImportContext { -- lowerProperty: string; -- constructor(public importRecord: any, public property: string, public cipher: CipherView) { -- this.lowerProperty = property.toLowerCase(); -- } -+ lowerProperty: string; -+ constructor(public importRecord: any, public property: string, public cipher: CipherView) { -+ this.lowerProperty = property.toLowerCase(); -+ } - } -diff --git a/jslib/common/src/importers/onepasswordImporters/onepassword1PifImporter.ts b/jslib/common/src/importers/onepasswordImporters/onepassword1PifImporter.ts -index e82f3185..fd202ba5 100644 ---- a/jslib/common/src/importers/onepasswordImporters/onepassword1PifImporter.ts -+++ b/jslib/common/src/importers/onepasswordImporters/onepassword1PifImporter.ts -@@ -1,248 +1,279 @@ --import { BaseImporter } from '../baseImporter'; --import { Importer } from '../importer'; -+import { BaseImporter } from "../baseImporter"; -+import { Importer } from "../importer"; - --import { ImportResult } from '../../models/domain/importResult'; -+import { ImportResult } from "../../models/domain/importResult"; - --import { CardView } from '../../models/view/cardView'; --import { CipherView } from '../../models/view/cipherView'; --import { IdentityView } from '../../models/view/identityView'; --import { PasswordHistoryView } from '../../models/view/passwordHistoryView'; --import { SecureNoteView } from '../../models/view/secureNoteView'; -+import { CardView } from "../../models/view/cardView"; -+import { CipherView } from "../../models/view/cipherView"; -+import { IdentityView } from "../../models/view/identityView"; -+import { PasswordHistoryView } from "../../models/view/passwordHistoryView"; -+import { SecureNoteView } from "../../models/view/secureNoteView"; - --import { CipherType } from '../../enums/cipherType'; --import { FieldType } from '../../enums/fieldType'; --import { SecureNoteType } from '../../enums/secureNoteType'; -+import { CipherType } from "../../enums/cipherType"; -+import { FieldType } from "../../enums/fieldType"; -+import { SecureNoteType } from "../../enums/secureNoteType"; - - export class OnePassword1PifImporter extends BaseImporter implements Importer { -- result = new ImportResult(); -+ result = new ImportResult(); - -- parse(data: string): Promise { -- data.split(this.newLineRegex).forEach(line => { -- if (this.isNullOrWhitespace(line) || line[0] !== '{') { -- return; -- } -- const item = JSON.parse(line); -- if (item.trashed === true) { -- return; -- } -- const cipher = this.initLoginCipher(); -+ parse(data: string): Promise { -+ data.split(this.newLineRegex).forEach((line) => { -+ if (this.isNullOrWhitespace(line) || line[0] !== "{") { -+ return; -+ } -+ const item = JSON.parse(line); -+ if (item.trashed === true) { -+ return; -+ } -+ const cipher = this.initLoginCipher(); - -- if (this.isNullOrWhitespace(item.hmac)) { -- this.processStandardItem(item, cipher); -- } else { -- this.processWinOpVaultItem(item, cipher); -- } -+ if (this.isNullOrWhitespace(item.hmac)) { -+ this.processStandardItem(item, cipher); -+ } else { -+ this.processWinOpVaultItem(item, cipher); -+ } -+ -+ this.convertToNoteIfNeeded(cipher); -+ this.cleanupCipher(cipher); -+ this.result.ciphers.push(cipher); -+ }); - -- this.convertToNoteIfNeeded(cipher); -- this.cleanupCipher(cipher); -- this.result.ciphers.push(cipher); -+ this.result.success = true; -+ return Promise.resolve(this.result); -+ } -+ -+ private processWinOpVaultItem(item: any, cipher: CipherView) { -+ if (item.overview != null) { -+ cipher.name = this.getValueOrDefault(item.overview.title); -+ if (item.overview.URLs != null) { -+ const urls: string[] = []; -+ item.overview.URLs.forEach((url: any) => { -+ if (!this.isNullOrWhitespace(url.u)) { -+ urls.push(url.u); -+ } - }); -+ cipher.login.uris = this.makeUriArray(urls); -+ } -+ } - -- this.result.success = true; -- return Promise.resolve(this.result); -+ if (item.details != null) { -+ if (item.details.passwordHistory != null) { -+ this.parsePasswordHistory(item.details.passwordHistory, cipher); -+ } -+ if ( -+ !this.isNullOrWhitespace(item.details.ccnum) || -+ !this.isNullOrWhitespace(item.details.cvv) -+ ) { -+ cipher.type = CipherType.Card; -+ cipher.card = new CardView(); -+ } else if ( -+ !this.isNullOrWhitespace(item.details.firstname) || -+ !this.isNullOrWhitespace(item.details.address1) -+ ) { -+ cipher.type = CipherType.Identity; -+ cipher.identity = new IdentityView(); -+ } -+ if (cipher.type === CipherType.Login && !this.isNullOrWhitespace(item.details.password)) { -+ cipher.login.password = item.details.password; -+ } -+ if (!this.isNullOrWhitespace(item.details.notesPlain)) { -+ cipher.notes = item.details.notesPlain.split(this.newLineRegex).join("\n") + "\n"; -+ } -+ if (item.details.fields != null) { -+ this.parseFields(item.details.fields, cipher, "designation", "value", "name"); -+ } -+ if (item.details.sections != null) { -+ item.details.sections.forEach((section: any) => { -+ if (section.fields != null) { -+ this.parseFields(section.fields, cipher, "n", "v", "t"); -+ } -+ }); -+ } - } -+ } - -- private processWinOpVaultItem(item: any, cipher: CipherView) { -- if (item.overview != null) { -- cipher.name = this.getValueOrDefault(item.overview.title); -- if (item.overview.URLs != null) { -- const urls: string[] = []; -- item.overview.URLs.forEach((url: any) => { -- if (!this.isNullOrWhitespace(url.u)) { -- urls.push(url.u); -- } -- }); -- cipher.login.uris = this.makeUriArray(urls); -- } -- } -+ private processStandardItem(item: any, cipher: CipherView) { -+ cipher.favorite = item.openContents && item.openContents.faveIndex ? true : false; -+ cipher.name = this.getValueOrDefault(item.title); - -- if (item.details != null) { -- if (item.details.passwordHistory != null) { -- this.parsePasswordHistory(item.details.passwordHistory, cipher); -- } -- if (!this.isNullOrWhitespace(item.details.ccnum) || !this.isNullOrWhitespace(item.details.cvv)) { -- cipher.type = CipherType.Card; -- cipher.card = new CardView(); -- } else if (!this.isNullOrWhitespace(item.details.firstname) || -- !this.isNullOrWhitespace(item.details.address1)) { -- cipher.type = CipherType.Identity; -- cipher.identity = new IdentityView(); -- } -- if (cipher.type === CipherType.Login && !this.isNullOrWhitespace(item.details.password)) { -- cipher.login.password = item.details.password; -- } -- if (!this.isNullOrWhitespace(item.details.notesPlain)) { -- cipher.notes = item.details.notesPlain.split(this.newLineRegex).join('\n') + '\n'; -- } -- if (item.details.fields != null) { -- this.parseFields(item.details.fields, cipher, 'designation', 'value', 'name'); -- } -- if (item.details.sections != null) { -- item.details.sections.forEach((section: any) => { -- if (section.fields != null) { -- this.parseFields(section.fields, cipher, 'n', 'v', 't'); -- } -- }); -- } -- } -+ if (item.typeName === "securenotes.SecureNote") { -+ cipher.type = CipherType.SecureNote; -+ cipher.secureNote = new SecureNoteView(); -+ cipher.secureNote.type = SecureNoteType.Generic; -+ } else if (item.typeName === "wallet.financial.CreditCard") { -+ cipher.type = CipherType.Card; -+ cipher.card = new CardView(); -+ } else if (item.typeName === "identities.Identity") { -+ cipher.type = CipherType.Identity; -+ cipher.identity = new IdentityView(); -+ } else { -+ cipher.login.uris = this.makeUriArray(item.location); - } - -- private processStandardItem(item: any, cipher: CipherView) { -- cipher.favorite = item.openContents && item.openContents.faveIndex ? true : false; -- cipher.name = this.getValueOrDefault(item.title); -- -- if (item.typeName === 'securenotes.SecureNote') { -- cipher.type = CipherType.SecureNote; -- cipher.secureNote = new SecureNoteView(); -- cipher.secureNote.type = SecureNoteType.Generic; -- } else if (item.typeName === 'wallet.financial.CreditCard') { -- cipher.type = CipherType.Card; -- cipher.card = new CardView(); -- } else if (item.typeName === 'identities.Identity') { -- cipher.type = CipherType.Identity; -- cipher.identity = new IdentityView(); -- } else { -- cipher.login.uris = this.makeUriArray(item.location); -+ if (item.secureContents != null) { -+ if (item.secureContents.passwordHistory != null) { -+ this.parsePasswordHistory(item.secureContents.passwordHistory, cipher); -+ } -+ if (!this.isNullOrWhitespace(item.secureContents.notesPlain)) { -+ cipher.notes = item.secureContents.notesPlain.split(this.newLineRegex).join("\n") + "\n"; -+ } -+ if (cipher.type === CipherType.Login) { -+ if (!this.isNullOrWhitespace(item.secureContents.password)) { -+ cipher.login.password = item.secureContents.password; - } -- -- if (item.secureContents != null) { -- if (item.secureContents.passwordHistory != null) { -- this.parsePasswordHistory(item.secureContents.passwordHistory, cipher); -- } -- if (!this.isNullOrWhitespace(item.secureContents.notesPlain)) { -- cipher.notes = item.secureContents.notesPlain.split(this.newLineRegex).join('\n') + '\n'; -- } -- if (cipher.type === CipherType.Login) { -- if (!this.isNullOrWhitespace(item.secureContents.password)) { -- cipher.login.password = item.secureContents.password; -- } -- if (item.secureContents.URLs != null) { -- const urls: string[] = []; -- item.secureContents.URLs.forEach((u: any) => { -- if (!this.isNullOrWhitespace(u.url)) { -- urls.push(u.url); -- } -- }); -- if (urls.length > 0) { -- cipher.login.uris = this.makeUriArray(urls); -- } -- } -- } -- if (item.secureContents.fields != null) { -- this.parseFields(item.secureContents.fields, cipher, 'designation', 'value', 'name'); -- } -- if (item.secureContents.sections != null) { -- item.secureContents.sections.forEach((section: any) => { -- if (section.fields != null) { -- this.parseFields(section.fields, cipher, 'n', 'v', 't'); -- } -- }); -+ if (item.secureContents.URLs != null) { -+ const urls: string[] = []; -+ item.secureContents.URLs.forEach((u: any) => { -+ if (!this.isNullOrWhitespace(u.url)) { -+ urls.push(u.url); - } -+ }); -+ if (urls.length > 0) { -+ cipher.login.uris = this.makeUriArray(urls); -+ } - } -+ } -+ if (item.secureContents.fields != null) { -+ this.parseFields(item.secureContents.fields, cipher, "designation", "value", "name"); -+ } -+ if (item.secureContents.sections != null) { -+ item.secureContents.sections.forEach((section: any) => { -+ if (section.fields != null) { -+ this.parseFields(section.fields, cipher, "n", "v", "t"); -+ } -+ }); -+ } - } -+ } - -- private parsePasswordHistory(items: any[], cipher: CipherView) { -- const maxSize = items.length > 5 ? 5 : items.length; -- cipher.passwordHistory = items -- .filter((h: any) => !this.isNullOrWhitespace(h.value) && h.time != null) -- .sort((a, b) => b.time - a.time) -- .slice(0, maxSize) -- .map((h: any) => { -- const ph = new PasswordHistoryView(); -- ph.password = h.value; -- ph.lastUsedDate = new Date(('' + h.time).length >= 13 ? h.time : h.time * 1000); -- return ph; -- }); -- } -+ private parsePasswordHistory(items: any[], cipher: CipherView) { -+ const maxSize = items.length > 5 ? 5 : items.length; -+ cipher.passwordHistory = items -+ .filter((h: any) => !this.isNullOrWhitespace(h.value) && h.time != null) -+ .sort((a, b) => b.time - a.time) -+ .slice(0, maxSize) -+ .map((h: any) => { -+ const ph = new PasswordHistoryView(); -+ ph.password = h.value; -+ ph.lastUsedDate = new Date(("" + h.time).length >= 13 ? h.time : h.time * 1000); -+ return ph; -+ }); -+ } - -- private parseFields(fields: any[], cipher: CipherView, designationKey: string, valueKey: string, nameKey: string) { -- fields.forEach((field: any) => { -- if (field[valueKey] == null || field[valueKey].toString().trim() === '') { -- return; -- } -+ private parseFields( -+ fields: any[], -+ cipher: CipherView, -+ designationKey: string, -+ valueKey: string, -+ nameKey: string -+ ) { -+ fields.forEach((field: any) => { -+ if (field[valueKey] == null || field[valueKey].toString().trim() === "") { -+ return; -+ } - -- const fieldValue = field[valueKey].toString(); -- const fieldDesignation = field[designationKey] != null ? field[designationKey].toString() : null; -- -- if (cipher.type === CipherType.Login) { -- if (this.isNullOrWhitespace(cipher.login.username) && fieldDesignation === 'username') { -- cipher.login.username = fieldValue; -- return; -- } else if (this.isNullOrWhitespace(cipher.login.password) && fieldDesignation === 'password') { -- cipher.login.password = fieldValue; -- return; -- } else if (this.isNullOrWhitespace(cipher.login.totp) && fieldDesignation != null && -- fieldDesignation.startsWith('TOTP_')) { -- cipher.login.totp = fieldValue; -- return; -- } -- } else if (cipher.type === CipherType.Card) { -- if (this.isNullOrWhitespace(cipher.card.number) && fieldDesignation === 'ccnum') { -- cipher.card.number = fieldValue; -- cipher.card.brand = this.getCardBrand(fieldValue); -- return; -- } else if (this.isNullOrWhitespace(cipher.card.code) && fieldDesignation === 'cvv') { -- cipher.card.code = fieldValue; -- return; -- } else if (this.isNullOrWhitespace(cipher.card.cardholderName) && fieldDesignation === 'cardholder') { -- cipher.card.cardholderName = fieldValue; -- return; -- } else if (this.isNullOrWhitespace(cipher.card.expiration) && fieldDesignation === 'expiry' && -- fieldValue.length === 6) { -- cipher.card.expMonth = (fieldValue as string).substr(4, 2); -- if (cipher.card.expMonth[0] === '0') { -- cipher.card.expMonth = cipher.card.expMonth.substr(1, 1); -- } -- cipher.card.expYear = (fieldValue as string).substr(0, 4); -- return; -- } else if (fieldDesignation === 'type') { -- // Skip since brand was determined from number above -- return; -- } -- } else if (cipher.type === CipherType.Identity) { -- const identity = cipher.identity; -- if (this.isNullOrWhitespace(identity.firstName) && fieldDesignation === 'firstname') { -- identity.firstName = fieldValue; -- return; -- } else if (this.isNullOrWhitespace(identity.lastName) && fieldDesignation === 'lastname') { -- identity.lastName = fieldValue; -- return; -- } else if (this.isNullOrWhitespace(identity.middleName) && fieldDesignation === 'initial') { -- identity.middleName = fieldValue; -- return; -- } else if (this.isNullOrWhitespace(identity.phone) && fieldDesignation === 'defphone') { -- identity.phone = fieldValue; -- return; -- } else if (this.isNullOrWhitespace(identity.company) && fieldDesignation === 'company') { -- identity.company = fieldValue; -- return; -- } else if (this.isNullOrWhitespace(identity.email) && fieldDesignation === 'email') { -- identity.email = fieldValue; -- return; -- } else if (this.isNullOrWhitespace(identity.username) && fieldDesignation === 'username') { -- identity.username = fieldValue; -- return; -- } else if (fieldDesignation === 'address') { -- // fieldValue is an object casted into a string, so access the plain value instead -- const { street, city, country, zip } = field[valueKey]; -- identity.address1 = this.getValueOrDefault(street); -- identity.city = this.getValueOrDefault(city); -- if (!this.isNullOrWhitespace(country)) { -- identity.country = country.toUpperCase(); -- } -- identity.postalCode = this.getValueOrDefault(zip); -- return; -- } -- } -+ // TODO: when date FieldType exists, store this as a date field type instead of formatted Text if k is 'date' -+ const fieldValue = -+ field.k === "date" -+ ? new Date(field[valueKey] * 1000).toUTCString() -+ : field[valueKey].toString(); -+ const fieldDesignation = -+ field[designationKey] != null ? field[designationKey].toString() : null; - -- const fieldName = this.isNullOrWhitespace(field[nameKey]) ? 'no_name' : field[nameKey]; -- if (fieldName === 'password' && cipher.passwordHistory != null && -- cipher.passwordHistory.some(h => h.password === fieldValue)) { -- return; -- } -+ if (cipher.type === CipherType.Login) { -+ if (this.isNullOrWhitespace(cipher.login.username) && fieldDesignation === "username") { -+ cipher.login.username = fieldValue; -+ return; -+ } else if ( -+ this.isNullOrWhitespace(cipher.login.password) && -+ fieldDesignation === "password" -+ ) { -+ cipher.login.password = fieldValue; -+ return; -+ } else if ( -+ this.isNullOrWhitespace(cipher.login.totp) && -+ fieldDesignation != null && -+ fieldDesignation.startsWith("TOTP_") -+ ) { -+ cipher.login.totp = fieldValue; -+ return; -+ } -+ } else if (cipher.type === CipherType.Card) { -+ if (this.isNullOrWhitespace(cipher.card.number) && fieldDesignation === "ccnum") { -+ cipher.card.number = fieldValue; -+ cipher.card.brand = this.getCardBrand(fieldValue); -+ return; -+ } else if (this.isNullOrWhitespace(cipher.card.code) && fieldDesignation === "cvv") { -+ cipher.card.code = fieldValue; -+ return; -+ } else if ( -+ this.isNullOrWhitespace(cipher.card.cardholderName) && -+ fieldDesignation === "cardholder" -+ ) { -+ cipher.card.cardholderName = fieldValue; -+ return; -+ } else if ( -+ this.isNullOrWhitespace(cipher.card.expiration) && -+ fieldDesignation === "expiry" && -+ fieldValue.length === 6 -+ ) { -+ cipher.card.expMonth = (fieldValue as string).substr(4, 2); -+ if (cipher.card.expMonth[0] === "0") { -+ cipher.card.expMonth = cipher.card.expMonth.substr(1, 1); -+ } -+ cipher.card.expYear = (fieldValue as string).substr(0, 4); -+ return; -+ } else if (fieldDesignation === "type") { -+ // Skip since brand was determined from number above -+ return; -+ } -+ } else if (cipher.type === CipherType.Identity) { -+ const identity = cipher.identity; -+ if (this.isNullOrWhitespace(identity.firstName) && fieldDesignation === "firstname") { -+ identity.firstName = fieldValue; -+ return; -+ } else if (this.isNullOrWhitespace(identity.lastName) && fieldDesignation === "lastname") { -+ identity.lastName = fieldValue; -+ return; -+ } else if (this.isNullOrWhitespace(identity.middleName) && fieldDesignation === "initial") { -+ identity.middleName = fieldValue; -+ return; -+ } else if (this.isNullOrWhitespace(identity.phone) && fieldDesignation === "defphone") { -+ identity.phone = fieldValue; -+ return; -+ } else if (this.isNullOrWhitespace(identity.company) && fieldDesignation === "company") { -+ identity.company = fieldValue; -+ return; -+ } else if (this.isNullOrWhitespace(identity.email) && fieldDesignation === "email") { -+ identity.email = fieldValue; -+ return; -+ } else if (this.isNullOrWhitespace(identity.username) && fieldDesignation === "username") { -+ identity.username = fieldValue; -+ return; -+ } else if (fieldDesignation === "address") { -+ // fieldValue is an object casted into a string, so access the plain value instead -+ const { street, city, country, zip } = field[valueKey]; -+ identity.address1 = this.getValueOrDefault(street); -+ identity.city = this.getValueOrDefault(city); -+ if (!this.isNullOrWhitespace(country)) { -+ identity.country = country.toUpperCase(); -+ } -+ identity.postalCode = this.getValueOrDefault(zip); -+ return; -+ } -+ } - -- const fieldType = field.k === 'concealed' ? FieldType.Hidden : FieldType.Text; -- this.processKvp(cipher, fieldName, fieldValue, fieldType); -- }); -- } -+ const fieldName = this.isNullOrWhitespace(field[nameKey]) ? "no_name" : field[nameKey]; -+ if ( -+ fieldName === "password" && -+ cipher.passwordHistory != null && -+ cipher.passwordHistory.some((h) => h.password === fieldValue) -+ ) { -+ return; -+ } -+ -+ const fieldType = field.k === "concealed" ? FieldType.Hidden : FieldType.Text; -+ this.processKvp(cipher, fieldName, fieldValue, fieldType); -+ }); -+ } - } -diff --git a/jslib/common/src/importers/onepasswordImporters/onepasswordCsvImporter.ts b/jslib/common/src/importers/onepasswordImporters/onepasswordCsvImporter.ts -index 8bf5f153..c4234022 100644 ---- a/jslib/common/src/importers/onepasswordImporters/onepasswordCsvImporter.ts -+++ b/jslib/common/src/importers/onepasswordImporters/onepasswordCsvImporter.ts -@@ -1,288 +1,382 @@ --import { ImportResult } from '../../models/domain/importResult'; --import { BaseImporter } from '../baseImporter'; --import { Importer } from '../importer'; -- --import { CipherType } from '../../enums/cipherType'; --import { FieldType } from '../../enums/fieldType'; --import { CipherView } from '../../models/view/cipherView'; --import { CipherImportContext } from './cipherImportContext'; -- --export const IgnoredProperties = ['ainfo', 'autosubmit', 'notesplain', 'ps', 'scope', 'tags', 'title', 'uuid', 'notes']; -+import { ImportResult } from "../../models/domain/importResult"; -+import { BaseImporter } from "../baseImporter"; -+import { Importer } from "../importer"; -+ -+import { CipherType } from "../../enums/cipherType"; -+import { FieldType } from "../../enums/fieldType"; -+import { CipherView } from "../../models/view/cipherView"; -+import { CipherImportContext } from "./cipherImportContext"; -+ -+export const IgnoredProperties = [ -+ "ainfo", -+ "autosubmit", -+ "notesplain", -+ "ps", -+ "scope", -+ "tags", -+ "title", -+ "uuid", -+ "notes", -+]; - - export abstract class OnePasswordCsvImporter extends BaseImporter implements Importer { -- protected loginPropertyParsers = [this.setLoginUsername, this.setLoginPassword, this.setLoginUris]; -- protected creditCardPropertyParsers = [this.setCreditCardNumber, this.setCreditCardVerification, this.setCreditCardCardholderName, this.setCreditCardExpiry]; -- protected identityPropertyParsers = [this.setIdentityFirstName, this.setIdentityInitial, this.setIdentityLastName, this.setIdentityUserName, this.setIdentityEmail, this.setIdentityPhone, this.setIdentityCompany]; -- -- abstract setCipherType(value: any, cipher: CipherView): void; -- -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true, { -- quoteChar: '"', -- escapeChar: '\\', -- }); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -- -- results.forEach(value => { -- if (this.isNullOrWhitespace(this.getProp(value, 'title'))) { -- return; -- } -- -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(this.getProp(value, 'title'), '--'); -- -- this.setNotes(value, cipher); -- -- this.setCipherType(value, cipher); -- -- let altUsername: string = null; -- for (const property in value) { -- if (!value.hasOwnProperty(property) || this.isNullOrWhitespace(value[property])) { -- continue; -- } -- -- const context = new CipherImportContext(value, property, cipher); -- if (cipher.type === CipherType.Login && this.setKnownLoginValue(context)) { -- continue; -- } else if (cipher.type === CipherType.Card && this.setKnownCreditCardValue(context)) { -- continue; -- } else if (cipher.type === CipherType.Identity && this.setKnownIdentityValue(context)) { -- continue; -- } -- -- altUsername = this.setUnknownValue(context, altUsername); -- } -+ protected loginPropertyParsers = [ -+ this.setLoginUsername, -+ this.setLoginPassword, -+ this.setLoginUris, -+ ]; -+ protected creditCardPropertyParsers = [ -+ this.setCreditCardNumber, -+ this.setCreditCardVerification, -+ this.setCreditCardCardholderName, -+ this.setCreditCardExpiry, -+ ]; -+ protected identityPropertyParsers = [ -+ this.setIdentityFirstName, -+ this.setIdentityInitial, -+ this.setIdentityLastName, -+ this.setIdentityUserName, -+ this.setIdentityEmail, -+ this.setIdentityPhone, -+ this.setIdentityCompany, -+ ]; -+ -+ abstract setCipherType(value: any, cipher: CipherView): void; -+ -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true, { -+ quoteChar: '"', -+ escapeChar: "\\", -+ }); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- if (cipher.type === CipherType.Login && !this.isNullOrWhitespace(altUsername) && -- this.isNullOrWhitespace(cipher.login.username) && altUsername.indexOf('://') === -1) { -- cipher.login.username = altUsername; -- } -+ results.forEach((value) => { -+ if (this.isNullOrWhitespace(this.getProp(value, "title"))) { -+ return; -+ } - -- this.convertToNoteIfNeeded(cipher); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(this.getProp(value, "title"), "--"); - -- result.success = true; -- return Promise.resolve(result); -- } -+ this.setNotes(value, cipher); - -- protected getProp(obj: any, name: string): any { -- const lowerObj = Object.entries(obj).reduce((agg: any, entry: [string, any]) => { -- agg[entry[0].toLowerCase()] = entry[1]; -- return agg; -- }, {}); -- return lowerObj[name.toLowerCase()]; -- } -+ this.setCipherType(value, cipher); - -- protected getPropByRegexp(obj: any, regexp: RegExp): any { -- const matchingKeys = Object.keys(obj).reduce((agg: string[], key: string) => { -- if (key.match(regexp)) { -- agg.push(key); -- } -- return agg; -- }, []); -- if (matchingKeys.length === 0) { -- return null; -- } else { -- return obj[matchingKeys[0]]; -+ let altUsername: string = null; -+ for (const property in value) { -+ if (!value.hasOwnProperty(property) || this.isNullOrWhitespace(value[property])) { -+ continue; - } -- } - -- protected getPropIncluding(obj: any, name: string): any { -- const includesMap = Object.keys(obj).reduce((agg: string[], entry: string) => { -- if (entry.toLowerCase().includes(name.toLowerCase())) { -- agg.push(entry); -- } -- return agg; -- }, []); -- if (includesMap.length === 0) { -- return null; -- } else { -- return obj[includesMap[0]]; -+ const context = new CipherImportContext(value, property, cipher); -+ if (cipher.type === CipherType.Login && this.setKnownLoginValue(context)) { -+ continue; -+ } else if (cipher.type === CipherType.Card && this.setKnownCreditCardValue(context)) { -+ continue; -+ } else if (cipher.type === CipherType.Identity && this.setKnownIdentityValue(context)) { -+ continue; - } -- } -- -- protected setNotes(importRecord: any, cipher: CipherView) { -- cipher.notes = this.getValueOrDefault(this.getProp(importRecord, 'notesPlain'), '') + '\n' + -- this.getValueOrDefault(this.getProp(importRecord, 'notes'), '') + '\n'; -- cipher.notes.trim(); - -+ altUsername = this.setUnknownValue(context, altUsername); -+ } -+ -+ if ( -+ cipher.type === CipherType.Login && -+ !this.isNullOrWhitespace(altUsername) && -+ this.isNullOrWhitespace(cipher.login.username) && -+ altUsername.indexOf("://") === -1 -+ ) { -+ cipher.login.username = altUsername; -+ } -+ -+ this.convertToNoteIfNeeded(cipher); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } -+ -+ protected getProp(obj: any, name: string): any { -+ const lowerObj = Object.entries(obj).reduce((agg: any, entry: [string, any]) => { -+ agg[entry[0].toLowerCase()] = entry[1]; -+ return agg; -+ }, {}); -+ return lowerObj[name.toLowerCase()]; -+ } -+ -+ protected getPropByRegexp(obj: any, regexp: RegExp): any { -+ const matchingKeys = Object.keys(obj).reduce((agg: string[], key: string) => { -+ if (key.match(regexp)) { -+ agg.push(key); -+ } -+ return agg; -+ }, []); -+ if (matchingKeys.length === 0) { -+ return null; -+ } else { -+ return obj[matchingKeys[0]]; - } -- -- protected setKnownLoginValue(context: CipherImportContext): boolean { -- return this.loginPropertyParsers.reduce((agg: boolean, func) => { -- if (!agg) { -- agg = func.bind(this)(context); -- } -- return agg; -- }, false); -+ } -+ -+ protected getPropIncluding(obj: any, name: string): any { -+ const includesMap = Object.keys(obj).reduce((agg: string[], entry: string) => { -+ if (entry.toLowerCase().includes(name.toLowerCase())) { -+ agg.push(entry); -+ } -+ return agg; -+ }, []); -+ if (includesMap.length === 0) { -+ return null; -+ } else { -+ return obj[includesMap[0]]; - } -- -- protected setKnownCreditCardValue(context: CipherImportContext): boolean { -- return this.creditCardPropertyParsers.reduce((agg: boolean, func) => { -- if (!agg) { -- agg = func.bind(this)(context); -- } -- return agg; -- }, false); -- } -- -- protected setKnownIdentityValue(context: CipherImportContext): boolean { -- return this.identityPropertyParsers.reduce((agg: boolean, func) => { -- if (!agg) { -- agg = func.bind(this)(context); -- } -- return agg; -- }, false); -- } -- -- protected setUnknownValue(context: CipherImportContext, altUsername: string): string { -- if (IgnoredProperties.indexOf(context.lowerProperty) === -1 && !context.lowerProperty.startsWith('section:') && -- !context.lowerProperty.startsWith('section ')) { -- if (altUsername == null && context.lowerProperty === 'email') { -- return context.importRecord[context.property]; -- } -- else if (context.lowerProperty === 'created date' || context.lowerProperty === 'modified date') { -- const readableDate = new Date(parseInt(context.importRecord[context.property], 10) * 1000).toUTCString(); -- this.processKvp(context.cipher, '1Password ' + context.property, readableDate); -- return null; -- } -- if (context.lowerProperty.includes('password') || context.lowerProperty.includes('key') || context.lowerProperty.includes('secret')) { -- this.processKvp(context.cipher, context.property, context.importRecord[context.property], FieldType.Hidden); -- } else { -- this.processKvp(context.cipher, context.property, context.importRecord[context.property]); -- } -- } -+ } -+ -+ protected setNotes(importRecord: any, cipher: CipherView) { -+ cipher.notes = -+ this.getValueOrDefault(this.getProp(importRecord, "notesPlain"), "") + -+ "\n" + -+ this.getValueOrDefault(this.getProp(importRecord, "notes"), "") + -+ "\n"; -+ cipher.notes.trim(); -+ } -+ -+ protected setKnownLoginValue(context: CipherImportContext): boolean { -+ return this.loginPropertyParsers.reduce((agg: boolean, func) => { -+ if (!agg) { -+ agg = func.bind(this)(context); -+ } -+ return agg; -+ }, false); -+ } -+ -+ protected setKnownCreditCardValue(context: CipherImportContext): boolean { -+ return this.creditCardPropertyParsers.reduce((agg: boolean, func) => { -+ if (!agg) { -+ agg = func.bind(this)(context); -+ } -+ return agg; -+ }, false); -+ } -+ -+ protected setKnownIdentityValue(context: CipherImportContext): boolean { -+ return this.identityPropertyParsers.reduce((agg: boolean, func) => { -+ if (!agg) { -+ agg = func.bind(this)(context); -+ } -+ return agg; -+ }, false); -+ } -+ -+ protected setUnknownValue(context: CipherImportContext, altUsername: string): string { -+ if ( -+ IgnoredProperties.indexOf(context.lowerProperty) === -1 && -+ !context.lowerProperty.startsWith("section:") && -+ !context.lowerProperty.startsWith("section ") -+ ) { -+ if (altUsername == null && context.lowerProperty === "email") { -+ return context.importRecord[context.property]; -+ } else if ( -+ context.lowerProperty === "created date" || -+ context.lowerProperty === "modified date" -+ ) { -+ const readableDate = new Date( -+ parseInt(context.importRecord[context.property], 10) * 1000 -+ ).toUTCString(); -+ this.processKvp(context.cipher, "1Password " + context.property, readableDate); - return null; -+ } -+ if ( -+ context.lowerProperty.includes("password") || -+ context.lowerProperty.includes("key") || -+ context.lowerProperty.includes("secret") -+ ) { -+ this.processKvp( -+ context.cipher, -+ context.property, -+ context.importRecord[context.property], -+ FieldType.Hidden -+ ); -+ } else { -+ this.processKvp(context.cipher, context.property, context.importRecord[context.property]); -+ } - } -- -- protected setIdentityFirstName(context: CipherImportContext) { -- if (this.isNullOrWhitespace(context.cipher.identity.firstName) && context.lowerProperty.includes('first name')) { -- context.cipher.identity.firstName = context.importRecord[context.property]; -- return true; -- } -- return false; -+ return null; -+ } -+ -+ protected setIdentityFirstName(context: CipherImportContext) { -+ if ( -+ this.isNullOrWhitespace(context.cipher.identity.firstName) && -+ context.lowerProperty.includes("first name") -+ ) { -+ context.cipher.identity.firstName = context.importRecord[context.property]; -+ return true; - } -- -- protected setIdentityInitial(context: CipherImportContext) { -- if (this.isNullOrWhitespace(context.cipher.identity.middleName) && context.lowerProperty.includes('initial')) { -- context.cipher.identity.middleName = context.importRecord[context.property]; -- return true; -- } -- return false; -+ return false; -+ } -+ -+ protected setIdentityInitial(context: CipherImportContext) { -+ if ( -+ this.isNullOrWhitespace(context.cipher.identity.middleName) && -+ context.lowerProperty.includes("initial") -+ ) { -+ context.cipher.identity.middleName = context.importRecord[context.property]; -+ return true; - } -- -- protected setIdentityLastName(context: CipherImportContext) { -- if (this.isNullOrWhitespace(context.cipher.identity.lastName) && context.lowerProperty.includes('last name')) { -- context.cipher.identity.lastName = context.importRecord[context.property]; -- return true; -- } -- return false; -+ return false; -+ } -+ -+ protected setIdentityLastName(context: CipherImportContext) { -+ if ( -+ this.isNullOrWhitespace(context.cipher.identity.lastName) && -+ context.lowerProperty.includes("last name") -+ ) { -+ context.cipher.identity.lastName = context.importRecord[context.property]; -+ return true; - } -- -- protected setIdentityUserName(context: CipherImportContext) { -- if (this.isNullOrWhitespace(context.cipher.identity.username) && context.lowerProperty.includes('username')) { -- context.cipher.identity.username = context.importRecord[context.property]; -- return true; -- } -- return false; -+ return false; -+ } -+ -+ protected setIdentityUserName(context: CipherImportContext) { -+ if ( -+ this.isNullOrWhitespace(context.cipher.identity.username) && -+ context.lowerProperty.includes("username") -+ ) { -+ context.cipher.identity.username = context.importRecord[context.property]; -+ return true; - } -- -- protected setIdentityCompany(context: CipherImportContext) { -- if (this.isNullOrWhitespace(context.cipher.identity.company) && context.lowerProperty.includes('company')) { -- context.cipher.identity.company = context.importRecord[context.property]; -- return true; -- } -- return false; -+ return false; -+ } -+ -+ protected setIdentityCompany(context: CipherImportContext) { -+ if ( -+ this.isNullOrWhitespace(context.cipher.identity.company) && -+ context.lowerProperty.includes("company") -+ ) { -+ context.cipher.identity.company = context.importRecord[context.property]; -+ return true; - } -- -- protected setIdentityPhone(context: CipherImportContext) { -- if (this.isNullOrWhitespace(context.cipher.identity.phone) && context.lowerProperty.includes('default phone')) { -- context.cipher.identity.phone = context.importRecord[context.property]; -- return true; -- } -- return false; -+ return false; -+ } -+ -+ protected setIdentityPhone(context: CipherImportContext) { -+ if ( -+ this.isNullOrWhitespace(context.cipher.identity.phone) && -+ context.lowerProperty.includes("default phone") -+ ) { -+ context.cipher.identity.phone = context.importRecord[context.property]; -+ return true; - } -- -- protected setIdentityEmail(context: CipherImportContext) { -- if (this.isNullOrWhitespace(context.cipher.identity.email) && context.lowerProperty.includes('email')) { -- context.cipher.identity.email = context.importRecord[context.property]; -- return true; -- } -- return false; -+ return false; -+ } -+ -+ protected setIdentityEmail(context: CipherImportContext) { -+ if ( -+ this.isNullOrWhitespace(context.cipher.identity.email) && -+ context.lowerProperty.includes("email") -+ ) { -+ context.cipher.identity.email = context.importRecord[context.property]; -+ return true; - } -- -- protected setCreditCardNumber(context: CipherImportContext): boolean { -- if (this.isNullOrWhitespace(context.cipher.card.number) && context.lowerProperty.includes('number')) { -- context.cipher.card.number = context.importRecord[context.property]; -- context.cipher.card.brand = this.getCardBrand(context.cipher.card.number); -- return true; -- } -- return false; -+ return false; -+ } -+ -+ protected setCreditCardNumber(context: CipherImportContext): boolean { -+ if ( -+ this.isNullOrWhitespace(context.cipher.card.number) && -+ context.lowerProperty.includes("number") -+ ) { -+ context.cipher.card.number = context.importRecord[context.property]; -+ context.cipher.card.brand = this.getCardBrand(context.cipher.card.number); -+ return true; - } -- -- protected setCreditCardVerification(context: CipherImportContext) { -- if (this.isNullOrWhitespace(context.cipher.card.code) && context.lowerProperty.includes('verification number')) { -- context.cipher.card.code = context.importRecord[context.property]; -- return true; -- } -- return false; -+ return false; -+ } -+ -+ protected setCreditCardVerification(context: CipherImportContext) { -+ if ( -+ this.isNullOrWhitespace(context.cipher.card.code) && -+ context.lowerProperty.includes("verification number") -+ ) { -+ context.cipher.card.code = context.importRecord[context.property]; -+ return true; - } -- -- protected setCreditCardCardholderName(context: CipherImportContext) { -- if (this.isNullOrWhitespace(context.cipher.card.cardholderName) && context.lowerProperty.includes('cardholder name')) { -- context.cipher.card.cardholderName = context.importRecord[context.property]; -- return true; -- } -- return false; -+ return false; -+ } -+ -+ protected setCreditCardCardholderName(context: CipherImportContext) { -+ if ( -+ this.isNullOrWhitespace(context.cipher.card.cardholderName) && -+ context.lowerProperty.includes("cardholder name") -+ ) { -+ context.cipher.card.cardholderName = context.importRecord[context.property]; -+ return true; - } -- -- protected setCreditCardExpiry(context: CipherImportContext) { -- if (this.isNullOrWhitespace(context.cipher.card.expiration) && context.lowerProperty.includes('expiry date') && -- context.importRecord[context.property].length === 7) { -- context.cipher.card.expMonth = (context.importRecord[context.property] as string).substr(0, 2); -- if (context.cipher.card.expMonth[0] === '0') { -- context.cipher.card.expMonth = context.cipher.card.expMonth.substr(1, 1); -- } -- context.cipher.card.expYear = (context.importRecord[context.property] as string).substr(3, 4); -- return true; -- } -- return false; -+ return false; -+ } -+ -+ protected setCreditCardExpiry(context: CipherImportContext) { -+ if ( -+ this.isNullOrWhitespace(context.cipher.card.expiration) && -+ context.lowerProperty.includes("expiry date") && -+ context.importRecord[context.property].length === 7 -+ ) { -+ context.cipher.card.expMonth = (context.importRecord[context.property] as string).substr( -+ 0, -+ 2 -+ ); -+ if (context.cipher.card.expMonth[0] === "0") { -+ context.cipher.card.expMonth = context.cipher.card.expMonth.substr(1, 1); -+ } -+ context.cipher.card.expYear = (context.importRecord[context.property] as string).substr(3, 4); -+ return true; - } -- -- protected setLoginPassword(context: CipherImportContext) { -- if (this.isNullOrWhitespace(context.cipher.login.password) && context.lowerProperty === 'password') { -- context.cipher.login.password = context.importRecord[context.property]; -- return true; -- } -- return false; -+ return false; -+ } -+ -+ protected setLoginPassword(context: CipherImportContext) { -+ if ( -+ this.isNullOrWhitespace(context.cipher.login.password) && -+ context.lowerProperty === "password" -+ ) { -+ context.cipher.login.password = context.importRecord[context.property]; -+ return true; - } -- -- protected setLoginUsername(context: CipherImportContext) { -- if (this.isNullOrWhitespace(context.cipher.login.username) && context.lowerProperty === 'username') { -- context.cipher.login.username = context.importRecord[context.property]; -- return true; -- } -- return false; -+ return false; -+ } -+ -+ protected setLoginUsername(context: CipherImportContext) { -+ if ( -+ this.isNullOrWhitespace(context.cipher.login.username) && -+ context.lowerProperty === "username" -+ ) { -+ context.cipher.login.username = context.importRecord[context.property]; -+ return true; - } -- -- protected setLoginUris(context: CipherImportContext) { -- if ((context.cipher.login.uris == null || context.cipher.login.uris.length === 0) && context.lowerProperty === 'urls') { -- const urls = context.importRecord[context.property].split(this.newLineRegex); -- context.cipher.login.uris = this.makeUriArray(urls); -- return true; -- } else if ((context.lowerProperty === 'url')) { -- if (context.cipher.login.uris == null) { -- context.cipher.login.uris = []; -- } -- context.cipher.login.uris.concat(this.makeUriArray(context.importRecord[context.property])); -- return true; -- } -- return false; -+ return false; -+ } -+ -+ protected setLoginUris(context: CipherImportContext) { -+ if ( -+ (context.cipher.login.uris == null || context.cipher.login.uris.length === 0) && -+ context.lowerProperty === "urls" -+ ) { -+ const urls = context.importRecord[context.property].split(this.newLineRegex); -+ context.cipher.login.uris = this.makeUriArray(urls); -+ return true; -+ } else if (context.lowerProperty === "url") { -+ if (context.cipher.login.uris == null) { -+ context.cipher.login.uris = []; -+ } -+ context.cipher.login.uris.concat(this.makeUriArray(context.importRecord[context.property])); -+ return true; - } -+ return false; -+ } - } -diff --git a/jslib/common/src/importers/onepasswordImporters/onepasswordMacCsvImporter.ts b/jslib/common/src/importers/onepasswordImporters/onepasswordMacCsvImporter.ts -index 35c602aa..4d91c1c0 100644 ---- a/jslib/common/src/importers/onepasswordImporters/onepasswordMacCsvImporter.ts -+++ b/jslib/common/src/importers/onepasswordImporters/onepasswordMacCsvImporter.ts -@@ -1,30 +1,30 @@ --import { Importer } from '../importer'; --import { IgnoredProperties, OnePasswordCsvImporter } from './onepasswordCsvImporter'; -+import { Importer } from "../importer"; -+import { IgnoredProperties, OnePasswordCsvImporter } from "./onepasswordCsvImporter"; - --import { CipherType } from '../../enums/cipherType'; --import { CardView } from '../../models/view/cardView'; --import { CipherView } from '../../models/view/cipherView'; --import { IdentityView } from '../../models/view/identityView'; -+import { CipherType } from "../../enums/cipherType"; -+import { CardView } from "../../models/view/cardView"; -+import { CipherView } from "../../models/view/cipherView"; -+import { IdentityView } from "../../models/view/identityView"; - - export class OnePasswordMacCsvImporter extends OnePasswordCsvImporter implements Importer { -- setCipherType(value: any, cipher: CipherView) { -- const onePassType = this.getValueOrDefault(this.getProp(value, 'type'), 'Login'); -- switch (onePassType) { -- case 'Credit Card': -- cipher.type = CipherType.Card; -- cipher.card = new CardView(); -- IgnoredProperties.push('type'); -- break; -- case 'Identity': -- cipher.type = CipherType.Identity; -- cipher.identity = new IdentityView(); -- IgnoredProperties.push('type'); -- break; -- case 'Login': -- case 'Secure Note': -- IgnoredProperties.push('type'); -- default: -- break; -- } -+ setCipherType(value: any, cipher: CipherView) { -+ const onePassType = this.getValueOrDefault(this.getProp(value, "type"), "Login"); -+ switch (onePassType) { -+ case "Credit Card": -+ cipher.type = CipherType.Card; -+ cipher.card = new CardView(); -+ IgnoredProperties.push("type"); -+ break; -+ case "Identity": -+ cipher.type = CipherType.Identity; -+ cipher.identity = new IdentityView(); -+ IgnoredProperties.push("type"); -+ break; -+ case "Login": -+ case "Secure Note": -+ IgnoredProperties.push("type"); -+ default: -+ break; - } -+ } - } -diff --git a/jslib/common/src/importers/onepasswordImporters/onepasswordWinCsvImporter.ts b/jslib/common/src/importers/onepasswordImporters/onepasswordWinCsvImporter.ts -index ef0c1055..ebc96b32 100644 ---- a/jslib/common/src/importers/onepasswordImporters/onepasswordWinCsvImporter.ts -+++ b/jslib/common/src/importers/onepasswordImporters/onepasswordWinCsvImporter.ts -@@ -1,56 +1,63 @@ --import { Importer } from '../importer'; --import { CipherImportContext } from './cipherImportContext'; --import { OnePasswordCsvImporter } from './onepasswordCsvImporter'; -+import { Importer } from "../importer"; -+import { CipherImportContext } from "./cipherImportContext"; -+import { OnePasswordCsvImporter } from "./onepasswordCsvImporter"; - --import { CipherType } from '../../enums/cipherType'; --import { CardView } from '../../models/view/cardView'; --import { CipherView } from '../../models/view/cipherView'; --import { IdentityView } from '../../models/view/identityView'; --import { LoginView } from '../../models/view/loginView'; -+import { CipherType } from "../../enums/cipherType"; -+import { CardView } from "../../models/view/cardView"; -+import { CipherView } from "../../models/view/cipherView"; -+import { IdentityView } from "../../models/view/identityView"; -+import { LoginView } from "../../models/view/loginView"; - - export class OnePasswordWinCsvImporter extends OnePasswordCsvImporter implements Importer { -- constructor() { -- super(); -- this.identityPropertyParsers.push(this.setIdentityAddress); -- } -+ constructor() { -+ super(); -+ this.identityPropertyParsers.push(this.setIdentityAddress); -+ } - -- setCipherType(value: any, cipher: CipherView) { -- cipher.type = CipherType.Login; -- cipher.login = new LoginView(); -+ setCipherType(value: any, cipher: CipherView) { -+ cipher.type = CipherType.Login; -+ cipher.login = new LoginView(); - -- if (!this.isNullOrWhitespace(this.getPropByRegexp(value, /\d+: number/i)) && -- !this.isNullOrWhitespace(this.getPropByRegexp(value, /\d+: expiry date/i))) { -- cipher.type = CipherType.Card; -- cipher.card = new CardView(); -- } -+ if ( -+ !this.isNullOrWhitespace(this.getPropByRegexp(value, /\d+: number/i)) && -+ !this.isNullOrWhitespace(this.getPropByRegexp(value, /\d+: expiry date/i)) -+ ) { -+ cipher.type = CipherType.Card; -+ cipher.card = new CardView(); -+ } - -- if (!this.isNullOrWhitespace(this.getPropByRegexp(value, /name \d+: first name/i)) || -- !this.isNullOrWhitespace(this.getPropByRegexp(value, /name \d+: initial/i)) || -- !this.isNullOrWhitespace(this.getPropByRegexp(value, /name \d+: last name/i)) || -- !this.isNullOrWhitespace(this.getPropByRegexp(value, /internet \d+: email/i))) { -- cipher.type = CipherType.Identity; -- cipher.identity = new IdentityView(); -- } -+ if ( -+ !this.isNullOrWhitespace(this.getPropByRegexp(value, /name \d+: first name/i)) || -+ !this.isNullOrWhitespace(this.getPropByRegexp(value, /name \d+: initial/i)) || -+ !this.isNullOrWhitespace(this.getPropByRegexp(value, /name \d+: last name/i)) || -+ !this.isNullOrWhitespace(this.getPropByRegexp(value, /internet \d+: email/i)) -+ ) { -+ cipher.type = CipherType.Identity; -+ cipher.identity = new IdentityView(); - } -+ } - -- setIdentityAddress(context: CipherImportContext) { -- if (context.lowerProperty.match(/address \d+: address/i)) { -- this.processKvp(context.cipher, 'address', context.importRecord[context.property]); -- return true; -- } -- return false; -+ setIdentityAddress(context: CipherImportContext) { -+ if (context.lowerProperty.match(/address \d+: address/i)) { -+ this.processKvp(context.cipher, "address", context.importRecord[context.property]); -+ return true; - } -+ return false; -+ } - -- setCreditCardExpiry(context: CipherImportContext) { -- if (this.isNullOrWhitespace(context.cipher.card.expiration) && context.lowerProperty.includes('expiry date')) { -- const expSplit = (context.importRecord[context.property] as string).split('/'); -- context.cipher.card.expMonth = expSplit[0]; -- if (context.cipher.card.expMonth[0] === '0' && context.cipher.card.expMonth.length === 2) { -- context.cipher.card.expMonth = context.cipher.card.expMonth.substr(1, 1); -- } -- context.cipher.card.expYear = expSplit[2].length > 4 ? expSplit[2].substr(0, 4) : expSplit[2]; -- return true; -- } -- return false; -+ setCreditCardExpiry(context: CipherImportContext) { -+ if ( -+ this.isNullOrWhitespace(context.cipher.card.expiration) && -+ context.lowerProperty.includes("expiry date") -+ ) { -+ const expSplit = (context.importRecord[context.property] as string).split("/"); -+ context.cipher.card.expMonth = expSplit[0]; -+ if (context.cipher.card.expMonth[0] === "0" && context.cipher.card.expMonth.length === 2) { -+ context.cipher.card.expMonth = context.cipher.card.expMonth.substr(1, 1); -+ } -+ context.cipher.card.expYear = expSplit[2].length > 4 ? expSplit[2].substr(0, 4) : expSplit[2]; -+ return true; - } -+ return false; -+ } - } -diff --git a/jslib/common/src/importers/padlockCsvImporter.ts b/jslib/common/src/importers/padlockCsvImporter.ts -index e106af3e..777d9822 100644 ---- a/jslib/common/src/importers/padlockCsvImporter.ts -+++ b/jslib/common/src/importers/padlockCsvImporter.ts -@@ -1,87 +1,87 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - --import { CollectionView } from '../models/view/collectionView'; --import { FolderView } from '../models/view/folderView'; -+import { CollectionView } from "../models/view/collectionView"; -+import { FolderView } from "../models/view/folderView"; - - export class PadlockCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, false); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -- -- let headers: string[] = null; -- results.forEach(value => { -- if (headers == null) { -- headers = value.map((v: string) => v); -- return; -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, false); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- if (value.length < 2 || value.length !== headers.length) { -- return; -- } -+ let headers: string[] = null; -+ results.forEach((value) => { -+ if (headers == null) { -+ headers = value.map((v: string) => v); -+ return; -+ } - -- if (!this.isNullOrWhitespace(value[1])) { -- if (this.organization) { -- const tags = (value[1] as string).split(','); -- tags.forEach(tag => { -- tag = tag.trim(); -- let addCollection = true; -- let collectionIndex = result.collections.length; -+ if (value.length < 2 || value.length !== headers.length) { -+ return; -+ } - -- for (let i = 0; i < result.collections.length; i++) { -- if (result.collections[i].name === tag) { -- addCollection = false; -- collectionIndex = i; -- break; -- } -- } -+ if (!this.isNullOrWhitespace(value[1])) { -+ if (this.organization) { -+ const tags = (value[1] as string).split(","); -+ tags.forEach((tag) => { -+ tag = tag.trim(); -+ let addCollection = true; -+ let collectionIndex = result.collections.length; - -- if (addCollection) { -- const collection = new CollectionView(); -- collection.name = tag; -- result.collections.push(collection); -- } -+ for (let i = 0; i < result.collections.length; i++) { -+ if (result.collections[i].name === tag) { -+ addCollection = false; -+ collectionIndex = i; -+ break; -+ } -+ } - -- result.collectionRelationships.push([result.ciphers.length, collectionIndex]); -- }); -- } else { -- const tags = (value[1] as string).split(','); -- const tag = tags.length > 0 ? tags[0].trim() : null; -- this.processFolder(result, tag); -- } -+ if (addCollection) { -+ const collection = new CollectionView(); -+ collection.name = tag; -+ result.collections.push(collection); - } - -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value[0], '--'); -+ result.collectionRelationships.push([result.ciphers.length, collectionIndex]); -+ }); -+ } else { -+ const tags = (value[1] as string).split(","); -+ const tag = tags.length > 0 ? tags[0].trim() : null; -+ this.processFolder(result, tag); -+ } -+ } - -- for (let i = 2; i < value.length; i++) { -- const header = headers[i].trim().toLowerCase(); -- if (this.isNullOrWhitespace(value[i]) || this.isNullOrWhitespace(header)) { -- continue; -- } -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(value[0], "--"); - -- if (this.usernameFieldNames.indexOf(header) > -1) { -- cipher.login.username = value[i]; -- } else if (this.passwordFieldNames.indexOf(header) > -1) { -- cipher.login.password = value[i]; -- } else if (this.uriFieldNames.indexOf(header) > -1) { -- cipher.login.uris = this.makeUriArray(value[i]); -- } else { -- this.processKvp(cipher, headers[i], value[i]); -- } -- } -+ for (let i = 2; i < value.length; i++) { -+ const header = headers[i].trim().toLowerCase(); -+ if (this.isNullOrWhitespace(value[i]) || this.isNullOrWhitespace(header)) { -+ continue; -+ } - -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ if (this.usernameFieldNames.indexOf(header) > -1) { -+ cipher.login.username = value[i]; -+ } else if (this.passwordFieldNames.indexOf(header) > -1) { -+ cipher.login.password = value[i]; -+ } else if (this.uriFieldNames.indexOf(header) > -1) { -+ cipher.login.uris = this.makeUriArray(value[i]); -+ } else { -+ this.processKvp(cipher, headers[i], value[i]); -+ } -+ } - -- result.success = true; -- return Promise.resolve(result); -- } -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/passkeepCsvImporter.ts b/jslib/common/src/importers/passkeepCsvImporter.ts -index a010550c..434f7ded 100644 ---- a/jslib/common/src/importers/passkeepCsvImporter.ts -+++ b/jslib/common/src/importers/passkeepCsvImporter.ts -@@ -1,39 +1,39 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class PassKeepCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -- -- results.forEach(value => { -- this.processFolder(result, this.getValue('category', value)); -- const cipher = this.initLoginCipher(); -- cipher.notes = this.getValue('description', value); -- cipher.name = this.getValueOrDefault(this.getValue('title', value), '--'); -- cipher.login.username = this.getValue('username', value); -- cipher.login.password = this.getValue('password', value); -- cipher.login.uris = this.makeUriArray(this.getValue('site', value)); -- this.processKvp(cipher, 'Password 2', this.getValue('password2', value)); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- if (this.organization) { -- this.moveFoldersToCollections(result); -- } -+ results.forEach((value) => { -+ this.processFolder(result, this.getValue("category", value)); -+ const cipher = this.initLoginCipher(); -+ cipher.notes = this.getValue("description", value); -+ cipher.name = this.getValueOrDefault(this.getValue("title", value), "--"); -+ cipher.login.username = this.getValue("username", value); -+ cipher.login.password = this.getValue("password", value); -+ cipher.login.uris = this.makeUriArray(this.getValue("site", value)); -+ this.processKvp(cipher, "Password 2", this.getValue("password2", value)); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -+ if (this.organization) { -+ this.moveFoldersToCollections(result); - } - -- private getValue(key: string, value: any) { -- return this.getValueOrDefault(value[key], this.getValueOrDefault(value[(' ' + key)])); -- } -+ result.success = true; -+ return Promise.resolve(result); -+ } -+ -+ private getValue(key: string, value: any) { -+ return this.getValueOrDefault(value[key], this.getValueOrDefault(value[" " + key])); -+ } - } -diff --git a/jslib/common/src/importers/passmanJsonImporter.ts b/jslib/common/src/importers/passmanJsonImporter.ts -index c00eeb1a..22232aba 100644 ---- a/jslib/common/src/importers/passmanJsonImporter.ts -+++ b/jslib/common/src/importers/passmanJsonImporter.ts -@@ -1,61 +1,61 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class PassmanJsonImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = JSON.parse(data); -- if (results == null || results.length === 0) { -- result.success = false; -- return Promise.resolve(result); -- } -- -- results.forEach((credential: any) => { -- if (credential.tags != null && credential.tags.length > 0) { -- const folderName = credential.tags[0].text; -- this.processFolder(result, folderName); -- } -- -- const cipher = this.initLoginCipher(); -- cipher.name = credential.label; -- -- cipher.login.username = this.getValueOrDefault(credential.username); -- if (this.isNullOrWhitespace(cipher.login.username)) { -- cipher.login.username = this.getValueOrDefault(credential.email); -- } else if (!this.isNullOrWhitespace(credential.email)) { -- cipher.notes = ('Email: ' + credential.email + '\n'); -- } -- -- cipher.login.password = this.getValueOrDefault(credential.password); -- cipher.login.uris = this.makeUriArray(credential.url); -- cipher.notes += this.getValueOrDefault(credential.description, ''); -- if (credential.otp != null) { -- cipher.login.totp = this.getValueOrDefault(credential.otp.secret); -- } -- -- if (credential.custom_fields != null) { -- credential.custom_fields.forEach((customField: any) => { -- switch (customField.field_type) { -- case 'text': -- case 'password': -- this.processKvp(cipher, customField.label, customField.value); -- break; -- } -- }); -- } -- -- this.convertToNoteIfNeeded(cipher); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = JSON.parse(data); -+ if (results == null || results.length === 0) { -+ result.success = false; -+ return Promise.resolve(result); -+ } -+ -+ results.forEach((credential: any) => { -+ if (credential.tags != null && credential.tags.length > 0) { -+ const folderName = credential.tags[0].text; -+ this.processFolder(result, folderName); -+ } -+ -+ const cipher = this.initLoginCipher(); -+ cipher.name = credential.label; -+ -+ cipher.login.username = this.getValueOrDefault(credential.username); -+ if (this.isNullOrWhitespace(cipher.login.username)) { -+ cipher.login.username = this.getValueOrDefault(credential.email); -+ } else if (!this.isNullOrWhitespace(credential.email)) { -+ cipher.notes = "Email: " + credential.email + "\n"; -+ } -+ -+ cipher.login.password = this.getValueOrDefault(credential.password); -+ cipher.login.uris = this.makeUriArray(credential.url); -+ cipher.notes += this.getValueOrDefault(credential.description, ""); -+ if (credential.otp != null) { -+ cipher.login.totp = this.getValueOrDefault(credential.otp.secret); -+ } -+ -+ if (credential.custom_fields != null) { -+ credential.custom_fields.forEach((customField: any) => { -+ switch (customField.field_type) { -+ case "text": -+ case "password": -+ this.processKvp(cipher, customField.label, customField.value); -+ break; -+ } - }); -+ } - -- if (this.organization) { -- this.moveFoldersToCollections(result); -- } -+ this.convertToNoteIfNeeded(cipher); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -+ if (this.organization) { -+ this.moveFoldersToCollections(result); - } -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/passpackCsvImporter.ts b/jslib/common/src/importers/passpackCsvImporter.ts -index ba51490f..e1a02707 100644 ---- a/jslib/common/src/importers/passpackCsvImporter.ts -+++ b/jslib/common/src/importers/passpackCsvImporter.ts -@@ -1,97 +1,104 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - --import { CollectionView } from '../models/view/collectionView'; -+import { CollectionView } from "../models/view/collectionView"; - - export class PasspackCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -- -- results.forEach(value => { -- const tagsJson = !this.isNullOrWhitespace(value.Tags) ? JSON.parse(value.Tags) : null; -- const tags: string[] = tagsJson != null && tagsJson.tags != null && tagsJson.tags.length > 0 ? -- tagsJson.tags.map((tagJson: string) => { -- try { -- const t = JSON.parse(tagJson); -- return this.getValueOrDefault(t.tag); -- } catch { -- // Ignore error -- } -- return null; -- }).filter((t: string) => !this.isNullOrWhitespace(t)) : null; -- -- if (this.organization && tags != null && tags.length > 0) { -- tags.forEach(tag => { -- let addCollection = true; -- let collectionIndex = result.collections.length; -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- for (let i = 0; i < result.collections.length; i++) { -- if (result.collections[i].name === tag) { -- addCollection = false; -- collectionIndex = i; -- break; -- } -- } -+ results.forEach((value) => { -+ const tagsJson = !this.isNullOrWhitespace(value.Tags) ? JSON.parse(value.Tags) : null; -+ const tags: string[] = -+ tagsJson != null && tagsJson.tags != null && tagsJson.tags.length > 0 -+ ? tagsJson.tags -+ .map((tagJson: string) => { -+ try { -+ const t = JSON.parse(tagJson); -+ return this.getValueOrDefault(t.tag); -+ } catch { -+ // Ignore error -+ } -+ return null; -+ }) -+ .filter((t: string) => !this.isNullOrWhitespace(t)) -+ : null; - -- if (addCollection) { -- const collection = new CollectionView(); -- collection.name = tag; -- result.collections.push(collection); -- } -+ if (this.organization && tags != null && tags.length > 0) { -+ tags.forEach((tag) => { -+ let addCollection = true; -+ let collectionIndex = result.collections.length; - -- result.collectionRelationships.push([result.ciphers.length, collectionIndex]); -- }); -- } else if (!this.organization && tags != null && tags.length > 0) { -- this.processFolder(result, tags[0]); -+ for (let i = 0; i < result.collections.length; i++) { -+ if (result.collections[i].name === tag) { -+ addCollection = false; -+ collectionIndex = i; -+ break; - } -+ } - -- const cipher = this.initLoginCipher(); -- cipher.notes = this.getValueOrDefault(value.Notes, ''); -- cipher.notes += ('\n\n' + this.getValueOrDefault(value['Shared Notes'], '') + '\n'); -- cipher.name = this.getValueOrDefault(value['Entry Name'], '--'); -- cipher.login.username = this.getValueOrDefault(value['User ID']); -- cipher.login.password = this.getValueOrDefault(value.Password); -- cipher.login.uris = this.makeUriArray(value.URL); -+ if (addCollection) { -+ const collection = new CollectionView(); -+ collection.name = tag; -+ result.collections.push(collection); -+ } - -- if (value.__parsed_extra != null && value.__parsed_extra.length > 0) { -- value.__parsed_extra.forEach((extra: string) => { -- if (!this.isNullOrWhitespace(extra)) { -- cipher.notes += ('\n' + extra); -- } -- }); -- } -+ result.collectionRelationships.push([result.ciphers.length, collectionIndex]); -+ }); -+ } else if (!this.organization && tags != null && tags.length > 0) { -+ this.processFolder(result, tags[0]); -+ } - -- const fieldsJson = !this.isNullOrWhitespace(value['Extra Fields']) ? -- JSON.parse(value['Extra Fields']) : null; -- const fields = fieldsJson != null && fieldsJson.extraFields != null && -- fieldsJson.extraFields.length > 0 ? fieldsJson.extraFields.map((fieldJson: string) => { -- try { -- return JSON.parse(fieldJson); -- } catch { -- // Ignore error -- } -- return null; -- }) : null; -- if (fields != null) { -- fields.forEach((f: any) => { -- if (f != null) { -- this.processKvp(cipher, f.name, f.data); -- } -- }); -- } -+ const cipher = this.initLoginCipher(); -+ cipher.notes = this.getValueOrDefault(value.Notes, ""); -+ cipher.notes += "\n\n" + this.getValueOrDefault(value["Shared Notes"], "") + "\n"; -+ cipher.name = this.getValueOrDefault(value["Entry Name"], "--"); -+ cipher.login.username = this.getValueOrDefault(value["User ID"]); -+ cipher.login.password = this.getValueOrDefault(value.Password); -+ cipher.login.uris = this.makeUriArray(value.URL); - -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -+ if (value.__parsed_extra != null && value.__parsed_extra.length > 0) { -+ value.__parsed_extra.forEach((extra: string) => { -+ if (!this.isNullOrWhitespace(extra)) { -+ cipher.notes += "\n" + extra; -+ } - }); -+ } - -- result.success = true; -- return Promise.resolve(result); -- } -+ const fieldsJson = !this.isNullOrWhitespace(value["Extra Fields"]) -+ ? JSON.parse(value["Extra Fields"]) -+ : null; -+ const fields = -+ fieldsJson != null && fieldsJson.extraFields != null && fieldsJson.extraFields.length > 0 -+ ? fieldsJson.extraFields.map((fieldJson: string) => { -+ try { -+ return JSON.parse(fieldJson); -+ } catch { -+ // Ignore error -+ } -+ return null; -+ }) -+ : null; -+ if (fields != null) { -+ fields.forEach((f: any) => { -+ if (f != null) { -+ this.processKvp(cipher, f.name, f.data); -+ } -+ }); -+ } -+ -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/passwordAgentCsvImporter.ts b/jslib/common/src/importers/passwordAgentCsvImporter.ts -index b505c633..9b673c3c 100644 ---- a/jslib/common/src/importers/passwordAgentCsvImporter.ts -+++ b/jslib/common/src/importers/passwordAgentCsvImporter.ts -@@ -1,52 +1,52 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class PasswordAgentCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, false); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -- -- let newVersion = true; -- results.forEach(value => { -- if (value.length !== 5 && value.length < 9) { -- return; -- } -- const altFormat = value.length === 10 && value[0] === '0'; -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value[altFormat ? 1 : 0], '--'); -- cipher.login.username = this.getValueOrDefault(value[altFormat ? 2 : 1]); -- cipher.login.password = this.getValueOrDefault(value[altFormat ? 3 : 2]); -- if (value.length === 5) { -- newVersion = false; -- cipher.notes = this.getValueOrDefault(value[4]); -- cipher.login.uris = this.makeUriArray(value[3]); -- } else { -- const folder = this.getValueOrDefault(value[altFormat ? 9 : 8], '(None)'); -- let folderName = folder !== '(None)' ? folder.split('\\').join('/') : null; -- if (folderName != null) { -- folderName = folder.split(' > ').join('/'); -- folderName = folder.split('>').join('/'); -- } -- this.processFolder(result, folderName); -- cipher.notes = this.getValueOrDefault(value[altFormat ? 5 : 3]); -- cipher.login.uris = this.makeUriArray(value[4]); -- } -- this.convertToNoteIfNeeded(cipher); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, false); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- if (newVersion && this.organization) { -- this.moveFoldersToCollections(result); -+ let newVersion = true; -+ results.forEach((value) => { -+ if (value.length !== 5 && value.length < 9) { -+ return; -+ } -+ const altFormat = value.length === 10 && value[0] === "0"; -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(value[altFormat ? 1 : 0], "--"); -+ cipher.login.username = this.getValueOrDefault(value[altFormat ? 2 : 1]); -+ cipher.login.password = this.getValueOrDefault(value[altFormat ? 3 : 2]); -+ if (value.length === 5) { -+ newVersion = false; -+ cipher.notes = this.getValueOrDefault(value[4]); -+ cipher.login.uris = this.makeUriArray(value[3]); -+ } else { -+ const folder = this.getValueOrDefault(value[altFormat ? 9 : 8], "(None)"); -+ let folderName = folder !== "(None)" ? folder.split("\\").join("/") : null; -+ if (folderName != null) { -+ folderName = folder.split(" > ").join("/"); -+ folderName = folder.split(">").join("/"); - } -+ this.processFolder(result, folderName); -+ cipher.notes = this.getValueOrDefault(value[altFormat ? 5 : 3]); -+ cipher.login.uris = this.makeUriArray(value[4]); -+ } -+ this.convertToNoteIfNeeded(cipher); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -+ if (newVersion && this.organization) { -+ this.moveFoldersToCollections(result); - } -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/passwordBossJsonImporter.ts b/jslib/common/src/importers/passwordBossJsonImporter.ts -index 88a70ee6..73f74050 100644 ---- a/jslib/common/src/importers/passwordBossJsonImporter.ts -+++ b/jslib/common/src/importers/passwordBossJsonImporter.ts -@@ -1,123 +1,131 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - --import { CardView } from '../models/view/cardView'; --import { FolderView } from '../models/view/folderView'; -+import { CardView } from "../models/view/cardView"; -+import { FolderView } from "../models/view/folderView"; - --import { CipherType } from '../enums/cipherType'; -+import { CipherType } from "../enums/cipherType"; - - export class PasswordBossJsonImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = JSON.parse(data); -- if (results == null || results.items == null) { -- result.success = false; -- return Promise.resolve(result); -- } -- -- const foldersMap = new Map(); -- results.folders.forEach((value: any) => { -- foldersMap.set(value.id, value.name); -- }); -- const foldersIndexMap = new Map(); -- foldersMap.forEach((val, key) => { -- foldersIndexMap.set(key, result.folders.length); -- const f = new FolderView(); -- f.name = val; -- result.folders.push(f); -- }); -- -- results.items.forEach((value: any) => { -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value.name, '--'); -- cipher.login.uris = this.makeUriArray(value.login_url); -- -- if (value.folder != null && foldersIndexMap.has(value.folder)) { -- result.folderRelationships.push([result.ciphers.length, foldersIndexMap.get(value.folder)]); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = JSON.parse(data); -+ if (results == null || results.items == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- if (value.identifiers == null) { -- return; -- } -+ const foldersMap = new Map(); -+ results.folders.forEach((value: any) => { -+ foldersMap.set(value.id, value.name); -+ }); -+ const foldersIndexMap = new Map(); -+ foldersMap.forEach((val, key) => { -+ foldersIndexMap.set(key, result.folders.length); -+ const f = new FolderView(); -+ f.name = val; -+ result.folders.push(f); -+ }); -+ -+ results.items.forEach((value: any) => { -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(value.name, "--"); -+ cipher.login.uris = this.makeUriArray(value.login_url); -+ -+ if (value.folder != null && foldersIndexMap.has(value.folder)) { -+ result.folderRelationships.push([result.ciphers.length, foldersIndexMap.get(value.folder)]); -+ } -+ -+ if (value.identifiers == null) { -+ return; -+ } -+ -+ if (!this.isNullOrWhitespace(value.identifiers.notes)) { -+ cipher.notes = value.identifiers.notes.split("\\r\\n").join("\n").split("\\n").join("\n"); -+ } -+ -+ if (value.type === "CreditCard") { -+ cipher.card = new CardView(); -+ cipher.type = CipherType.Card; -+ } -+ -+ for (const property in value.identifiers) { -+ if (!value.identifiers.hasOwnProperty(property)) { -+ continue; -+ } -+ const valObj = value.identifiers[property]; -+ const val = valObj != null ? valObj.toString() : null; -+ if ( -+ this.isNullOrWhitespace(val) || -+ property === "notes" || -+ property === "ignoreItemInSecurityScore" -+ ) { -+ continue; -+ } - -- if (!this.isNullOrWhitespace(value.identifiers.notes)) { -- cipher.notes = value.identifiers.notes.split('\\r\\n').join('\n').split('\\n').join('\n'); -- } -+ if (property === "custom_fields") { -+ valObj.forEach((cf: any) => { -+ this.processKvp(cipher, cf.name, cf.value); -+ }); -+ continue; -+ } - -- if (value.type === 'CreditCard') { -- cipher.card = new CardView(); -- cipher.type = CipherType.Card; -+ if (cipher.type === CipherType.Card) { -+ if (property === "cardNumber") { -+ cipher.card.number = val; -+ cipher.card.brand = this.getCardBrand(val); -+ continue; -+ } else if (property === "nameOnCard") { -+ cipher.card.cardholderName = val; -+ continue; -+ } else if (property === "security_code") { -+ cipher.card.code = val; -+ continue; -+ } else if (property === "expires") { -+ try { -+ const expDate = new Date(val); -+ cipher.card.expYear = expDate.getFullYear().toString(); -+ cipher.card.expMonth = (expDate.getMonth() + 1).toString(); -+ } catch { -+ // Ignore error - } -+ continue; -+ } else if (property === "cardType") { -+ continue; -+ } -+ } else { -+ if ( -+ (property === "username" || property === "email") && -+ this.isNullOrWhitespace(cipher.login.username) -+ ) { -+ cipher.login.username = val; -+ continue; -+ } else if (property === "password") { -+ cipher.login.password = val; -+ continue; -+ } else if (property === "totp") { -+ cipher.login.totp = val; -+ continue; -+ } else if ( -+ (cipher.login.uris == null || cipher.login.uris.length === 0) && -+ this.uriFieldNames.indexOf(property) > -1 -+ ) { -+ cipher.login.uris = this.makeUriArray(val); -+ continue; -+ } -+ } - -- for (const property in value.identifiers) { -- if (!value.identifiers.hasOwnProperty(property)) { -- continue; -- } -- const valObj = value.identifiers[property]; -- const val = valObj != null ? valObj.toString() : null; -- if (this.isNullOrWhitespace(val) || property === 'notes' || property === 'ignoreItemInSecurityScore') { -- continue; -- } -- -- if (property === 'custom_fields') { -- valObj.forEach((cf: any) => { -- this.processKvp(cipher, cf.name, cf.value); -- }); -- continue; -- } -- -- if (cipher.type === CipherType.Card) { -- if (property === 'cardNumber') { -- cipher.card.number = val; -- cipher.card.brand = this.getCardBrand(val); -- continue; -- } else if (property === 'nameOnCard') { -- cipher.card.cardholderName = val; -- continue; -- } else if (property === 'security_code') { -- cipher.card.code = val; -- continue; -- } else if (property === 'expires') { -- try { -- const expDate = new Date(val); -- cipher.card.expYear = expDate.getFullYear().toString(); -- cipher.card.expMonth = (expDate.getMonth() + 1).toString(); -- } catch { -- // Ignore error -- } -- continue; -- } else if (property === 'cardType') { -- continue; -- } -- } else { -- if ((property === 'username' || property === 'email') && -- this.isNullOrWhitespace(cipher.login.username)) { -- cipher.login.username = val; -- continue; -- } else if (property === 'password') { -- cipher.login.password = val; -- continue; -- } else if (property === 'totp') { -- cipher.login.totp = val; -- continue; -- } else if ((cipher.login.uris == null || cipher.login.uris.length === 0) && -- this.uriFieldNames.indexOf(property) > -1) { -- cipher.login.uris = this.makeUriArray(val); -- continue; -- } -- } -- -- this.processKvp(cipher, property, val); -- } -+ this.processKvp(cipher, property, val); -+ } - -- this.convertToNoteIfNeeded(cipher); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ this.convertToNoteIfNeeded(cipher); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -- } -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/passwordDragonXmlImporter.ts b/jslib/common/src/importers/passwordDragonXmlImporter.ts -index 15110b82..74cbe960 100644 ---- a/jslib/common/src/importers/passwordDragonXmlImporter.ts -+++ b/jslib/common/src/importers/passwordDragonXmlImporter.ts -@@ -1,57 +1,63 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class PasswordDragonXmlImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const doc = this.parseXml(data); -- if (doc == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const doc = this.parseXml(data); -+ if (doc == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- const records = doc.querySelectorAll('PasswordManager > record'); -- Array.from(records).forEach(record => { -- const category = this.querySelectorDirectChild(record, 'Category'); -- const categoryText = category != null && !this.isNullOrWhitespace(category.textContent) && -- category.textContent !== 'Unfiled' ? category.textContent : null; -- this.processFolder(result, categoryText); -- -- const accountName = this.querySelectorDirectChild(record, 'Account-Name'); -- const userId = this.querySelectorDirectChild(record, 'User-Id'); -- const password = this.querySelectorDirectChild(record, 'Password'); -- const url = this.querySelectorDirectChild(record, 'URL'); -- const notes = this.querySelectorDirectChild(record, 'Notes'); -- const cipher = this.initLoginCipher(); -- cipher.name = accountName != null ? this.getValueOrDefault(accountName.textContent, '--') : '--'; -- cipher.notes = notes != null ? this.getValueOrDefault(notes.textContent) : ''; -- cipher.login.username = userId != null ? this.getValueOrDefault(userId.textContent) : null; -- cipher.login.password = password != null ? this.getValueOrDefault(password.textContent) : null; -- cipher.login.uris = url != null ? this.makeUriArray(url.textContent) : null; -- -- const attributes: string[] = []; -- for (let i = 1; i <= 10; i++) { -- attributes.push('Attribute-' + i); -- } -- -- this.querySelectorAllDirectChild(record, attributes.join(',')).forEach(attr => { -- if (this.isNullOrWhitespace(attr.textContent) || attr.textContent === 'null') { -- return; -- } -- this.processKvp(cipher, attr.tagName, attr.textContent); -- }); -- -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -- -- if (this.organization) { -- this.moveFoldersToCollections(result); -+ const records = doc.querySelectorAll("PasswordManager > record"); -+ Array.from(records).forEach((record) => { -+ const category = this.querySelectorDirectChild(record, "Category"); -+ const categoryText = -+ category != null && -+ !this.isNullOrWhitespace(category.textContent) && -+ category.textContent !== "Unfiled" -+ ? category.textContent -+ : null; -+ this.processFolder(result, categoryText); -+ -+ const accountName = this.querySelectorDirectChild(record, "Account-Name"); -+ const userId = this.querySelectorDirectChild(record, "User-Id"); -+ const password = this.querySelectorDirectChild(record, "Password"); -+ const url = this.querySelectorDirectChild(record, "URL"); -+ const notes = this.querySelectorDirectChild(record, "Notes"); -+ const cipher = this.initLoginCipher(); -+ cipher.name = -+ accountName != null ? this.getValueOrDefault(accountName.textContent, "--") : "--"; -+ cipher.notes = notes != null ? this.getValueOrDefault(notes.textContent) : ""; -+ cipher.login.username = userId != null ? this.getValueOrDefault(userId.textContent) : null; -+ cipher.login.password = -+ password != null ? this.getValueOrDefault(password.textContent) : null; -+ cipher.login.uris = url != null ? this.makeUriArray(url.textContent) : null; -+ -+ const attributes: string[] = []; -+ for (let i = 1; i <= 10; i++) { -+ attributes.push("Attribute-" + i); -+ } -+ -+ this.querySelectorAllDirectChild(record, attributes.join(",")).forEach((attr) => { -+ if (this.isNullOrWhitespace(attr.textContent) || attr.textContent === "null") { -+ return; - } -+ this.processKvp(cipher, attr.tagName, attr.textContent); -+ }); - -- result.success = true; -- return Promise.resolve(result); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); -+ -+ if (this.organization) { -+ this.moveFoldersToCollections(result); - } -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/passwordSafeXmlImporter.ts b/jslib/common/src/importers/passwordSafeXmlImporter.ts -index e8e60731..55ae93ed 100644 ---- a/jslib/common/src/importers/passwordSafeXmlImporter.ts -+++ b/jslib/common/src/importers/passwordSafeXmlImporter.ts -@@ -1,62 +1,69 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class PasswordSafeXmlImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const doc = this.parseXml(data); -- if (doc == null) { -- result.success = false; -- return Promise.resolve(result); -- } -- -- const passwordSafe = doc.querySelector('passwordsafe'); -- if (passwordSafe == null) { -- result.errorMessage = 'Missing `passwordsafe` node.'; -- result.success = false; -- return Promise.resolve(result); -- } -- -- const notesDelimiter = passwordSafe.getAttribute('delimiter'); -- const entries = doc.querySelectorAll('passwordsafe > entry'); -- Array.from(entries).forEach(entry => { -- const group = this.querySelectorDirectChild(entry, 'group'); -- const groupText = group != null && !this.isNullOrWhitespace(group.textContent) ? -- group.textContent.split('.').join('/') : null; -- this.processFolder(result, groupText); -- -- const title = this.querySelectorDirectChild(entry, 'title'); -- const username = this.querySelectorDirectChild(entry, 'username'); -- const email = this.querySelectorDirectChild(entry, 'email'); -- const password = this.querySelectorDirectChild(entry, 'password'); -- const url = this.querySelectorDirectChild(entry, 'url'); -- const notes = this.querySelectorDirectChild(entry, 'notes'); -- const cipher = this.initLoginCipher(); -- cipher.name = title != null ? this.getValueOrDefault(title.textContent, '--') : '--'; -- cipher.notes = notes != null ? -- this.getValueOrDefault(notes.textContent, '').split(notesDelimiter).join('\n') : null; -- cipher.login.username = username != null ? this.getValueOrDefault(username.textContent) : null; -- cipher.login.password = password != null ? this.getValueOrDefault(password.textContent) : null; -- cipher.login.uris = url != null ? this.makeUriArray(url.textContent) : null; -- -- if (this.isNullOrWhitespace(cipher.login.username) && email != null) { -- cipher.login.username = this.getValueOrDefault(email.textContent); -- } else if (email != null && !this.isNullOrWhitespace(email.textContent)) { -- cipher.notes = this.isNullOrWhitespace(cipher.notes) ? 'Email: ' + email.textContent -- : (cipher.notes + '\n' + 'Email: ' + email.textContent); -- } -- -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -- -- if (this.organization) { -- this.moveFoldersToCollections(result); -- } -- -- result.success = true; -- return Promise.resolve(result); -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const doc = this.parseXml(data); -+ if (doc == null) { -+ result.success = false; -+ return Promise.resolve(result); - } -+ -+ const passwordSafe = doc.querySelector("passwordsafe"); -+ if (passwordSafe == null) { -+ result.errorMessage = "Missing `passwordsafe` node."; -+ result.success = false; -+ return Promise.resolve(result); -+ } -+ -+ const notesDelimiter = passwordSafe.getAttribute("delimiter"); -+ const entries = doc.querySelectorAll("passwordsafe > entry"); -+ Array.from(entries).forEach((entry) => { -+ const group = this.querySelectorDirectChild(entry, "group"); -+ const groupText = -+ group != null && !this.isNullOrWhitespace(group.textContent) -+ ? group.textContent.split(".").join("/") -+ : null; -+ this.processFolder(result, groupText); -+ -+ const title = this.querySelectorDirectChild(entry, "title"); -+ const username = this.querySelectorDirectChild(entry, "username"); -+ const email = this.querySelectorDirectChild(entry, "email"); -+ const password = this.querySelectorDirectChild(entry, "password"); -+ const url = this.querySelectorDirectChild(entry, "url"); -+ const notes = this.querySelectorDirectChild(entry, "notes"); -+ const cipher = this.initLoginCipher(); -+ cipher.name = title != null ? this.getValueOrDefault(title.textContent, "--") : "--"; -+ cipher.notes = -+ notes != null -+ ? this.getValueOrDefault(notes.textContent, "").split(notesDelimiter).join("\n") -+ : null; -+ cipher.login.username = -+ username != null ? this.getValueOrDefault(username.textContent) : null; -+ cipher.login.password = -+ password != null ? this.getValueOrDefault(password.textContent) : null; -+ cipher.login.uris = url != null ? this.makeUriArray(url.textContent) : null; -+ -+ if (this.isNullOrWhitespace(cipher.login.username) && email != null) { -+ cipher.login.username = this.getValueOrDefault(email.textContent); -+ } else if (email != null && !this.isNullOrWhitespace(email.textContent)) { -+ cipher.notes = this.isNullOrWhitespace(cipher.notes) -+ ? "Email: " + email.textContent -+ : cipher.notes + "\n" + "Email: " + email.textContent; -+ } -+ -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); -+ -+ if (this.organization) { -+ this.moveFoldersToCollections(result); -+ } -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/passwordWalletTxtImporter.ts b/jslib/common/src/importers/passwordWalletTxtImporter.ts -index 8a279562..eff71957 100644 ---- a/jslib/common/src/importers/passwordWalletTxtImporter.ts -+++ b/jslib/common/src/importers/passwordWalletTxtImporter.ts -@@ -1,47 +1,47 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class PasswordWalletTxtImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, false); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -- -- results.forEach(value => { -- if (value.length < 1) { -- return; -- } -- if (value.length > 5) { -- this.processFolder(result, value[5]); -- } -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value[0], '--'); -- if (value.length > 4) { -- cipher.notes = this.getValueOrDefault(value[4], '').split('¬').join('\n'); -- } -- if (value.length > 2) { -- cipher.login.username = this.getValueOrDefault(value[2]); -- } -- if (value.length > 3) { -- cipher.login.password = this.getValueOrDefault(value[3]); -- } -- if (value.length > 1) { -- cipher.login.uris = this.makeUriArray(value[1]); -- } -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, false); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- if (this.organization) { -- this.moveFoldersToCollections(result); -- } -+ results.forEach((value) => { -+ if (value.length < 1) { -+ return; -+ } -+ if (value.length > 5) { -+ this.processFolder(result, value[5]); -+ } -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(value[0], "--"); -+ if (value.length > 4) { -+ cipher.notes = this.getValueOrDefault(value[4], "").split("¬").join("\n"); -+ } -+ if (value.length > 2) { -+ cipher.login.username = this.getValueOrDefault(value[2]); -+ } -+ if (value.length > 3) { -+ cipher.login.password = this.getValueOrDefault(value[3]); -+ } -+ if (value.length > 1) { -+ cipher.login.uris = this.makeUriArray(value[1]); -+ } -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -+ if (this.organization) { -+ this.moveFoldersToCollections(result); - } -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/rememBearCsvImporter.ts b/jslib/common/src/importers/rememBearCsvImporter.ts -index 2388ea0f..2df49cfa 100644 ---- a/jslib/common/src/importers/rememBearCsvImporter.ts -+++ b/jslib/common/src/importers/rememBearCsvImporter.ts -@@ -1,77 +1,77 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { CipherType } from '../enums/cipherType'; -+import { CipherType } from "../enums/cipherType"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - --import { CardView } from '../models/view/cardView'; -+import { CardView } from "../models/view/cardView"; - - export class RememBearCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -- -- results.forEach(value => { -- if (value.trash === 'true') { -- return; -- } -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value.name); -- cipher.notes = this.getValueOrDefault(value.notes); -- if (value.type === 'LoginItem') { -- cipher.login.uris = this.makeUriArray(value.website); -- cipher.login.password = this.getValueOrDefault(value.password); -- cipher.login.username = this.getValueOrDefault(value.username); -- } else if (value.type === 'CreditCardItem') { -- cipher.type = CipherType.Card; -- cipher.card = new CardView(); -- cipher.card.cardholderName = this.getValueOrDefault(value.cardholder); -- cipher.card.number = this.getValueOrDefault(value.number); -- cipher.card.brand = this.getCardBrand(cipher.card.number); -- cipher.card.code = this.getValueOrDefault(value.verification); -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- try { -- const expMonth = this.getValueOrDefault(value.expiryMonth); -- if (expMonth != null) { -- const expMonthNumber = parseInt(expMonth, null); -- if (expMonthNumber != null && expMonthNumber >= 1 && expMonthNumber <= 12) { -- cipher.card.expMonth = expMonthNumber.toString(); -- } -- } -- } catch { -- // Ignore error -- } -- try { -- const expYear = this.getValueOrDefault(value.expiryYear); -- if (expYear != null) { -- const expYearNumber = parseInt(expYear, null); -- if (expYearNumber != null) { -- cipher.card.expYear = expYearNumber.toString(); -- } -- } -- } catch { -- // Ignore error -- } -+ results.forEach((value) => { -+ if (value.trash === "true") { -+ return; -+ } -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(value.name); -+ cipher.notes = this.getValueOrDefault(value.notes); -+ if (value.type === "LoginItem") { -+ cipher.login.uris = this.makeUriArray(value.website); -+ cipher.login.password = this.getValueOrDefault(value.password); -+ cipher.login.username = this.getValueOrDefault(value.username); -+ } else if (value.type === "CreditCardItem") { -+ cipher.type = CipherType.Card; -+ cipher.card = new CardView(); -+ cipher.card.cardholderName = this.getValueOrDefault(value.cardholder); -+ cipher.card.number = this.getValueOrDefault(value.number); -+ cipher.card.brand = this.getCardBrand(cipher.card.number); -+ cipher.card.code = this.getValueOrDefault(value.verification); - -- const pin = this.getValueOrDefault(value.pin); -- if (pin != null) { -- this.processKvp(cipher, 'PIN', pin); -- } -- const zip = this.getValueOrDefault(value.zipCode); -- if (zip != null) { -- this.processKvp(cipher, 'Zip Code', zip); -- } -+ try { -+ const expMonth = this.getValueOrDefault(value.expiryMonth); -+ if (expMonth != null) { -+ const expMonthNumber = parseInt(expMonth, null); -+ if (expMonthNumber != null && expMonthNumber >= 1 && expMonthNumber <= 12) { -+ cipher.card.expMonth = expMonthNumber.toString(); -+ } -+ } -+ } catch { -+ // Ignore error -+ } -+ try { -+ const expYear = this.getValueOrDefault(value.expiryYear); -+ if (expYear != null) { -+ const expYearNumber = parseInt(expYear, null); -+ if (expYearNumber != null) { -+ cipher.card.expYear = expYearNumber.toString(); - } -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ } -+ } catch { -+ // Ignore error -+ } - -- result.success = true; -- return Promise.resolve(result); -- } -+ const pin = this.getValueOrDefault(value.pin); -+ if (pin != null) { -+ this.processKvp(cipher, "PIN", pin); -+ } -+ const zip = this.getValueOrDefault(value.zipCode); -+ if (zip != null) { -+ this.processKvp(cipher, "Zip Code", zip); -+ } -+ } -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/roboformCsvImporter.ts b/jslib/common/src/importers/roboformCsvImporter.ts -index cb0c0de8..6c90dbfd 100644 ---- a/jslib/common/src/importers/roboformCsvImporter.ts -+++ b/jslib/common/src/importers/roboformCsvImporter.ts -@@ -1,63 +1,69 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class RoboFormCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- let i = 1; -- results.forEach(value => { -- const folder = !this.isNullOrWhitespace(value.Folder) && value.Folder.startsWith('/') ? -- value.Folder.replace('/', '') : value.Folder; -- const folderName = !this.isNullOrWhitespace(folder) ? folder : null; -- this.processFolder(result, folderName); -- -- const cipher = this.initLoginCipher(); -- cipher.notes = this.getValueOrDefault(value.Note); -- cipher.name = this.getValueOrDefault(value.Name, '--'); -- cipher.login.username = this.getValueOrDefault(value.Login); -- cipher.login.password = this.getValueOrDefault(value.Pwd); -- cipher.login.uris = this.makeUriArray(value.Url); -- -- if (!this.isNullOrWhitespace(value.Rf_fields)) { -- let fields: string[] = [value.Rf_fields]; -- if (value.__parsed_extra != null && value.__parsed_extra.length > 0) { -- fields = fields.concat(value.__parsed_extra); -- } -- fields.forEach((field: string) => { -- const parts = field.split(':'); -- if (parts.length < 3) { -- return; -- } -- const key = parts[0] === '-no-name-' ? null : parts[0]; -- const val = parts.length === 4 && parts[2] === 'rck' ? parts[1] : parts[2]; -- this.processKvp(cipher, key, val); -- }); -- } -- -- this.convertToNoteIfNeeded(cipher); -- this.cleanupCipher(cipher); -- -- if (i === results.length && cipher.name === '--' && this.isNullOrWhitespace(cipher.login.password)) { -- return; -- } -- -- result.ciphers.push(cipher); -- i++; -- }); -+ let i = 1; -+ results.forEach((value) => { -+ const folder = -+ !this.isNullOrWhitespace(value.Folder) && value.Folder.startsWith("/") -+ ? value.Folder.replace("/", "") -+ : value.Folder; -+ const folderName = !this.isNullOrWhitespace(folder) ? folder : null; -+ this.processFolder(result, folderName); - -- if (this.organization) { -- this.moveFoldersToCollections(result); -+ const cipher = this.initLoginCipher(); -+ cipher.notes = this.getValueOrDefault(value.Note); -+ cipher.name = this.getValueOrDefault(value.Name, "--"); -+ cipher.login.username = this.getValueOrDefault(value.Login); -+ cipher.login.password = this.getValueOrDefault(value.Pwd); -+ cipher.login.uris = this.makeUriArray(value.Url); -+ -+ if (!this.isNullOrWhitespace(value.Rf_fields)) { -+ let fields: string[] = [value.Rf_fields]; -+ if (value.__parsed_extra != null && value.__parsed_extra.length > 0) { -+ fields = fields.concat(value.__parsed_extra); - } -+ fields.forEach((field: string) => { -+ const parts = field.split(":"); -+ if (parts.length < 3) { -+ return; -+ } -+ const key = parts[0] === "-no-name-" ? null : parts[0]; -+ const val = parts.length === 4 && parts[2] === "rck" ? parts[1] : parts[2]; -+ this.processKvp(cipher, key, val); -+ }); -+ } - -- result.success = true; -- return Promise.resolve(result); -+ this.convertToNoteIfNeeded(cipher); -+ this.cleanupCipher(cipher); -+ -+ if ( -+ i === results.length && -+ cipher.name === "--" && -+ this.isNullOrWhitespace(cipher.login.password) -+ ) { -+ return; -+ } -+ -+ result.ciphers.push(cipher); -+ i++; -+ }); -+ -+ if (this.organization) { -+ this.moveFoldersToCollections(result); - } -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/safariCsvImporter.ts b/jslib/common/src/importers/safariCsvImporter.ts -index 64a30b3a..1d32c92a 100644 ---- a/jslib/common/src/importers/safariCsvImporter.ts -+++ b/jslib/common/src/importers/safariCsvImporter.ts -@@ -1,29 +1,29 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class SafariCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- results.forEach(value => { -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value.Title, '--'); -- cipher.login.username = this.getValueOrDefault(value.Username); -- cipher.login.password = this.getValueOrDefault(value.Password); -- cipher.login.uris = this.makeUriArray(value.Url); -- cipher.login.totp = this.getValueOrDefault(value.OTPAuth); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ results.forEach((value) => { -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(value.Title, "--"); -+ cipher.login.username = this.getValueOrDefault(value.Username); -+ cipher.login.password = this.getValueOrDefault(value.Password); -+ cipher.login.uris = this.makeUriArray(value.Url); -+ cipher.login.totp = this.getValueOrDefault(value.OTPAuth); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -- } -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/safeInCloudXmlImporter.ts b/jslib/common/src/importers/safeInCloudXmlImporter.ts -index 93339a66..82514e24 100644 ---- a/jslib/common/src/importers/safeInCloudXmlImporter.ts -+++ b/jslib/common/src/importers/safeInCloudXmlImporter.ts -@@ -1,136 +1,135 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - --import { FolderView } from '../models/view/folderView'; --import { SecureNoteView } from '../models/view/secureNoteView'; -+import { FolderView } from "../models/view/folderView"; -+import { SecureNoteView } from "../models/view/secureNoteView"; - --import { CipherType } from '../enums/cipherType'; --import { SecureNoteType } from '../enums/secureNoteType'; -+import { CipherType } from "../enums/cipherType"; -+import { SecureNoteType } from "../enums/secureNoteType"; - --import { FieldType } from '../enums/fieldType'; --import { CipherView } from '../models/view/cipherView'; --import { FieldView } from '../models/view/fieldView'; -+import { FieldType } from "../enums/fieldType"; -+import { CipherView } from "../models/view/cipherView"; -+import { FieldView } from "../models/view/fieldView"; - - export class SafeInCloudXmlImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const doc = this.parseXml(data); -- if (doc == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const doc = this.parseXml(data); -+ if (doc == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- const db = doc.querySelector('database'); -- if (db == null) { -- result.errorMessage = 'Missing `database` node.'; -- result.success = false; -- return Promise.resolve(result); -- } -+ const db = doc.querySelector("database"); -+ if (db == null) { -+ result.errorMessage = "Missing `database` node."; -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- const foldersMap = new Map(); -- -- Array.from(doc.querySelectorAll('database > label')).forEach(labelEl => { -- const name = labelEl.getAttribute('name'); -- const id = labelEl.getAttribute('id'); -- if (!this.isNullOrWhitespace(name) && !this.isNullOrWhitespace(id)) { -- foldersMap.set(id, result.folders.length); -- const folder = new FolderView(); -- folder.name = name; -- result.folders.push(folder); -- } -+ const foldersMap = new Map(); -+ -+ Array.from(doc.querySelectorAll("database > label")).forEach((labelEl) => { -+ const name = labelEl.getAttribute("name"); -+ const id = labelEl.getAttribute("id"); -+ if (!this.isNullOrWhitespace(name) && !this.isNullOrWhitespace(id)) { -+ foldersMap.set(id, result.folders.length); -+ const folder = new FolderView(); -+ folder.name = name; -+ result.folders.push(folder); -+ } -+ }); -+ -+ Array.from(doc.querySelectorAll("database > card")).forEach((cardEl) => { -+ if (cardEl.getAttribute("template") === "true" || cardEl.getAttribute("deleted") === "true") { -+ return; -+ } -+ -+ const labelIdEl = this.querySelectorDirectChild(cardEl, "label_id"); -+ if (labelIdEl != null) { -+ const labelId = labelIdEl.textContent; -+ if (!this.isNullOrWhitespace(labelId) && foldersMap.has(labelId)) { -+ result.folderRelationships.push([result.ciphers.length, foldersMap.get(labelId)]); -+ } -+ } -+ -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(cardEl.getAttribute("title"), "--"); -+ -+ if (cardEl.getAttribute("star") === "true") { -+ cipher.favorite = true; -+ } -+ -+ const cardType = cardEl.getAttribute("type"); -+ if (cardType === "note") { -+ cipher.type = CipherType.SecureNote; -+ cipher.secureNote = new SecureNoteView(); -+ cipher.secureNote.type = SecureNoteType.Generic; -+ } else { -+ Array.from(this.querySelectorAllDirectChild(cardEl, "field")).forEach((fieldEl) => { -+ const text = fieldEl.textContent; -+ if (this.isNullOrWhitespace(text)) { -+ return; -+ } -+ const name = fieldEl.getAttribute("name"); -+ const fieldType = this.getValueOrDefault(fieldEl.getAttribute("type"), "").toLowerCase(); -+ if (fieldType === "login") { -+ cipher.login.username = text; -+ } else if (fieldType === "password" || fieldType === "secret") { -+ // safeInCloud allows for more than one password. we just insert them here and find the one used as password later -+ this.processKvp(cipher, name, text, FieldType.Hidden); -+ } else if (fieldType === "one_time_password") { -+ cipher.login.totp = text; -+ } else if (fieldType === "notes") { -+ cipher.notes += text + "\n"; -+ } else if (fieldType === "weblogin" || fieldType === "website") { -+ cipher.login.uris = this.makeUriArray(text); -+ } else { -+ this.processKvp(cipher, name, text); -+ } - }); -+ } - -- Array.from(doc.querySelectorAll('database > card')).forEach(cardEl => { -- if (cardEl.getAttribute('template') === 'true' || cardEl.getAttribute('deleted') === 'true') { -- return; -- } -- -- const labelIdEl = this.querySelectorDirectChild(cardEl, 'label_id'); -- if (labelIdEl != null) { -- const labelId = labelIdEl.textContent; -- if (!this.isNullOrWhitespace(labelId) && foldersMap.has(labelId)) { -- result.folderRelationships.push([result.ciphers.length, foldersMap.get(labelId)]); -- } -- } -- -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(cardEl.getAttribute('title'), '--'); -- -- if (cardEl.getAttribute('star') === 'true') { -- cipher.favorite = true; -- } -- -- const cardType = cardEl.getAttribute('type'); -- if (cardType === 'note') { -- cipher.type = CipherType.SecureNote; -- cipher.secureNote = new SecureNoteView(); -- cipher.secureNote.type = SecureNoteType.Generic; -- } else { -- Array.from(this.querySelectorAllDirectChild(cardEl, 'field')).forEach(fieldEl => { -- const text = fieldEl.textContent; -- if (this.isNullOrWhitespace(text)) { -- return; -- } -- const name = fieldEl.getAttribute('name'); -- const fieldType = this.getValueOrDefault(fieldEl.getAttribute('type'), '').toLowerCase(); -- if (fieldType === 'login') { -- cipher.login.username = text; -- } else if (fieldType === 'password' || fieldType === 'secret') { -- // safeInCloud allows for more than one password. we just insert them here and find the one used as password later -- this.processKvp(cipher, name, text, FieldType.Hidden); -- } else if (fieldType === 'one_time_password') { -- cipher.login.totp = text; -- } else if (fieldType === 'notes') { -- cipher.notes += (text + '\n'); -- } else if (fieldType === 'weblogin' || fieldType === 'website') { -- cipher.login.uris = this.makeUriArray(text); -- } -- else { -- this.processKvp(cipher, name, text); -- } -- }); -- } -- -- Array.from(this.querySelectorAllDirectChild(cardEl, 'notes')).forEach(notesEl => { -- cipher.notes += (notesEl.textContent + '\n'); -- }); -- -- this.setPassword(cipher); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ Array.from(this.querySelectorAllDirectChild(cardEl, "notes")).forEach((notesEl) => { -+ cipher.notes += notesEl.textContent + "\n"; -+ }); - -- if (this.organization) { -- this.moveFoldersToCollections(result); -- } -+ this.setPassword(cipher); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -+ if (this.organization) { -+ this.moveFoldersToCollections(result); - } - -- // Choose a password from all passwords. Take one that has password in its name, or the first one if there is no such entry -- // if its name is password, we can safely remove it form the fields. otherwise, it would maybe be best to keep it as a hidden field -- setPassword(cipher: CipherView) { -- const candidates = cipher.fields.filter(field => field.type === FieldType.Hidden); -- if (!candidates.length) { -- return; -- } -+ result.success = true; -+ return Promise.resolve(result); -+ } - -- let choice: FieldView; -- for (const field of candidates) { -- if (this.passwordFieldNames.includes(field.name.toLowerCase())) { -- choice = field; -- cipher.fields = cipher.fields.filter(f => f !== choice); -- break; -- } -- } -+ // Choose a password from all passwords. Take one that has password in its name, or the first one if there is no such entry -+ // if its name is password, we can safely remove it form the fields. otherwise, it would maybe be best to keep it as a hidden field -+ setPassword(cipher: CipherView) { -+ const candidates = cipher.fields.filter((field) => field.type === FieldType.Hidden); -+ if (!candidates.length) { -+ return; -+ } - -- if (!choice) { -- choice = candidates[0]; -- } -+ let choice: FieldView; -+ for (const field of candidates) { -+ if (this.passwordFieldNames.includes(field.name.toLowerCase())) { -+ choice = field; -+ cipher.fields = cipher.fields.filter((f) => f !== choice); -+ break; -+ } -+ } - -- cipher.login.password = choice.value; -+ if (!choice) { -+ choice = candidates[0]; - } -+ -+ cipher.login.password = choice.value; -+ } - } -diff --git a/jslib/common/src/importers/saferpassCsvImport.ts b/jslib/common/src/importers/saferpassCsvImport.ts -index 1273e741..435a1a37 100644 ---- a/jslib/common/src/importers/saferpassCsvImport.ts -+++ b/jslib/common/src/importers/saferpassCsvImport.ts -@@ -1,29 +1,29 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class SaferPassCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- results.forEach(value => { -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(this.nameFromUrl(value.url), '--'); -- cipher.notes = this.getValueOrDefault(value.notes); -- cipher.login.username = this.getValueOrDefault(value.username); -- cipher.login.password = this.getValueOrDefault(value.password); -- cipher.login.uris = this.makeUriArray(value.url); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ results.forEach((value) => { -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(this.nameFromUrl(value.url), "--"); -+ cipher.notes = this.getValueOrDefault(value.notes); -+ cipher.login.username = this.getValueOrDefault(value.username); -+ cipher.login.password = this.getValueOrDefault(value.password); -+ cipher.login.uris = this.makeUriArray(value.url); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -- } -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/secureSafeCsvImporter.ts b/jslib/common/src/importers/secureSafeCsvImporter.ts -index fc15efec..540177a0 100644 ---- a/jslib/common/src/importers/secureSafeCsvImporter.ts -+++ b/jslib/common/src/importers/secureSafeCsvImporter.ts -@@ -1,29 +1,29 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class SecureSafeCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- results.forEach(value => { -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value.Title); -- cipher.notes = this.getValueOrDefault(value.Comment); -- cipher.login.uris = this.makeUriArray(value.Url); -- cipher.login.password = this.getValueOrDefault(value.Password); -- cipher.login.username = this.getValueOrDefault(value.Username); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ results.forEach((value) => { -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(value.Title); -+ cipher.notes = this.getValueOrDefault(value.Comment); -+ cipher.login.uris = this.makeUriArray(value.Url); -+ cipher.login.password = this.getValueOrDefault(value.Password); -+ cipher.login.username = this.getValueOrDefault(value.Username); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -- } -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/splashIdCsvImporter.ts b/jslib/common/src/importers/splashIdCsvImporter.ts -index 4e895d55..b38380a9 100644 ---- a/jslib/common/src/importers/splashIdCsvImporter.ts -+++ b/jslib/common/src/importers/splashIdCsvImporter.ts -@@ -1,57 +1,57 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; --import { CipherView } from '../models/view/cipherView'; -+import { ImportResult } from "../models/domain/importResult"; -+import { CipherView } from "../models/view/cipherView"; - - export class SplashIdCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, false); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -- -- results.forEach(value => { -- if (value.length < 3) { -- return; -- } -- -- this.processFolder(result, this.getValueOrDefault(value[value.length - 1])); -- const cipher = this.initLoginCipher(); -- cipher.notes = this.getValueOrDefault(value[value.length - 2], ''); -- cipher.name = this.getValueOrDefault(value[1], '--'); -- -- if (value[0] === 'Web Logins' || value[0] === 'Servers' || value[0] === 'Email Accounts') { -- cipher.login.username = this.getValueOrDefault(value[2]); -- cipher.login.password = this.getValueOrDefault(value[3]); -- cipher.login.uris = this.makeUriArray(value[4]); -- this.parseFieldsToNotes(cipher, 5, value); -- } else { -- this.parseFieldsToNotes(cipher, 2, value); -- } -- -- this.convertToNoteIfNeeded(cipher); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -- -- if (this.organization) { -- this.moveFoldersToCollections(result); -- } -- -- result.success = true; -- return Promise.resolve(result); -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, false); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); - } - -- private parseFieldsToNotes(cipher: CipherView, startIndex: number, value: any) { -- // last 3 rows do not get parsed -- for (let i = startIndex; i < value.length - 3; i++) { -- if (this.isNullOrWhitespace(value[i])) { -- continue; -- } -- cipher.notes += (value[i] + '\n'); -- } -+ results.forEach((value) => { -+ if (value.length < 3) { -+ return; -+ } -+ -+ this.processFolder(result, this.getValueOrDefault(value[value.length - 1])); -+ const cipher = this.initLoginCipher(); -+ cipher.notes = this.getValueOrDefault(value[value.length - 2], ""); -+ cipher.name = this.getValueOrDefault(value[1], "--"); -+ -+ if (value[0] === "Web Logins" || value[0] === "Servers" || value[0] === "Email Accounts") { -+ cipher.login.username = this.getValueOrDefault(value[2]); -+ cipher.login.password = this.getValueOrDefault(value[3]); -+ cipher.login.uris = this.makeUriArray(value[4]); -+ this.parseFieldsToNotes(cipher, 5, value); -+ } else { -+ this.parseFieldsToNotes(cipher, 2, value); -+ } -+ -+ this.convertToNoteIfNeeded(cipher); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); -+ -+ if (this.organization) { -+ this.moveFoldersToCollections(result); - } -+ -+ result.success = true; -+ return Promise.resolve(result); -+ } -+ -+ private parseFieldsToNotes(cipher: CipherView, startIndex: number, value: any) { -+ // last 3 rows do not get parsed -+ for (let i = startIndex; i < value.length - 3; i++) { -+ if (this.isNullOrWhitespace(value[i])) { -+ continue; -+ } -+ cipher.notes += value[i] + "\n"; -+ } -+ } - } -diff --git a/jslib/common/src/importers/stickyPasswordXmlImporter.ts b/jslib/common/src/importers/stickyPasswordXmlImporter.ts -index 313ab1fc..1bfba5b6 100644 ---- a/jslib/common/src/importers/stickyPasswordXmlImporter.ts -+++ b/jslib/common/src/importers/stickyPasswordXmlImporter.ts -@@ -1,79 +1,83 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class StickyPasswordXmlImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const doc = this.parseXml(data); -- if (doc == null) { -- result.success = false; -- return Promise.resolve(result); -- } -- -- const loginNodes = doc.querySelectorAll('root > Database > Logins > Login'); -- Array.from(loginNodes).forEach(loginNode => { -- const accountId = loginNode.getAttribute('ID'); -- if (this.isNullOrWhitespace(accountId)) { -- return; -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const doc = this.parseXml(data); -+ if (doc == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- const usernameText = loginNode.getAttribute('Name'); -- const passwordText = loginNode.getAttribute('Password'); -- let titleText: string = null; -- let linkText: string = null; -- let notesText: string = null; -- let groupId: string = null; -- let groupText: string = null; -+ const loginNodes = doc.querySelectorAll("root > Database > Logins > Login"); -+ Array.from(loginNodes).forEach((loginNode) => { -+ const accountId = loginNode.getAttribute("ID"); -+ if (this.isNullOrWhitespace(accountId)) { -+ return; -+ } - -- const accountLogin = doc.querySelector('root > Database > Accounts > Account > ' + -- 'LoginLinks > Login[SourceLoginID="' + accountId + '"]'); -- if (accountLogin != null) { -- const account = accountLogin.parentElement.parentElement; -- if (account != null) { -- titleText = account.getAttribute('Name'); -- linkText = account.getAttribute('Link'); -- groupId = account.getAttribute('ParentID'); -- notesText = account.getAttribute('Comments'); -- if (!this.isNullOrWhitespace(notesText)) { -- notesText = notesText.split('/n').join('\n'); -- } -- } -- } -+ const usernameText = loginNode.getAttribute("Name"); -+ const passwordText = loginNode.getAttribute("Password"); -+ let titleText: string = null; -+ let linkText: string = null; -+ let notesText: string = null; -+ let groupId: string = null; -+ let groupText: string = null; - -- if (!this.isNullOrWhitespace(groupId)) { -- groupText = this.buildGroupText(doc, groupId, ''); -- this.processFolder(result, groupText); -- } -+ const accountLogin = doc.querySelector( -+ "root > Database > Accounts > Account > " + -+ 'LoginLinks > Login[SourceLoginID="' + -+ accountId + -+ '"]' -+ ); -+ if (accountLogin != null) { -+ const account = accountLogin.parentElement.parentElement; -+ if (account != null) { -+ titleText = account.getAttribute("Name"); -+ linkText = account.getAttribute("Link"); -+ groupId = account.getAttribute("ParentID"); -+ notesText = account.getAttribute("Comments"); -+ if (!this.isNullOrWhitespace(notesText)) { -+ notesText = notesText.split("/n").join("\n"); -+ } -+ } -+ } - -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(titleText, '--'); -- cipher.notes = this.getValueOrDefault(notesText); -- cipher.login.username = this.getValueOrDefault(usernameText); -- cipher.login.password = this.getValueOrDefault(passwordText); -- cipher.login.uris = this.makeUriArray(linkText); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ if (!this.isNullOrWhitespace(groupId)) { -+ groupText = this.buildGroupText(doc, groupId, ""); -+ this.processFolder(result, groupText); -+ } - -- if (this.organization) { -- this.moveFoldersToCollections(result); -- } -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(titleText, "--"); -+ cipher.notes = this.getValueOrDefault(notesText); -+ cipher.login.username = this.getValueOrDefault(usernameText); -+ cipher.login.password = this.getValueOrDefault(passwordText); -+ cipher.login.uris = this.makeUriArray(linkText); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -+ if (this.organization) { -+ this.moveFoldersToCollections(result); - } - -- buildGroupText(doc: Document, groupId: string, groupText: string): string { -- const group = doc.querySelector('root > Database > Groups > Group[ID="' + groupId + '"]'); -- if (group == null) { -- return groupText; -- } -- if (!this.isNullOrWhitespace(groupText)) { -- groupText = '/' + groupText; -- } -- groupText = group.getAttribute('Name') + groupText; -- return this.buildGroupText(doc, group.getAttribute('ParentID'), groupText); -+ result.success = true; -+ return Promise.resolve(result); -+ } -+ -+ buildGroupText(doc: Document, groupId: string, groupText: string): string { -+ const group = doc.querySelector('root > Database > Groups > Group[ID="' + groupId + '"]'); -+ if (group == null) { -+ return groupText; -+ } -+ if (!this.isNullOrWhitespace(groupText)) { -+ groupText = "/" + groupText; - } -+ groupText = group.getAttribute("Name") + groupText; -+ return this.buildGroupText(doc, group.getAttribute("ParentID"), groupText); -+ } - } -diff --git a/jslib/common/src/importers/truekeyCsvImporter.ts b/jslib/common/src/importers/truekeyCsvImporter.ts -index aa004b67..622ef89c 100644 ---- a/jslib/common/src/importers/truekeyCsvImporter.ts -+++ b/jslib/common/src/importers/truekeyCsvImporter.ts -@@ -1,76 +1,89 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - --import { CardView } from '../models/view/cardView'; --import { SecureNoteView } from '../models/view/secureNoteView'; -+import { CardView } from "../models/view/cardView"; -+import { SecureNoteView } from "../models/view/secureNoteView"; - --import { CipherType } from '../enums/cipherType'; --import { SecureNoteType } from '../enums/secureNoteType'; -+import { CipherType } from "../enums/cipherType"; -+import { SecureNoteType } from "../enums/secureNoteType"; - --const PropertiesToIgnore = ['kind', 'autologin', 'favorite', 'hexcolor', 'protectedwithpassword', 'subdomainonly', -- 'type', 'tk_export_version', 'note', 'title', 'document_content', -+const PropertiesToIgnore = [ -+ "kind", -+ "autologin", -+ "favorite", -+ "hexcolor", -+ "protectedwithpassword", -+ "subdomainonly", -+ "type", -+ "tk_export_version", -+ "note", -+ "title", -+ "document_content", - ]; - - export class TrueKeyCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- results.forEach(value => { -- const cipher = this.initLoginCipher(); -- cipher.favorite = this.getValueOrDefault(value.favorite, '').toLowerCase() === 'true'; -- cipher.name = this.getValueOrDefault(value.name, '--'); -- cipher.notes = this.getValueOrDefault(value.memo, ''); -- cipher.login.username = this.getValueOrDefault(value.login); -- cipher.login.password = this.getValueOrDefault(value.password); -- cipher.login.uris = this.makeUriArray(value.url); -+ results.forEach((value) => { -+ const cipher = this.initLoginCipher(); -+ cipher.favorite = this.getValueOrDefault(value.favorite, "").toLowerCase() === "true"; -+ cipher.name = this.getValueOrDefault(value.name, "--"); -+ cipher.notes = this.getValueOrDefault(value.memo, ""); -+ cipher.login.username = this.getValueOrDefault(value.login); -+ cipher.login.password = this.getValueOrDefault(value.password); -+ cipher.login.uris = this.makeUriArray(value.url); - -- if (value.kind !== 'login') { -- cipher.name = this.getValueOrDefault(value.title, '--'); -- cipher.notes = this.getValueOrDefault(value.note, ''); -- } -+ if (value.kind !== "login") { -+ cipher.name = this.getValueOrDefault(value.title, "--"); -+ cipher.notes = this.getValueOrDefault(value.note, ""); -+ } - -- if (value.kind === 'cc') { -- cipher.type = CipherType.Card; -- cipher.card = new CardView(); -- cipher.card.cardholderName = this.getValueOrDefault(value.cardholder); -- cipher.card.number = this.getValueOrDefault(value.number); -- cipher.card.brand = this.getCardBrand(cipher.card.number); -- if (!this.isNullOrWhitespace(value.expiryDate)) { -- try { -- const expDate = new Date(value.expiryDate); -- cipher.card.expYear = expDate.getFullYear().toString(); -- cipher.card.expMonth = (expDate.getMonth() + 1).toString(); -- } catch { -- // Ignore error -- } -- } -- } else if (value.kind !== 'login') { -- cipher.type = CipherType.SecureNote; -- cipher.secureNote = new SecureNoteView(); -- cipher.secureNote.type = SecureNoteType.Generic; -- if (!this.isNullOrWhitespace(cipher.notes)) { -- cipher.notes = this.getValueOrDefault(value.document_content, ''); -- } -- for (const property in value) { -- if (value.hasOwnProperty(property) && PropertiesToIgnore.indexOf(property.toLowerCase()) < 0 && -- !this.isNullOrWhitespace(value[property])) { -- this.processKvp(cipher, property, value[property]); -- } -- } -- } -+ if (value.kind === "cc") { -+ cipher.type = CipherType.Card; -+ cipher.card = new CardView(); -+ cipher.card.cardholderName = this.getValueOrDefault(value.cardholder); -+ cipher.card.number = this.getValueOrDefault(value.number); -+ cipher.card.brand = this.getCardBrand(cipher.card.number); -+ if (!this.isNullOrWhitespace(value.expiryDate)) { -+ try { -+ const expDate = new Date(value.expiryDate); -+ cipher.card.expYear = expDate.getFullYear().toString(); -+ cipher.card.expMonth = (expDate.getMonth() + 1).toString(); -+ } catch { -+ // Ignore error -+ } -+ } -+ } else if (value.kind !== "login") { -+ cipher.type = CipherType.SecureNote; -+ cipher.secureNote = new SecureNoteView(); -+ cipher.secureNote.type = SecureNoteType.Generic; -+ if (!this.isNullOrWhitespace(cipher.notes)) { -+ cipher.notes = this.getValueOrDefault(value.document_content, ""); -+ } -+ for (const property in value) { -+ if ( -+ value.hasOwnProperty(property) && -+ PropertiesToIgnore.indexOf(property.toLowerCase()) < 0 && -+ !this.isNullOrWhitespace(value[property]) -+ ) { -+ this.processKvp(cipher, property, value[property]); -+ } -+ } -+ } - -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -- } -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/upmCsvImporter.ts b/jslib/common/src/importers/upmCsvImporter.ts -index 23e4ece3..a13c5be6 100644 ---- a/jslib/common/src/importers/upmCsvImporter.ts -+++ b/jslib/common/src/importers/upmCsvImporter.ts -@@ -1,32 +1,32 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class UpmCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, false); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, false); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- results.forEach(value => { -- if (value.length !== 5) { -- return; -- } -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value[0], '--'); -- cipher.notes = this.getValueOrDefault(value[4]); -- cipher.login.username = this.getValueOrDefault(value[1]); -- cipher.login.password = this.getValueOrDefault(value[2]); -- cipher.login.uris = this.makeUriArray(value[3]); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ results.forEach((value) => { -+ if (value.length !== 5) { -+ return; -+ } -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(value[0], "--"); -+ cipher.notes = this.getValueOrDefault(value[4]); -+ cipher.login.username = this.getValueOrDefault(value[1]); -+ cipher.login.password = this.getValueOrDefault(value[2]); -+ cipher.login.uris = this.makeUriArray(value[3]); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -- } -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/yotiCsvImporter.ts b/jslib/common/src/importers/yotiCsvImporter.ts -index 56a80d5d..fedc1c81 100644 ---- a/jslib/common/src/importers/yotiCsvImporter.ts -+++ b/jslib/common/src/importers/yotiCsvImporter.ts -@@ -1,28 +1,28 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - - export class YotiCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- results.forEach(value => { -- const cipher = this.initLoginCipher(); -- cipher.name = this.getValueOrDefault(value.Name, '--'); -- cipher.login.username = this.getValueOrDefault(value['User name']); -- cipher.login.password = this.getValueOrDefault(value.Password); -- cipher.login.uris = this.makeUriArray(value.URL); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ results.forEach((value) => { -+ const cipher = this.initLoginCipher(); -+ cipher.name = this.getValueOrDefault(value.Name, "--"); -+ cipher.login.username = this.getValueOrDefault(value["User name"]); -+ cipher.login.password = this.getValueOrDefault(value.Password); -+ cipher.login.uris = this.makeUriArray(value.URL); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -- } -+ result.success = true; -+ return Promise.resolve(result); -+ } - } -diff --git a/jslib/common/src/importers/zohoVaultCsvImporter.ts b/jslib/common/src/importers/zohoVaultCsvImporter.ts -index 536dc8cc..1a3e009c 100644 ---- a/jslib/common/src/importers/zohoVaultCsvImporter.ts -+++ b/jslib/common/src/importers/zohoVaultCsvImporter.ts -@@ -1,68 +1,81 @@ --import { BaseImporter } from './baseImporter'; --import { Importer } from './importer'; -+import { BaseImporter } from "./baseImporter"; -+import { Importer } from "./importer"; - --import { ImportResult } from '../models/domain/importResult'; --import { CipherView } from '../models/view/cipherView'; -+import { ImportResult } from "../models/domain/importResult"; -+import { CipherView } from "../models/view/cipherView"; - - export class ZohoVaultCsvImporter extends BaseImporter implements Importer { -- parse(data: string): Promise { -- const result = new ImportResult(); -- const results = this.parseCsv(data, true); -- if (results == null) { -- result.success = false; -- return Promise.resolve(result); -- } -- -- results.forEach(value => { -- if (this.isNullOrWhitespace(value['Password Name']) && this.isNullOrWhitespace(value['Secret Name'])) { -- return; -- } -- this.processFolder(result, this.getValueOrDefault(value.ChamberName)); -- const cipher = this.initLoginCipher(); -- cipher.favorite = this.getValueOrDefault(value.Favorite, '0') === '1'; -- cipher.notes = this.getValueOrDefault(value.Notes); -- cipher.name = this.getValueOrDefault( -- value['Password Name'], this.getValueOrDefault(value['Secret Name'], '--')); -- cipher.login.uris = this.makeUriArray( -- this.getValueOrDefault(value['Password URL'], this.getValueOrDefault(value['Secret URL']))); -- this.parseData(cipher, value.SecretData); -- this.parseData(cipher, value.CustomData); -- this.convertToNoteIfNeeded(cipher); -- this.cleanupCipher(cipher); -- result.ciphers.push(cipher); -- }); -+ parse(data: string): Promise { -+ const result = new ImportResult(); -+ const results = this.parseCsv(data, true); -+ if (results == null) { -+ result.success = false; -+ return Promise.resolve(result); -+ } - -- if (this.organization) { -- this.moveFoldersToCollections(result); -- } -+ results.forEach((value) => { -+ if ( -+ this.isNullOrWhitespace(value["Password Name"]) && -+ this.isNullOrWhitespace(value["Secret Name"]) -+ ) { -+ return; -+ } -+ this.processFolder(result, this.getValueOrDefault(value.ChamberName)); -+ const cipher = this.initLoginCipher(); -+ cipher.favorite = this.getValueOrDefault(value.Favorite, "0") === "1"; -+ cipher.notes = this.getValueOrDefault(value.Notes); -+ cipher.name = this.getValueOrDefault( -+ value["Password Name"], -+ this.getValueOrDefault(value["Secret Name"], "--") -+ ); -+ cipher.login.uris = this.makeUriArray( -+ this.getValueOrDefault(value["Password URL"], this.getValueOrDefault(value["Secret URL"])) -+ ); -+ this.parseData(cipher, value.SecretData); -+ this.parseData(cipher, value.CustomData); -+ this.convertToNoteIfNeeded(cipher); -+ this.cleanupCipher(cipher); -+ result.ciphers.push(cipher); -+ }); - -- result.success = true; -- return Promise.resolve(result); -+ if (this.organization) { -+ this.moveFoldersToCollections(result); - } - -- private parseData(cipher: CipherView, data: string) { -- if (this.isNullOrWhitespace(data)) { -- return; -- } -- const dataLines = this.splitNewLine(data); -- dataLines.forEach(line => { -- const delimPosition = line.indexOf(':'); -- if (delimPosition < 0) { -- return; -- } -- const field = line.substring(0, delimPosition); -- const value = line.length > delimPosition ? line.substring(delimPosition + 1) : null; -- if (this.isNullOrWhitespace(field) || this.isNullOrWhitespace(value) || field === 'SecretType') { -- return; -- } -- const fieldLower = field.toLowerCase(); -- if (cipher.login.username == null && this.usernameFieldNames.indexOf(fieldLower) > -1) { -- cipher.login.username = value; -- } else if (cipher.login.password == null && this.passwordFieldNames.indexOf(fieldLower) > -1) { -- cipher.login.password = value; -- } else { -- this.processKvp(cipher, field, value); -- } -- }); -+ result.success = true; -+ return Promise.resolve(result); -+ } -+ -+ private parseData(cipher: CipherView, data: string) { -+ if (this.isNullOrWhitespace(data)) { -+ return; - } -+ const dataLines = this.splitNewLine(data); -+ dataLines.forEach((line) => { -+ const delimPosition = line.indexOf(":"); -+ if (delimPosition < 0) { -+ return; -+ } -+ const field = line.substring(0, delimPosition); -+ const value = line.length > delimPosition ? line.substring(delimPosition + 1) : null; -+ if ( -+ this.isNullOrWhitespace(field) || -+ this.isNullOrWhitespace(value) || -+ field === "SecretType" -+ ) { -+ return; -+ } -+ const fieldLower = field.toLowerCase(); -+ if (cipher.login.username == null && this.usernameFieldNames.indexOf(fieldLower) > -1) { -+ cipher.login.username = value; -+ } else if ( -+ cipher.login.password == null && -+ this.passwordFieldNames.indexOf(fieldLower) > -1 -+ ) { -+ cipher.login.password = value; -+ } else { -+ this.processKvp(cipher, field, value); -+ } -+ }); -+ } - } -diff --git a/jslib/common/src/misc/captcha_iframe.ts b/jslib/common/src/misc/captcha_iframe.ts -index c098288d..b3b1af92 100644 ---- a/jslib/common/src/misc/captcha_iframe.ts -+++ b/jslib/common/src/misc/captcha_iframe.ts -@@ -1,22 +1,37 @@ --import { I18nService } from '../abstractions/i18n.service'; --import { IFrameComponent } from './iframe_component'; -+import { I18nService } from "../abstractions/i18n.service"; -+import { IFrameComponent } from "./iframe_component"; - - export class CaptchaIFrame extends IFrameComponent { -- constructor(win: Window, webVaultUrl: string, -- private i18nService: I18nService, successCallback: (message: string) => any, errorCallback: (message: string) => any, -- infoCallback: (message: string) => any) { -- super(win, webVaultUrl, 'captcha-connector.html', 'hcaptcha_iframe', successCallback, errorCallback, (message: string) => { -- const parsedMessage = JSON.parse(message); -- if (typeof (parsedMessage) !== 'string') { -- this.iframe.height = (parsedMessage.height).toString(); -- this.iframe.width = (parsedMessage.width).toString(); -- } else { -- infoCallback(parsedMessage); -- } -- }); -- } -+ constructor( -+ win: Window, -+ webVaultUrl: string, -+ private i18nService: I18nService, -+ successCallback: (message: string) => any, -+ errorCallback: (message: string) => any, -+ infoCallback: (message: string) => any -+ ) { -+ super( -+ win, -+ webVaultUrl, -+ "captcha-connector.html", -+ "hcaptcha_iframe", -+ successCallback, -+ errorCallback, -+ (message: string) => { -+ const parsedMessage = JSON.parse(message); -+ if (typeof parsedMessage !== "string") { -+ this.iframe.height = parsedMessage.height.toString(); -+ this.iframe.width = parsedMessage.width.toString(); -+ } else { -+ infoCallback(parsedMessage); -+ } -+ } -+ ); -+ } - -- init(siteKey: string): void { -- super.initComponent(this.createParams({ siteKey: siteKey, locale: this.i18nService.translationLocale }, 1)); -- } -+ init(siteKey: string): void { -+ super.initComponent( -+ this.createParams({ siteKey: siteKey, locale: this.i18nService.translationLocale }, 1) -+ ); -+ } - } -diff --git a/jslib/common/src/misc/iframe_component.ts b/jslib/common/src/misc/iframe_component.ts -index 0e04dee0..7315e11b 100644 ---- a/jslib/common/src/misc/iframe_component.ts -+++ b/jslib/common/src/misc/iframe_component.ts -@@ -1,80 +1,96 @@ --import { I18nService } from '../abstractions/i18n.service'; -+import { I18nService } from "../abstractions/i18n.service"; - - export abstract class IFrameComponent { -- iframe: HTMLIFrameElement; -- private connectorLink: HTMLAnchorElement; -- private parseFunction = this.parseMessage.bind(this); -+ iframe: HTMLIFrameElement; -+ private connectorLink: HTMLAnchorElement; -+ private parseFunction = this.parseMessage.bind(this); - -- constructor(private win: Window, protected webVaultUrl: string, private path: string, private iframeId: string, -- public successCallback?: (message: string) => any, -- public errorCallback?: (message: string) => any, public infoCallback?: (message: string) => any) { -- this.connectorLink = win.document.createElement('a'); -- } -+ constructor( -+ private win: Window, -+ protected webVaultUrl: string, -+ private path: string, -+ private iframeId: string, -+ public successCallback?: (message: string) => any, -+ public errorCallback?: (message: string) => any, -+ public infoCallback?: (message: string) => any -+ ) { -+ this.connectorLink = win.document.createElement("a"); -+ } - -- stop() { -- this.sendMessage('stop'); -- } -+ stop() { -+ this.sendMessage("stop"); -+ } - -- start() { -- this.sendMessage('start'); -- } -+ start() { -+ this.sendMessage("start"); -+ } - -- sendMessage(message: any) { -- if (!this.iframe || !this.iframe.src || !this.iframe.contentWindow) { -- return; -- } -- -- this.iframe.contentWindow.postMessage(message, this.iframe.src); -+ sendMessage(message: any) { -+ if (!this.iframe || !this.iframe.src || !this.iframe.contentWindow) { -+ return; - } - -- base64Encode(str: string): string { -- return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) => { -- return String.fromCharCode(('0x' + p1) as any); -- })); -- } -+ this.iframe.contentWindow.postMessage(message, this.iframe.src); -+ } - -- cleanup() { -- this.win.removeEventListener('message', this.parseFunction, false); -- } -+ base64Encode(str: string): string { -+ return btoa( -+ encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) => { -+ return String.fromCharCode(("0x" + p1) as any); -+ }) -+ ); -+ } - -- protected createParams(data: any, version: number) { -- return new URLSearchParams({ -- data: this.base64Encode(JSON.stringify(data)), -- parent: encodeURIComponent(this.win.document.location.href), -- v: version.toString(), -- }); -- } -+ cleanup() { -+ this.win.removeEventListener("message", this.parseFunction, false); -+ } - -- protected initComponent(params: URLSearchParams): void { -- this.connectorLink.href = `${this.webVaultUrl}/${this.path}?${params}`; -- this.iframe = this.win.document.getElementById(this.iframeId) as HTMLIFrameElement; -- this.iframe.src = this.connectorLink.href; -+ protected createParams(data: any, version: number) { -+ return new URLSearchParams({ -+ data: this.base64Encode(JSON.stringify(data)), -+ parent: encodeURIComponent(this.win.document.location.href), -+ v: version.toString(), -+ }); -+ } - -- this.win.addEventListener('message', this.parseFunction, false); -- } -+ protected initComponent(params: URLSearchParams): void { -+ this.connectorLink.href = `${this.webVaultUrl}/${this.path}?${params}`; -+ this.iframe = this.win.document.getElementById(this.iframeId) as HTMLIFrameElement; -+ this.iframe.src = this.connectorLink.href; - -- private parseMessage(event: MessageEvent) { -- if (!this.validMessage(event)) { -- return; -- } -+ this.win.addEventListener("message", this.parseFunction, false); -+ } - -- const parts: string[] = event.data.split('|'); -- if (parts[0] === 'success' && this.successCallback) { -- this.successCallback(parts[1]); -- } else if (parts[0] === 'error' && this.errorCallback) { -- this.errorCallback(parts[1]); -- } else if (parts[0] === 'info' && this.infoCallback) { -- this.infoCallback(parts[1]); -- } -+ private parseMessage(event: MessageEvent) { -+ if (!this.validMessage(event)) { -+ return; - } - -- private validMessage(event: MessageEvent) { -- if (event.origin == null || event.origin === '' || event.origin !== (this.connectorLink as any).origin || -- event.data == null || typeof (event.data) !== 'string') { -- return false; -- } -+ const parts: string[] = event.data.split("|"); -+ if (parts[0] === "success" && this.successCallback) { -+ this.successCallback(parts[1]); -+ } else if (parts[0] === "error" && this.errorCallback) { -+ this.errorCallback(parts[1]); -+ } else if (parts[0] === "info" && this.infoCallback) { -+ this.infoCallback(parts[1]); -+ } -+ } - -- return event.data.indexOf('success|') === 0 || event.data.indexOf('error|') === 0 || -- event.data.indexOf('info|') === 0; -+ private validMessage(event: MessageEvent) { -+ if ( -+ event.origin == null || -+ event.origin === "" || -+ event.origin !== (this.connectorLink as any).origin || -+ event.data == null || -+ typeof event.data !== "string" -+ ) { -+ return false; - } -+ -+ return ( -+ event.data.indexOf("success|") === 0 || -+ event.data.indexOf("error|") === 0 || -+ event.data.indexOf("info|") === 0 -+ ); -+ } - } -diff --git a/jslib/common/src/misc/linkedFieldOption.decorator.ts b/jslib/common/src/misc/linkedFieldOption.decorator.ts -index 5bf05275..8f10709c 100644 ---- a/jslib/common/src/misc/linkedFieldOption.decorator.ts -+++ b/jslib/common/src/misc/linkedFieldOption.decorator.ts -@@ -1,13 +1,13 @@ --import { ItemView } from '../models/view/itemView'; -+import { ItemView } from "../models/view/itemView"; - --import { LinkedIdType } from '../enums/linkedIdType'; -+import { LinkedIdType } from "../enums/linkedIdType"; - - export class LinkedMetadata { -- constructor(readonly propertyKey: string, private readonly _i18nKey?: string) { } -+ constructor(readonly propertyKey: string, private readonly _i18nKey?: string) {} - -- get i18nKey() { -- return this._i18nKey ?? this.propertyKey; -- } -+ get i18nKey() { -+ return this._i18nKey ?? this.propertyKey; -+ } - } - - /** -@@ -18,11 +18,11 @@ export class LinkedMetadata { - * of the class property will be used as the i18n key. - */ - export function linkedFieldOption(id: LinkedIdType, i18nKey?: string) { -- return (prototype: ItemView, propertyKey: string) => { -- if (prototype.linkedFieldOptions == null) { -- prototype.linkedFieldOptions = new Map(); -- } -+ return (prototype: ItemView, propertyKey: string) => { -+ if (prototype.linkedFieldOptions == null) { -+ prototype.linkedFieldOptions = new Map(); -+ } - -- prototype.linkedFieldOptions.set(id, new LinkedMetadata(propertyKey, i18nKey)); -- }; -+ prototype.linkedFieldOptions.set(id, new LinkedMetadata(propertyKey, i18nKey)); -+ }; - } -diff --git a/jslib/common/src/misc/nodeUtils.ts b/jslib/common/src/misc/nodeUtils.ts -index d3864fe5..5b5ba27b 100644 ---- a/jslib/common/src/misc/nodeUtils.ts -+++ b/jslib/common/src/misc/nodeUtils.ts -@@ -1,34 +1,34 @@ --import * as fs from 'fs'; --import * as path from 'path'; --import * as readline from 'readline'; -+import * as fs from "fs"; -+import * as path from "path"; -+import * as readline from "readline"; - - export class NodeUtils { -- static mkdirpSync(targetDir: string, mode = '700', relative = false, relativeDir: string = null) { -- const initialDir = path.isAbsolute(targetDir) ? path.sep : ''; -- const baseDir = relative ? (relativeDir != null ? relativeDir : __dirname) : '.'; -- targetDir.split(path.sep).reduce((parentDir, childDir) => { -- const dir = path.resolve(baseDir, parentDir, childDir); -- if (!fs.existsSync(dir)) { -- fs.mkdirSync(dir, mode); -- } -- return dir; -- }, initialDir); -- } -- static readFirstLine(fileName: string) { -- return new Promise((resolve, reject) => { -- const readStream = fs.createReadStream(fileName, {encoding: 'utf8'}); -- const readInterface = readline.createInterface(readStream); -- readInterface -- .on('line', line => { -- readStream.close(); -- resolve(line); -- }) -- .on('error', err => reject(err)); -- }); -- } -+ static mkdirpSync(targetDir: string, mode = "700", relative = false, relativeDir: string = null) { -+ const initialDir = path.isAbsolute(targetDir) ? path.sep : ""; -+ const baseDir = relative ? (relativeDir != null ? relativeDir : __dirname) : "."; -+ targetDir.split(path.sep).reduce((parentDir, childDir) => { -+ const dir = path.resolve(baseDir, parentDir, childDir); -+ if (!fs.existsSync(dir)) { -+ fs.mkdirSync(dir, mode); -+ } -+ return dir; -+ }, initialDir); -+ } -+ static readFirstLine(fileName: string) { -+ return new Promise((resolve, reject) => { -+ const readStream = fs.createReadStream(fileName, { encoding: "utf8" }); -+ const readInterface = readline.createInterface(readStream); -+ readInterface -+ .on("line", (line) => { -+ readStream.close(); -+ resolve(line); -+ }) -+ .on("error", (err) => reject(err)); -+ }); -+ } - -- // https://stackoverflow.com/a/31394257 -- static bufferToArrayBuffer(buf: Buffer): ArrayBuffer { -- return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); -- } -+ // https://stackoverflow.com/a/31394257 -+ static bufferToArrayBuffer(buf: Buffer): ArrayBuffer { -+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); -+ } - } -diff --git a/jslib/common/src/misc/sequentialize.ts b/jslib/common/src/misc/sequentialize.ts -index ff8c4856..c2421147 100644 ---- a/jslib/common/src/misc/sequentialize.ts -+++ b/jslib/common/src/misc/sequentialize.ts -@@ -9,46 +9,49 @@ - * Read more at https://github.com/bitwarden/jslib/pull/7 - */ - export function sequentialize(cacheKey: (args: any[]) => string) { -- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => { -- const originalMethod: () => Promise = descriptor.value; -- const caches = new Map>>(); -+ return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => { -+ const originalMethod: () => Promise = descriptor.value; -+ const caches = new Map>>(); - -- const getCache = (obj: any) => { -- let cache = caches.get(obj); -- if (cache != null) { -- return cache; -- } -- cache = new Map>(); -- caches.set(obj, cache); -- return cache; -- }; -- -- return { -- value: function(...args: any[]) { -- const cache = getCache(this); -- const argsCacheKey = cacheKey(args); -- let response = cache.get(argsCacheKey); -- if (response != null) { -- return response; -- } -+ const getCache = (obj: any) => { -+ let cache = caches.get(obj); -+ if (cache != null) { -+ return cache; -+ } -+ cache = new Map>(); -+ caches.set(obj, cache); -+ return cache; -+ }; - -- const onFinally = () => { -- cache.delete(argsCacheKey); -- if (cache.size === 0) { -- caches.delete(this); -- } -- }; -- response = originalMethod.apply(this, args).then((val: any) => { -- onFinally(); -- return val; -- }).catch((err: any) => { -- onFinally(); -- throw err; -- }); -+ return { -+ value: function (...args: any[]) { -+ const cache = getCache(this); -+ const argsCacheKey = cacheKey(args); -+ let response = cache.get(argsCacheKey); -+ if (response != null) { -+ return response; -+ } - -- cache.set(argsCacheKey, response); -- return response; -- }, -+ const onFinally = () => { -+ cache.delete(argsCacheKey); -+ if (cache.size === 0) { -+ caches.delete(this); -+ } - }; -+ response = originalMethod -+ .apply(this, args) -+ .then((val: any) => { -+ onFinally(); -+ return val; -+ }) -+ .catch((err: any) => { -+ onFinally(); -+ throw err; -+ }); -+ -+ cache.set(argsCacheKey, response); -+ return response; -+ }, - }; -+ }; - } -diff --git a/jslib/common/src/misc/serviceUtils.ts b/jslib/common/src/misc/serviceUtils.ts -index f8cb5257..b6c05092 100644 ---- a/jslib/common/src/misc/serviceUtils.ts -+++ b/jslib/common/src/misc/serviceUtils.ts -@@ -1,54 +1,72 @@ --import { -- ITreeNodeObject, -- TreeNode, --} from '../models/domain/treeNode'; -+import { ITreeNodeObject, TreeNode } from "../models/domain/treeNode"; - - export class ServiceUtils { -- static nestedTraverse(nodeTree: TreeNode[], partIndex: number, parts: string[], -- obj: ITreeNodeObject, parent: ITreeNodeObject, delimiter: string) { -- if (parts.length <= partIndex) { -- return; -- } -+ static nestedTraverse( -+ nodeTree: TreeNode[], -+ partIndex: number, -+ parts: string[], -+ obj: ITreeNodeObject, -+ parent: ITreeNodeObject, -+ delimiter: string -+ ) { -+ if (parts.length <= partIndex) { -+ return; -+ } - -- const end = partIndex === parts.length - 1; -- const partName = parts[partIndex]; -+ const end = partIndex === parts.length - 1; -+ const partName = parts[partIndex]; - -- for (let i = 0; i < nodeTree.length; i++) { -- if (nodeTree[i].node.name !== parts[partIndex]) { -- continue; -- } -- if (end && nodeTree[i].node.id !== obj.id) { -- // Another node with the same name. -- nodeTree.push(new TreeNode(obj, partName, parent)); -- return; -- } -- ServiceUtils.nestedTraverse(nodeTree[i].children, partIndex + 1, parts, -- obj, nodeTree[i].node, delimiter); -- return; -- } -+ for (let i = 0; i < nodeTree.length; i++) { -+ if (nodeTree[i].node.name !== parts[partIndex]) { -+ continue; -+ } -+ if (end && nodeTree[i].node.id !== obj.id) { -+ // Another node with the same name. -+ nodeTree.push(new TreeNode(obj, partName, parent)); -+ return; -+ } -+ ServiceUtils.nestedTraverse( -+ nodeTree[i].children, -+ partIndex + 1, -+ parts, -+ obj, -+ nodeTree[i].node, -+ delimiter -+ ); -+ return; -+ } - -- if (nodeTree.filter(n => n.node.name === partName).length === 0) { -- if (end) { -- nodeTree.push(new TreeNode(obj, partName, parent)); -- return; -- } -- const newPartName = parts[partIndex] + delimiter + parts[partIndex + 1]; -- ServiceUtils.nestedTraverse(nodeTree, 0, [newPartName, ...parts.slice(partIndex + 2)], -- obj, parent, delimiter); -- } -+ if (nodeTree.filter((n) => n.node.name === partName).length === 0) { -+ if (end) { -+ nodeTree.push(new TreeNode(obj, partName, parent)); -+ return; -+ } -+ const newPartName = parts[partIndex] + delimiter + parts[partIndex + 1]; -+ ServiceUtils.nestedTraverse( -+ nodeTree, -+ 0, -+ [newPartName, ...parts.slice(partIndex + 2)], -+ obj, -+ parent, -+ delimiter -+ ); - } -+ } - -- static getTreeNodeObject(nodeTree: TreeNode[], id: string): TreeNode { -- for (let i = 0; i < nodeTree.length; i++) { -- if (nodeTree[i].node.id === id) { -- return nodeTree[i]; -- } else if (nodeTree[i].children != null) { -- const node = ServiceUtils.getTreeNodeObject(nodeTree[i].children, id); -- if (node !== null) { -- return node; -- } -- } -+ static getTreeNodeObject( -+ nodeTree: TreeNode[], -+ id: string -+ ): TreeNode { -+ for (let i = 0; i < nodeTree.length; i++) { -+ if (nodeTree[i].node.id === id) { -+ return nodeTree[i]; -+ } else if (nodeTree[i].children != null) { -+ const node = ServiceUtils.getTreeNodeObject(nodeTree[i].children, id); -+ if (node !== null) { -+ return node; - } -- return null; -+ } - } -+ return null; -+ } - } -diff --git a/jslib/common/src/misc/throttle.ts b/jslib/common/src/misc/throttle.ts -index 5455db72..852ee999 100644 ---- a/jslib/common/src/misc/throttle.ts -+++ b/jslib/common/src/misc/throttle.ts -@@ -5,58 +5,65 @@ - * Calls beyond the limit will be queued, and run when one of the active calls finishes - */ - export function throttle(limit: number, throttleKey: (args: any[]) => string) { -- return (target: any, propertyKey: string | symbol, -- descriptor: TypedPropertyDescriptor<(...args: any[]) => Promise>) => { -- const originalMethod: () => Promise = descriptor.value; -- const allThrottles = new Map void)[]>>(); -+ return ( -+ target: any, -+ propertyKey: string | symbol, -+ descriptor: TypedPropertyDescriptor<(...args: any[]) => Promise> -+ ) => { -+ const originalMethod: () => Promise = descriptor.value; -+ const allThrottles = new Map void)[]>>(); - -- const getThrottles = (obj: any) => { -- let throttles = allThrottles.get(obj); -- if (throttles != null) { -- return throttles; -- } -- throttles = new Map void)[]>(); -- allThrottles.set(obj, throttles); -- return throttles; -- }; -+ const getThrottles = (obj: any) => { -+ let throttles = allThrottles.get(obj); -+ if (throttles != null) { -+ return throttles; -+ } -+ throttles = new Map void)[]>(); -+ allThrottles.set(obj, throttles); -+ return throttles; -+ }; - -- return { -- value: function(...args: any[]) { -- const throttles = getThrottles(this); -- const argsThrottleKey = throttleKey(args); -- let queue = throttles.get(argsThrottleKey); -- if (queue == null) { -- queue = []; -- throttles.set(argsThrottleKey, queue); -- } -+ return { -+ value: function (...args: any[]) { -+ const throttles = getThrottles(this); -+ const argsThrottleKey = throttleKey(args); -+ let queue = throttles.get(argsThrottleKey); -+ if (queue == null) { -+ queue = []; -+ throttles.set(argsThrottleKey, queue); -+ } - -- return new Promise((resolve, reject) => { -- const exec = () => { -- const onFinally = () => { -- queue.splice(queue.indexOf(exec), 1); -- if (queue.length >= limit) { -- queue[limit - 1](); -- } else if (queue.length === 0) { -- throttles.delete(argsThrottleKey); -- if (throttles.size === 0) { -- allThrottles.delete(this); -- } -- } -- }; -- originalMethod.apply(this, args).then((val: any) => { -- onFinally(); -- return val; -- }).catch((err: any) => { -- onFinally(); -- throw err; -- }).then(resolve, reject); -- }; -- queue.push(exec); -- if (queue.length <= limit) { -- exec(); -- } -- }); -- }, -- }; -+ return new Promise((resolve, reject) => { -+ const exec = () => { -+ const onFinally = () => { -+ queue.splice(queue.indexOf(exec), 1); -+ if (queue.length >= limit) { -+ queue[limit - 1](); -+ } else if (queue.length === 0) { -+ throttles.delete(argsThrottleKey); -+ if (throttles.size === 0) { -+ allThrottles.delete(this); -+ } -+ } -+ }; -+ originalMethod -+ .apply(this, args) -+ .then((val: any) => { -+ onFinally(); -+ return val; -+ }) -+ .catch((err: any) => { -+ onFinally(); -+ throw err; -+ }) -+ .then(resolve, reject); -+ }; -+ queue.push(exec); -+ if (queue.length <= limit) { -+ exec(); -+ } -+ }); -+ }, - }; -+ }; - } -diff --git a/jslib/common/src/misc/tldjs.noop.ts b/jslib/common/src/misc/tldjs.noop.ts -index b3bd6db7..b6273a61 100644 ---- a/jslib/common/src/misc/tldjs.noop.ts -+++ b/jslib/common/src/misc/tldjs.noop.ts -@@ -1,7 +1,7 @@ - export function getDomain(host: string): string | null { -- return null; -+ return null; - } - - export function isValid(host: string): boolean { -- return true; -+ return true; - } -diff --git a/jslib/common/src/misc/utils.ts b/jslib/common/src/misc/utils.ts -index 6ceb459a..a91ccd5a 100644 ---- a/jslib/common/src/misc/utils.ts -+++ b/jslib/common/src/misc/utils.ts -@@ -1,381 +1,406 @@ --import * as tldjs from 'tldjs'; -+import * as tldjs from "tldjs"; - --import { I18nService } from '../abstractions/i18n.service'; -+import { I18nService } from "../abstractions/i18n.service"; - - // tslint:disable-next-line --const nodeURL = typeof window === 'undefined' ? require('url') : null; -+const nodeURL = typeof window === "undefined" ? require("url") : null; - - export class Utils { -- static inited = false; -- static isNativeScript = false; -- static isNode = false; -- static isBrowser = true; -- static isMobileBrowser = false; -- static isAppleMobileBrowser = false; -- static global: any = null; -- static tldEndingRegex = /.*\.(com|net|org|edu|uk|gov|ca|de|jp|fr|au|ru|ch|io|es|us|co|xyz|info|ly|mil)$/; -- // Transpiled version of /\p{Emoji_Presentation}/gu using https://mothereff.in/regexpu. Used for compatability in older browsers. -- static regexpEmojiPresentation = /(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDED5-\uDED7\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDD78\uDD7A-\uDDCB\uDDCD-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6])/g; -- -- static init() { -- if (Utils.inited) { -- return; -+ static inited = false; -+ static isNode = false; -+ static isBrowser = true; -+ static isMobileBrowser = false; -+ static isAppleMobileBrowser = false; -+ static global: any = null; -+ static tldEndingRegex = -+ /.*\.(com|net|org|edu|uk|gov|ca|de|jp|fr|au|ru|ch|io|es|us|co|xyz|info|ly|mil)$/; -+ // Transpiled version of /\p{Emoji_Presentation}/gu using https://mothereff.in/regexpu. Used for compatability in older browsers. -+ static regexpEmojiPresentation = -+ /(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDED5-\uDED7\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDD78\uDD7A-\uDDCB\uDDCD-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6])/g; -+ -+ static init() { -+ if (Utils.inited) { -+ return; -+ } -+ -+ Utils.inited = true; -+ Utils.isNode = -+ typeof process !== "undefined" && -+ (process as any).release != null && -+ (process as any).release.name === "node"; -+ Utils.isBrowser = typeof window !== "undefined"; -+ Utils.isMobileBrowser = Utils.isBrowser && this.isMobile(window); -+ Utils.isAppleMobileBrowser = Utils.isBrowser && this.isAppleMobile(window); -+ Utils.global = Utils.isNode && !Utils.isBrowser ? global : window; -+ } -+ -+ static fromB64ToArray(str: string): Uint8Array { -+ if (Utils.isNode) { -+ return new Uint8Array(Buffer.from(str, "base64")); -+ } else { -+ const binaryString = window.atob(str); -+ const bytes = new Uint8Array(binaryString.length); -+ for (let i = 0; i < binaryString.length; i++) { -+ bytes[i] = binaryString.charCodeAt(i); -+ } -+ return bytes; -+ } -+ } -+ -+ static fromUrlB64ToArray(str: string): Uint8Array { -+ return Utils.fromB64ToArray(Utils.fromUrlB64ToB64(str)); -+ } -+ -+ static fromHexToArray(str: string): Uint8Array { -+ if (Utils.isNode) { -+ return new Uint8Array(Buffer.from(str, "hex")); -+ } else { -+ const bytes = new Uint8Array(str.length / 2); -+ for (let i = 0; i < str.length; i += 2) { -+ bytes[i / 2] = parseInt(str.substr(i, 2), 16); -+ } -+ return bytes; -+ } -+ } -+ -+ static fromUtf8ToArray(str: string): Uint8Array { -+ if (Utils.isNode) { -+ return new Uint8Array(Buffer.from(str, "utf8")); -+ } else { -+ const strUtf8 = unescape(encodeURIComponent(str)); -+ const arr = new Uint8Array(strUtf8.length); -+ for (let i = 0; i < strUtf8.length; i++) { -+ arr[i] = strUtf8.charCodeAt(i); -+ } -+ return arr; -+ } -+ } -+ -+ static fromByteStringToArray(str: string): Uint8Array { -+ const arr = new Uint8Array(str.length); -+ for (let i = 0; i < str.length; i++) { -+ arr[i] = str.charCodeAt(i); -+ } -+ return arr; -+ } -+ -+ static fromBufferToB64(buffer: ArrayBuffer): string { -+ if (Utils.isNode) { -+ return Buffer.from(buffer).toString("base64"); -+ } else { -+ let binary = ""; -+ const bytes = new Uint8Array(buffer); -+ for (let i = 0; i < bytes.byteLength; i++) { -+ binary += String.fromCharCode(bytes[i]); -+ } -+ return window.btoa(binary); -+ } -+ } -+ -+ static fromBufferToUrlB64(buffer: ArrayBuffer): string { -+ return Utils.fromB64toUrlB64(Utils.fromBufferToB64(buffer)); -+ } -+ -+ static fromB64toUrlB64(b64Str: string) { -+ return b64Str.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); -+ } -+ -+ static fromBufferToUtf8(buffer: ArrayBuffer): string { -+ if (Utils.isNode) { -+ return Buffer.from(buffer).toString("utf8"); -+ } else { -+ const bytes = new Uint8Array(buffer); -+ const encodedString = String.fromCharCode.apply(null, bytes); -+ return decodeURIComponent(escape(encodedString)); -+ } -+ } -+ -+ static fromBufferToByteString(buffer: ArrayBuffer): string { -+ return String.fromCharCode.apply(null, new Uint8Array(buffer)); -+ } -+ -+ // ref: https://stackoverflow.com/a/40031979/1090359 -+ static fromBufferToHex(buffer: ArrayBuffer): string { -+ if (Utils.isNode) { -+ return Buffer.from(buffer).toString("hex"); -+ } else { -+ const bytes = new Uint8Array(buffer); -+ return Array.prototype.map -+ .call(bytes, (x: number) => ("00" + x.toString(16)).slice(-2)) -+ .join(""); -+ } -+ } -+ -+ static fromUrlB64ToB64(urlB64Str: string): string { -+ let output = urlB64Str.replace(/-/g, "+").replace(/_/g, "/"); -+ switch (output.length % 4) { -+ case 0: -+ break; -+ case 2: -+ output += "=="; -+ break; -+ case 3: -+ output += "="; -+ break; -+ default: -+ throw new Error("Illegal base64url string!"); -+ } -+ -+ return output; -+ } -+ -+ static fromUrlB64ToUtf8(urlB64Str: string): string { -+ return Utils.fromB64ToUtf8(Utils.fromUrlB64ToB64(urlB64Str)); -+ } -+ -+ static fromUtf8ToB64(utfStr: string): string { -+ if (Utils.isNode) { -+ return Buffer.from(utfStr, "utf8").toString("base64"); -+ } else { -+ return decodeURIComponent(escape(window.btoa(utfStr))); -+ } -+ } -+ -+ static fromUtf8ToUrlB64(utfStr: string): string { -+ return Utils.fromBufferToUrlB64(Utils.fromUtf8ToArray(utfStr)); -+ } -+ -+ static fromB64ToUtf8(b64Str: string): string { -+ if (Utils.isNode) { -+ return Buffer.from(b64Str, "base64").toString("utf8"); -+ } else { -+ return decodeURIComponent(escape(window.atob(b64Str))); -+ } -+ } -+ -+ // ref: http://stackoverflow.com/a/2117523/1090359 -+ static newGuid(): string { -+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { -+ // tslint:disable-next-line -+ const r = (Math.random() * 16) | 0; -+ // tslint:disable-next-line -+ const v = c === "x" ? r : (r & 0x3) | 0x8; -+ return v.toString(16); -+ }); -+ } -+ -+ static isGuid(id: string) { -+ return RegExp( -+ /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/, -+ "i" -+ ).test(id); -+ } -+ -+ static getHostname(uriString: string): string { -+ const url = Utils.getUrl(uriString); -+ try { -+ return url != null && url.hostname !== "" ? url.hostname : null; -+ } catch { -+ return null; -+ } -+ } -+ -+ static getHost(uriString: string): string { -+ const url = Utils.getUrl(uriString); -+ try { -+ return url != null && url.host !== "" ? url.host : null; -+ } catch { -+ return null; -+ } -+ } -+ -+ static getDomain(uriString: string): string { -+ if (uriString == null) { -+ return null; -+ } -+ -+ uriString = uriString.trim(); -+ if (uriString === "") { -+ return null; -+ } -+ -+ if (uriString.startsWith("data:")) { -+ return null; -+ } -+ -+ let httpUrl = uriString.startsWith("http://") || uriString.startsWith("https://"); -+ if ( -+ !httpUrl && -+ uriString.indexOf("://") < 0 && -+ Utils.tldEndingRegex.test(uriString) && -+ uriString.indexOf("@") < 0 -+ ) { -+ uriString = "http://" + uriString; -+ httpUrl = true; -+ } -+ -+ if (httpUrl) { -+ try { -+ const url = Utils.getUrlObject(uriString); -+ const validHostname = tldjs?.isValid != null ? tldjs.isValid(url.hostname) : true; -+ if (!validHostname) { -+ return null; - } - -- Utils.inited = true; -- Utils.isNode = typeof process !== 'undefined' && (process as any).release != null && -- (process as any).release.name === 'node'; -- Utils.isBrowser = typeof window !== 'undefined'; -- Utils.isNativeScript = !Utils.isNode && !Utils.isBrowser; -- Utils.isMobileBrowser = Utils.isBrowser && this.isMobile(window); -- Utils.isAppleMobileBrowser = Utils.isBrowser && this.isAppleMobile(window); -- Utils.global = Utils.isNativeScript ? global : (Utils.isNode && !Utils.isBrowser ? global : window); -- } -- -- static fromB64ToArray(str: string): Uint8Array { -- if (Utils.isNode || Utils.isNativeScript) { -- return new Uint8Array(Buffer.from(str, 'base64')); -- } else { -- const binaryString = window.atob(str); -- const bytes = new Uint8Array(binaryString.length); -- for (let i = 0; i < binaryString.length; i++) { -- bytes[i] = binaryString.charCodeAt(i); -- } -- return bytes; -+ if (url.hostname === "localhost" || Utils.validIpAddress(url.hostname)) { -+ return url.hostname; - } -- } - -- static fromUrlB64ToArray(str: string): Uint8Array { -- return Utils.fromB64ToArray(Utils.fromUrlB64ToB64(str)); -- } -- -- static fromHexToArray(str: string): Uint8Array { -- if (Utils.isNode || Utils.isNativeScript) { -- return new Uint8Array(Buffer.from(str, 'hex')); -- } else { -- const bytes = new Uint8Array(str.length / 2); -- for (let i = 0; i < str.length; i += 2) { -- bytes[i / 2] = parseInt(str.substr(i, 2), 16); -- } -- return bytes; -+ const urlDomain = -+ tldjs != null && tldjs.getDomain != null ? tldjs.getDomain(url.hostname) : null; -+ return urlDomain != null ? urlDomain : url.hostname; -+ } catch (e) { -+ // Invalid domain, try another approach below. -+ } -+ } -+ -+ try { -+ const domain = tldjs != null && tldjs.getDomain != null ? tldjs.getDomain(uriString) : null; -+ -+ if (domain != null) { -+ return domain; -+ } -+ } catch { -+ return null; -+ } -+ -+ return null; -+ } -+ -+ static getQueryParams(uriString: string): Map { -+ const url = Utils.getUrl(uriString); -+ if (url == null || url.search == null || url.search === "") { -+ return null; -+ } -+ const map = new Map(); -+ const pairs = (url.search[0] === "?" ? url.search.substr(1) : url.search).split("&"); -+ pairs.forEach((pair) => { -+ const parts = pair.split("="); -+ if (parts.length < 1) { -+ return; -+ } -+ map.set( -+ decodeURIComponent(parts[0]).toLowerCase(), -+ parts[1] == null ? "" : decodeURIComponent(parts[1]) -+ ); -+ }); -+ return map; -+ } -+ -+ static getSortFunction(i18nService: I18nService, prop: string) { -+ return (a: any, b: any) => { -+ if (a[prop] == null && b[prop] != null) { -+ return -1; -+ } -+ if (a[prop] != null && b[prop] == null) { -+ return 1; -+ } -+ if (a[prop] == null && b[prop] == null) { -+ return 0; -+ } -+ -+ return i18nService.collator -+ ? i18nService.collator.compare(a[prop], b[prop]) -+ : a[prop].localeCompare(b[prop]); -+ }; -+ } -+ -+ static isNullOrWhitespace(str: string): boolean { -+ return str == null || typeof str !== "string" || str.trim() === ""; -+ } -+ -+ static nameOf(name: string & keyof T) { -+ return name; -+ } -+ -+ static assign(target: T, source: Partial): T { -+ return Object.assign(target, source); -+ } -+ -+ static iterateEnum(obj: O) { -+ return (Object.keys(obj).filter((k) => Number.isNaN(+k)) as K[]).map((k) => obj[k]); -+ } -+ -+ static getUrl(uriString: string): URL { -+ if (uriString == null) { -+ return null; -+ } -+ -+ uriString = uriString.trim(); -+ if (uriString === "") { -+ return null; -+ } -+ -+ let url = Utils.getUrlObject(uriString); -+ if (url == null) { -+ const hasHttpProtocol = -+ uriString.indexOf("http://") === 0 || uriString.indexOf("https://") === 0; -+ if (!hasHttpProtocol && uriString.indexOf(".") > -1) { -+ url = Utils.getUrlObject("http://" + uriString); -+ } -+ } -+ return url; -+ } -+ -+ static camelToPascalCase(s: string) { -+ return s.charAt(0).toUpperCase() + s.slice(1); -+ } -+ -+ private static validIpAddress(ipString: string): boolean { -+ // tslint:disable-next-line -+ const ipRegex = -+ /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; -+ return ipRegex.test(ipString); -+ } -+ -+ private static isMobile(win: Window) { -+ let mobile = false; -+ ((a) => { -+ // tslint:disable-next-line -+ if ( -+ /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test( -+ a -+ ) || -+ /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test( -+ a.substr(0, 4) -+ ) -+ ) { -+ mobile = true; -+ } -+ })(win.navigator.userAgent || win.navigator.vendor || (win as any).opera); -+ return mobile || win.navigator.userAgent.match(/iPad/i) != null; -+ } -+ -+ private static isAppleMobile(win: Window) { -+ return ( -+ win.navigator.userAgent.match(/iPhone/i) != null || -+ win.navigator.userAgent.match(/iPad/i) != null -+ ); -+ } -+ -+ private static getUrlObject(uriString: string): URL { -+ try { -+ if (nodeURL != null) { -+ return new nodeURL.URL(uriString); -+ } else if (typeof URL === "function") { -+ return new URL(uriString); -+ } else if (window != null) { -+ const hasProtocol = uriString.indexOf("://") > -1; -+ if (!hasProtocol && uriString.indexOf(".") > -1) { -+ uriString = "http://" + uriString; -+ } else if (!hasProtocol) { -+ return null; - } -+ const anchor = window.document.createElement("a"); -+ anchor.href = uriString; -+ return anchor as any; -+ } -+ } catch (e) { -+ // Ignore error - } - -- static fromUtf8ToArray(str: string): Uint8Array { -- if (Utils.isNode || Utils.isNativeScript) { -- return new Uint8Array(Buffer.from(str, 'utf8')); -- } else { -- const strUtf8 = unescape(encodeURIComponent(str)); -- const arr = new Uint8Array(strUtf8.length); -- for (let i = 0; i < strUtf8.length; i++) { -- arr[i] = strUtf8.charCodeAt(i); -- } -- return arr; -- } -- } -- -- static fromByteStringToArray(str: string): Uint8Array { -- const arr = new Uint8Array(str.length); -- for (let i = 0; i < str.length; i++) { -- arr[i] = str.charCodeAt(i); -- } -- return arr; -- } -- -- static fromBufferToB64(buffer: ArrayBuffer): string { -- if (Utils.isNode || Utils.isNativeScript) { -- return Buffer.from(buffer).toString('base64'); -- } else { -- let binary = ''; -- const bytes = new Uint8Array(buffer); -- for (let i = 0; i < bytes.byteLength; i++) { -- binary += String.fromCharCode(bytes[i]); -- } -- return window.btoa(binary); -- } -- } -- -- static fromBufferToUrlB64(buffer: ArrayBuffer): string { -- return Utils.fromB64toUrlB64(Utils.fromBufferToB64(buffer)); -- } -- -- static fromB64toUrlB64(b64Str: string) { -- return b64Str.replace(/\+/g, '-') -- .replace(/\//g, '_') -- .replace(/=/g, ''); -- } -- -- static fromBufferToUtf8(buffer: ArrayBuffer): string { -- if (Utils.isNode || Utils.isNativeScript) { -- return Buffer.from(buffer).toString('utf8'); -- } else { -- const bytes = new Uint8Array(buffer); -- const encodedString = String.fromCharCode.apply(null, bytes); -- return decodeURIComponent(escape(encodedString)); -- } -- } -- -- static fromBufferToByteString(buffer: ArrayBuffer): string { -- return String.fromCharCode.apply(null, new Uint8Array(buffer)); -- } -- -- // ref: https://stackoverflow.com/a/40031979/1090359 -- static fromBufferToHex(buffer: ArrayBuffer): string { -- if (Utils.isNode || Utils.isNativeScript) { -- return Buffer.from(buffer).toString('hex'); -- } else { -- const bytes = new Uint8Array(buffer); -- return Array.prototype.map.call(bytes, (x: number) => ('00' + x.toString(16)).slice(-2)).join(''); -- } -- } -- -- static fromUrlB64ToB64(urlB64Str: string): string { -- let output = urlB64Str.replace(/-/g, '+').replace(/_/g, '/'); -- switch (output.length % 4) { -- case 0: -- break; -- case 2: -- output += '=='; -- break; -- case 3: -- output += '='; -- break; -- default: -- throw new Error('Illegal base64url string!'); -- } -- -- return output; -- } -- -- static fromUrlB64ToUtf8(urlB64Str: string): string { -- return Utils.fromB64ToUtf8(Utils.fromUrlB64ToB64(urlB64Str)); -- } -- -- static fromUtf8ToB64(utfStr: string): string { -- if (Utils.isNode || Utils.isNativeScript) { -- return Buffer.from(utfStr, 'utf8').toString('base64'); -- } else { -- return decodeURIComponent(escape(window.btoa(utfStr))); -- } -- } -- -- static fromUtf8ToUrlB64(utfStr: string): string { -- return Utils.fromBufferToUrlB64(Utils.fromUtf8ToArray(utfStr)); -- } -- -- static fromB64ToUtf8(b64Str: string): string { -- if (Utils.isNode || Utils.isNativeScript) { -- return Buffer.from(b64Str, 'base64').toString('utf8'); -- } else { -- return decodeURIComponent(escape(window.atob(b64Str))); -- } -- } -- -- // ref: http://stackoverflow.com/a/2117523/1090359 -- static newGuid(): string { -- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { -- // tslint:disable-next-line -- const r = Math.random() * 16 | 0; -- // tslint:disable-next-line -- const v = c === 'x' ? r : (r & 0x3 | 0x8); -- return v.toString(16); -- }); -- } -- -- static isGuid(id: string) { -- return RegExp(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/, 'i').test(id); -- } -- -- static getHostname(uriString: string): string { -- const url = Utils.getUrl(uriString); -- try { -- return url != null && url.hostname !== '' ? url.hostname : null; -- } catch { -- return null; -- } -- } -- -- static getHost(uriString: string): string { -- const url = Utils.getUrl(uriString); -- try { -- return url != null && url.host !== '' ? url.host : null; -- } catch { -- return null; -- } -- } -- -- static getDomain(uriString: string): string { -- if (uriString == null) { -- return null; -- } -- -- uriString = uriString.trim(); -- if (uriString === '') { -- return null; -- } -- -- if (uriString.startsWith('data:')) { -- return null; -- } -- -- let httpUrl = uriString.startsWith('http://') || uriString.startsWith('https://'); -- if (!httpUrl && uriString.indexOf('://') < 0 && Utils.tldEndingRegex.test(uriString) && -- uriString.indexOf('@') < 0) { -- uriString = 'http://' + uriString; -- httpUrl = true; -- } -- -- if (httpUrl) { -- try { -- const url = Utils.getUrlObject(uriString); -- const validHostname = tldjs?.isValid != null ? tldjs.isValid(url.hostname) : true; -- if (!validHostname) { -- return null; -- } -- -- if (url.hostname === 'localhost' || Utils.validIpAddress(url.hostname)) { -- return url.hostname; -- } -- -- const urlDomain = tldjs != null && tldjs.getDomain != null ? tldjs.getDomain(url.hostname) : null; -- return urlDomain != null ? urlDomain : url.hostname; -- } catch (e) { -- // Invalid domain, try another approach below. -- } -- } -- -- try { -- const domain = tldjs != null && tldjs.getDomain != null ? tldjs.getDomain(uriString) : null; -- -- if (domain != null) { -- return domain; -- } -- } catch { -- return null; -- } -- -- return null; -- } -- -- static getQueryParams(uriString: string): Map { -- const url = Utils.getUrl(uriString); -- if (url == null || url.search == null || url.search === '') { -- return null; -- } -- const map = new Map(); -- const pairs = (url.search[0] === '?' ? url.search.substr(1) : url.search).split('&'); -- pairs.forEach(pair => { -- const parts = pair.split('='); -- if (parts.length < 1) { -- return; -- } -- map.set(decodeURIComponent(parts[0]).toLowerCase(), parts[1] == null ? '' : decodeURIComponent(parts[1])); -- }); -- return map; -- } -- -- static getSortFunction(i18nService: I18nService, prop: string) { -- return (a: any, b: any) => { -- if (a[prop] == null && b[prop] != null) { -- return -1; -- } -- if (a[prop] != null && b[prop] == null) { -- return 1; -- } -- if (a[prop] == null && b[prop] == null) { -- return 0; -- } -- -- return i18nService.collator ? i18nService.collator.compare(a[prop], b[prop]) : -- a[prop].localeCompare(b[prop]); -- }; -- } -- -- static isNullOrWhitespace(str: string): boolean { -- return str == null || typeof str !== 'string' || str.trim() === ''; -- } -- -- static nameOf(name: string & keyof T) { -- return name; -- } -- -- static assign(target: T, source: Partial): T { -- return Object.assign(target, source); -- } -- -- static iterateEnum(obj: O) { -- return (Object.keys(obj).filter(k => Number.isNaN(+k)) as K[]).map(k => obj[k]); -- } -- -- -- static getUrl(uriString: string): URL { -- if (uriString == null) { -- return null; -- } -- -- uriString = uriString.trim(); -- if (uriString === '') { -- return null; -- } -- -- let url = Utils.getUrlObject(uriString); -- if (url == null) { -- const hasHttpProtocol = uriString.indexOf('http://') === 0 || uriString.indexOf('https://') === 0; -- if (!hasHttpProtocol && uriString.indexOf('.') > -1) { -- url = Utils.getUrlObject('http://' + uriString); -- } -- } -- return url; -- } -- -- static camelToPascalCase(s: string) { -- return s.charAt(0).toUpperCase() + s.slice(1); -- } -- -- private static validIpAddress(ipString: string): boolean { -- // tslint:disable-next-line -- const ipRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; -- return ipRegex.test(ipString); -- } -- -- private static isMobile(win: Window) { -- let mobile = false; -- (a => { -- // tslint:disable-next-line -- if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) { -- mobile = true; -- } -- })(win.navigator.userAgent || win.navigator.vendor || (win as any).opera); -- return mobile || win.navigator.userAgent.match(/iPad/i) != null; -- } -- -- private static isAppleMobile(win: Window) { -- return win.navigator.userAgent.match(/iPhone/i) != null || win.navigator.userAgent.match(/iPad/i) != null; -- } -- -- private static getUrlObject(uriString: string): URL { -- try { -- if (nodeURL != null) { -- return nodeURL.URL ? new nodeURL.URL(uriString) : nodeURL.parse(uriString); -- } else if (typeof URL === 'function') { -- return new URL(uriString); -- } else if (window != null) { -- const hasProtocol = uriString.indexOf('://') > -1; -- if (!hasProtocol && uriString.indexOf('.') > -1) { -- uriString = 'http://' + uriString; -- } else if (!hasProtocol) { -- return null; -- } -- const anchor = window.document.createElement('a'); -- anchor.href = uriString; -- return anchor as any; -- } -- } catch (e) { -- // Ignore error -- } -- -- return null; -- } -+ return null; -+ } - } - - Utils.init(); -diff --git a/jslib/common/src/misc/webauthn_iframe.ts b/jslib/common/src/misc/webauthn_iframe.ts -index 54dbdc45..712594ba 100644 ---- a/jslib/common/src/misc/webauthn_iframe.ts -+++ b/jslib/common/src/misc/webauthn_iframe.ts -@@ -1,87 +1,106 @@ --import { I18nService } from '../abstractions/i18n.service'; --import { PlatformUtilsService } from '../abstractions/platformUtils.service'; -+import { I18nService } from "../abstractions/i18n.service"; -+import { PlatformUtilsService } from "../abstractions/platformUtils.service"; - - export class WebAuthnIFrame { -- private iframe: HTMLIFrameElement = null; -- private connectorLink: HTMLAnchorElement; -- private parseFunction = this.parseMessage.bind(this); -+ private iframe: HTMLIFrameElement = null; -+ private connectorLink: HTMLAnchorElement; -+ private parseFunction = this.parseMessage.bind(this); - -- constructor(private win: Window, private webVaultUrl: string, private webAuthnNewTab: boolean, -- private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, -- private successCallback: Function, private errorCallback: Function, private infoCallback: Function) { -- this.connectorLink = win.document.createElement('a'); -- } -- -- init(data: any): void { -- const params = new URLSearchParams({ -- data: this.base64Encode(JSON.stringify(data)), -- parent: encodeURIComponent(this.win.document.location.href), -- btnText: encodeURIComponent(this.i18nService.t('webAuthnAuthenticate')), -- v: '1', -- }); -+ constructor( -+ private win: Window, -+ private webVaultUrl: string, -+ private webAuthnNewTab: boolean, -+ private platformUtilsService: PlatformUtilsService, -+ private i18nService: I18nService, -+ private successCallback: Function, -+ private errorCallback: Function, -+ private infoCallback: Function -+ ) { -+ this.connectorLink = win.document.createElement("a"); -+ } - -- if (this.webAuthnNewTab) { -- // Firefox fallback which opens the webauthn page in a new tab -- params.append('locale', this.i18nService.translationLocale); -- this.platformUtilsService.launchUri(`${this.webVaultUrl}/webauthn-fallback-connector.html?${params}`); -- } else { -- this.connectorLink.href = `${this.webVaultUrl}/webauthn-connector.html?${params}`; -- this.iframe = this.win.document.getElementById('webauthn_iframe') as HTMLIFrameElement; -- this.iframe.allow = 'publickey-credentials-get ' + new URL(this.webVaultUrl).origin; -- this.iframe.src = this.connectorLink.href; -+ init(data: any): void { -+ const params = new URLSearchParams({ -+ data: this.base64Encode(JSON.stringify(data)), -+ parent: encodeURIComponent(this.win.document.location.href), -+ btnText: encodeURIComponent(this.i18nService.t("webAuthnAuthenticate")), -+ v: "1", -+ }); - -- this.win.addEventListener('message', this.parseFunction, false); -- } -- } -+ if (this.webAuthnNewTab) { -+ // Firefox fallback which opens the webauthn page in a new tab -+ params.append("locale", this.i18nService.translationLocale); -+ this.platformUtilsService.launchUri( -+ `${this.webVaultUrl}/webauthn-fallback-connector.html?${params}` -+ ); -+ } else { -+ this.connectorLink.href = `${this.webVaultUrl}/webauthn-connector.html?${params}`; -+ this.iframe = this.win.document.getElementById("webauthn_iframe") as HTMLIFrameElement; -+ this.iframe.allow = "publickey-credentials-get " + new URL(this.webVaultUrl).origin; -+ this.iframe.src = this.connectorLink.href; - -- stop() { -- this.sendMessage('stop'); -+ this.win.addEventListener("message", this.parseFunction, false); - } -+ } - -- start() { -- this.sendMessage('start'); -- } -+ stop() { -+ this.sendMessage("stop"); -+ } - -- sendMessage(message: any) { -- if (!this.iframe || !this.iframe.src || !this.iframe.contentWindow) { -- return; -- } -+ start() { -+ this.sendMessage("start"); -+ } - -- this.iframe.contentWindow.postMessage(message, this.iframe.src); -+ sendMessage(message: any) { -+ if (!this.iframe || !this.iframe.src || !this.iframe.contentWindow) { -+ return; - } - -- base64Encode(str: string): string { -- return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) => { -- return String.fromCharCode(('0x' + p1) as any); -- })); -- } -+ this.iframe.contentWindow.postMessage(message, this.iframe.src); -+ } - -- cleanup() { -- this.win.removeEventListener('message', this.parseFunction, false); -- } -+ base64Encode(str: string): string { -+ return btoa( -+ encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) => { -+ return String.fromCharCode(("0x" + p1) as any); -+ }) -+ ); -+ } - -- private parseMessage(event: MessageEvent) { -- if (!this.validMessage(event)) { -- return; -- } -+ cleanup() { -+ this.win.removeEventListener("message", this.parseFunction, false); -+ } - -- const parts: string[] = event.data.split('|'); -- if (parts[0] === 'success' && this.successCallback) { -- this.successCallback(parts[1]); -- } else if (parts[0] === 'error' && this.errorCallback) { -- this.errorCallback(parts[1]); -- } else if (parts[0] === 'info' && this.infoCallback) { -- this.infoCallback(parts[1]); -- } -+ private parseMessage(event: MessageEvent) { -+ if (!this.validMessage(event)) { -+ return; - } - -- private validMessage(event: MessageEvent) { -- if (event.origin == null || event.origin === '' || event.origin !== (this.connectorLink as any).origin || -- event.data == null || typeof (event.data) !== 'string') { -- return false; -- } -+ const parts: string[] = event.data.split("|"); -+ if (parts[0] === "success" && this.successCallback) { -+ this.successCallback(parts[1]); -+ } else if (parts[0] === "error" && this.errorCallback) { -+ this.errorCallback(parts[1]); -+ } else if (parts[0] === "info" && this.infoCallback) { -+ this.infoCallback(parts[1]); -+ } -+ } - -- return event.data.indexOf('success|') === 0 || event.data.indexOf('error|') === 0 || -- event.data.indexOf('info|') === 0; -+ private validMessage(event: MessageEvent) { -+ if ( -+ event.origin == null || -+ event.origin === "" || -+ event.origin !== (this.connectorLink as any).origin || -+ event.data == null || -+ typeof event.data !== "string" -+ ) { -+ return false; - } -+ -+ return ( -+ event.data.indexOf("success|") === 0 || -+ event.data.indexOf("error|") === 0 || -+ event.data.indexOf("info|") === 0 -+ ); -+ } - } -diff --git a/jslib/common/src/misc/wordlist.ts b/jslib/common/src/misc/wordlist.ts -index fdbfa5ad..cdd30110 100644 ---- a/jslib/common/src/misc/wordlist.ts -+++ b/jslib/common/src/misc/wordlist.ts -@@ -1,7779 +1,7779 @@ - // EFF's Long Wordlist from https://www.eff.org/dice - export const EEFLongWordList = [ -- 'abacus', -- 'abdomen', -- 'abdominal', -- 'abide', -- 'abiding', -- 'ability', -- 'ablaze', -- 'able', -- 'abnormal', -- 'abrasion', -- 'abrasive', -- 'abreast', -- 'abridge', -- 'abroad', -- 'abruptly', -- 'absence', -- 'absentee', -- 'absently', -- 'absinthe', -- 'absolute', -- 'absolve', -- 'abstain', -- 'abstract', -- 'absurd', -- 'accent', -- 'acclaim', -- 'acclimate', -- 'accompany', -- 'account', -- 'accuracy', -- 'accurate', -- 'accustom', -- 'acetone', -- 'achiness', -- 'aching', -- 'acid', -- 'acorn', -- 'acquaint', -- 'acquire', -- 'acre', -- 'acrobat', -- 'acronym', -- 'acting', -- 'action', -- 'activate', -- 'activator', -- 'active', -- 'activism', -- 'activist', -- 'activity', -- 'actress', -- 'acts', -- 'acutely', -- 'acuteness', -- 'aeration', -- 'aerobics', -- 'aerosol', -- 'aerospace', -- 'afar', -- 'affair', -- 'affected', -- 'affecting', -- 'affection', -- 'affidavit', -- 'affiliate', -- 'affirm', -- 'affix', -- 'afflicted', -- 'affluent', -- 'afford', -- 'affront', -- 'aflame', -- 'afloat', -- 'aflutter', -- 'afoot', -- 'afraid', -- 'afterglow', -- 'afterlife', -- 'aftermath', -- 'aftermost', -- 'afternoon', -- 'aged', -- 'ageless', -- 'agency', -- 'agenda', -- 'agent', -- 'aggregate', -- 'aghast', -- 'agile', -- 'agility', -- 'aging', -- 'agnostic', -- 'agonize', -- 'agonizing', -- 'agony', -- 'agreeable', -- 'agreeably', -- 'agreed', -- 'agreeing', -- 'agreement', -- 'aground', -- 'ahead', -- 'ahoy', -- 'aide', -- 'aids', -- 'aim', -- 'ajar', -- 'alabaster', -- 'alarm', -- 'albatross', -- 'album', -- 'alfalfa', -- 'algebra', -- 'algorithm', -- 'alias', -- 'alibi', -- 'alienable', -- 'alienate', -- 'aliens', -- 'alike', -- 'alive', -- 'alkaline', -- 'alkalize', -- 'almanac', -- 'almighty', -- 'almost', -- 'aloe', -- 'aloft', -- 'aloha', -- 'alone', -- 'alongside', -- 'aloof', -- 'alphabet', -- 'alright', -- 'although', -- 'altitude', -- 'alto', -- 'aluminum', -- 'alumni', -- 'always', -- 'amaretto', -- 'amaze', -- 'amazingly', -- 'amber', -- 'ambiance', -- 'ambiguity', -- 'ambiguous', -- 'ambition', -- 'ambitious', -- 'ambulance', -- 'ambush', -- 'amendable', -- 'amendment', -- 'amends', -- 'amenity', -- 'amiable', -- 'amicably', -- 'amid', -- 'amigo', -- 'amino', -- 'amiss', -- 'ammonia', -- 'ammonium', -- 'amnesty', -- 'amniotic', -- 'among', -- 'amount', -- 'amperage', -- 'ample', -- 'amplifier', -- 'amplify', -- 'amply', -- 'amuck', -- 'amulet', -- 'amusable', -- 'amused', -- 'amusement', -- 'amuser', -- 'amusing', -- 'anaconda', -- 'anaerobic', -- 'anagram', -- 'anatomist', -- 'anatomy', -- 'anchor', -- 'anchovy', -- 'ancient', -- 'android', -- 'anemia', -- 'anemic', -- 'aneurism', -- 'anew', -- 'angelfish', -- 'angelic', -- 'anger', -- 'angled', -- 'angler', -- 'angles', -- 'angling', -- 'angrily', -- 'angriness', -- 'anguished', -- 'angular', -- 'animal', -- 'animate', -- 'animating', -- 'animation', -- 'animator', -- 'anime', -- 'animosity', -- 'ankle', -- 'annex', -- 'annotate', -- 'announcer', -- 'annoying', -- 'annually', -- 'annuity', -- 'anointer', -- 'another', -- 'answering', -- 'antacid', -- 'antarctic', -- 'anteater', -- 'antelope', -- 'antennae', -- 'anthem', -- 'anthill', -- 'anthology', -- 'antibody', -- 'antics', -- 'antidote', -- 'antihero', -- 'antiquely', -- 'antiques', -- 'antiquity', -- 'antirust', -- 'antitoxic', -- 'antitrust', -- 'antiviral', -- 'antivirus', -- 'antler', -- 'antonym', -- 'antsy', -- 'anvil', -- 'anybody', -- 'anyhow', -- 'anymore', -- 'anyone', -- 'anyplace', -- 'anything', -- 'anytime', -- 'anyway', -- 'anywhere', -- 'aorta', -- 'apache', -- 'apostle', -- 'appealing', -- 'appear', -- 'appease', -- 'appeasing', -- 'appendage', -- 'appendix', -- 'appetite', -- 'appetizer', -- 'applaud', -- 'applause', -- 'apple', -- 'appliance', -- 'applicant', -- 'applied', -- 'apply', -- 'appointee', -- 'appraisal', -- 'appraiser', -- 'apprehend', -- 'approach', -- 'approval', -- 'approve', -- 'apricot', -- 'april', -- 'apron', -- 'aptitude', -- 'aptly', -- 'aqua', -- 'aqueduct', -- 'arbitrary', -- 'arbitrate', -- 'ardently', -- 'area', -- 'arena', -- 'arguable', -- 'arguably', -- 'argue', -- 'arise', -- 'armadillo', -- 'armband', -- 'armchair', -- 'armed', -- 'armful', -- 'armhole', -- 'arming', -- 'armless', -- 'armoire', -- 'armored', -- 'armory', -- 'armrest', -- 'army', -- 'aroma', -- 'arose', -- 'around', -- 'arousal', -- 'arrange', -- 'array', -- 'arrest', -- 'arrival', -- 'arrive', -- 'arrogance', -- 'arrogant', -- 'arson', -- 'art', -- 'ascend', -- 'ascension', -- 'ascent', -- 'ascertain', -- 'ashamed', -- 'ashen', -- 'ashes', -- 'ashy', -- 'aside', -- 'askew', -- 'asleep', -- 'asparagus', -- 'aspect', -- 'aspirate', -- 'aspire', -- 'aspirin', -- 'astonish', -- 'astound', -- 'astride', -- 'astrology', -- 'astronaut', -- 'astronomy', -- 'astute', -- 'atlantic', -- 'atlas', -- 'atom', -- 'atonable', -- 'atop', -- 'atrium', -- 'atrocious', -- 'atrophy', -- 'attach', -- 'attain', -- 'attempt', -- 'attendant', -- 'attendee', -- 'attention', -- 'attentive', -- 'attest', -- 'attic', -- 'attire', -- 'attitude', -- 'attractor', -- 'attribute', -- 'atypical', -- 'auction', -- 'audacious', -- 'audacity', -- 'audible', -- 'audibly', -- 'audience', -- 'audio', -- 'audition', -- 'augmented', -- 'august', -- 'authentic', -- 'author', -- 'autism', -- 'autistic', -- 'autograph', -- 'automaker', -- 'automated', -- 'automatic', -- 'autopilot', -- 'available', -- 'avalanche', -- 'avatar', -- 'avenge', -- 'avenging', -- 'avenue', -- 'average', -- 'aversion', -- 'avert', -- 'aviation', -- 'aviator', -- 'avid', -- 'avoid', -- 'await', -- 'awaken', -- 'award', -- 'aware', -- 'awhile', -- 'awkward', -- 'awning', -- 'awoke', -- 'awry', -- 'axis', -- 'babble', -- 'babbling', -- 'babied', -- 'baboon', -- 'backache', -- 'backboard', -- 'backboned', -- 'backdrop', -- 'backed', -- 'backer', -- 'backfield', -- 'backfire', -- 'backhand', -- 'backing', -- 'backlands', -- 'backlash', -- 'backless', -- 'backlight', -- 'backlit', -- 'backlog', -- 'backpack', -- 'backpedal', -- 'backrest', -- 'backroom', -- 'backshift', -- 'backside', -- 'backslid', -- 'backspace', -- 'backspin', -- 'backstab', -- 'backstage', -- 'backtalk', -- 'backtrack', -- 'backup', -- 'backward', -- 'backwash', -- 'backwater', -- 'backyard', -- 'bacon', -- 'bacteria', -- 'bacterium', -- 'badass', -- 'badge', -- 'badland', -- 'badly', -- 'badness', -- 'baffle', -- 'baffling', -- 'bagel', -- 'bagful', -- 'baggage', -- 'bagged', -- 'baggie', -- 'bagginess', -- 'bagging', -- 'baggy', -- 'bagpipe', -- 'baguette', -- 'baked', -- 'bakery', -- 'bakeshop', -- 'baking', -- 'balance', -- 'balancing', -- 'balcony', -- 'balmy', -- 'balsamic', -- 'bamboo', -- 'banana', -- 'banish', -- 'banister', -- 'banjo', -- 'bankable', -- 'bankbook', -- 'banked', -- 'banker', -- 'banking', -- 'banknote', -- 'bankroll', -- 'banner', -- 'bannister', -- 'banshee', -- 'banter', -- 'barbecue', -- 'barbed', -- 'barbell', -- 'barber', -- 'barcode', -- 'barge', -- 'bargraph', -- 'barista', -- 'baritone', -- 'barley', -- 'barmaid', -- 'barman', -- 'barn', -- 'barometer', -- 'barrack', -- 'barracuda', -- 'barrel', -- 'barrette', -- 'barricade', -- 'barrier', -- 'barstool', -- 'bartender', -- 'barterer', -- 'bash', -- 'basically', -- 'basics', -- 'basil', -- 'basin', -- 'basis', -- 'basket', -- 'batboy', -- 'batch', -- 'bath', -- 'baton', -- 'bats', -- 'battalion', -- 'battered', -- 'battering', -- 'battery', -- 'batting', -- 'battle', -- 'bauble', -- 'bazooka', -- 'blabber', -- 'bladder', -- 'blade', -- 'blah', -- 'blame', -- 'blaming', -- 'blanching', -- 'blandness', -- 'blank', -- 'blaspheme', -- 'blasphemy', -- 'blast', -- 'blatancy', -- 'blatantly', -- 'blazer', -- 'blazing', -- 'bleach', -- 'bleak', -- 'bleep', -- 'blemish', -- 'blend', -- 'bless', -- 'blighted', -- 'blimp', -- 'bling', -- 'blinked', -- 'blinker', -- 'blinking', -- 'blinks', -- 'blip', -- 'blissful', -- 'blitz', -- 'blizzard', -- 'bloated', -- 'bloating', -- 'blob', -- 'blog', -- 'bloomers', -- 'blooming', -- 'blooper', -- 'blot', -- 'blouse', -- 'blubber', -- 'bluff', -- 'bluish', -- 'blunderer', -- 'blunt', -- 'blurb', -- 'blurred', -- 'blurry', -- 'blurt', -- 'blush', -- 'blustery', -- 'boaster', -- 'boastful', -- 'boasting', -- 'boat', -- 'bobbed', -- 'bobbing', -- 'bobble', -- 'bobcat', -- 'bobsled', -- 'bobtail', -- 'bodacious', -- 'body', -- 'bogged', -- 'boggle', -- 'bogus', -- 'boil', -- 'bok', -- 'bolster', -- 'bolt', -- 'bonanza', -- 'bonded', -- 'bonding', -- 'bondless', -- 'boned', -- 'bonehead', -- 'boneless', -- 'bonelike', -- 'boney', -- 'bonfire', -- 'bonnet', -- 'bonsai', -- 'bonus', -- 'bony', -- 'boogeyman', -- 'boogieman', -- 'book', -- 'boondocks', -- 'booted', -- 'booth', -- 'bootie', -- 'booting', -- 'bootlace', -- 'bootleg', -- 'boots', -- 'boozy', -- 'borax', -- 'boring', -- 'borough', -- 'borrower', -- 'borrowing', -- 'boss', -- 'botanical', -- 'botanist', -- 'botany', -- 'botch', -- 'both', -- 'bottle', -- 'bottling', -- 'bottom', -- 'bounce', -- 'bouncing', -- 'bouncy', -- 'bounding', -- 'boundless', -- 'bountiful', -- 'bovine', -- 'boxcar', -- 'boxer', -- 'boxing', -- 'boxlike', -- 'boxy', -- 'breach', -- 'breath', -- 'breeches', -- 'breeching', -- 'breeder', -- 'breeding', -- 'breeze', -- 'breezy', -- 'brethren', -- 'brewery', -- 'brewing', -- 'briar', -- 'bribe', -- 'brick', -- 'bride', -- 'bridged', -- 'brigade', -- 'bright', -- 'brilliant', -- 'brim', -- 'bring', -- 'brink', -- 'brisket', -- 'briskly', -- 'briskness', -- 'bristle', -- 'brittle', -- 'broadband', -- 'broadcast', -- 'broaden', -- 'broadly', -- 'broadness', -- 'broadside', -- 'broadways', -- 'broiler', -- 'broiling', -- 'broken', -- 'broker', -- 'bronchial', -- 'bronco', -- 'bronze', -- 'bronzing', -- 'brook', -- 'broom', -- 'brought', -- 'browbeat', -- 'brownnose', -- 'browse', -- 'browsing', -- 'bruising', -- 'brunch', -- 'brunette', -- 'brunt', -- 'brush', -- 'brussels', -- 'brute', -- 'brutishly', -- 'bubble', -- 'bubbling', -- 'bubbly', -- 'buccaneer', -- 'bucked', -- 'bucket', -- 'buckle', -- 'buckshot', -- 'buckskin', -- 'bucktooth', -- 'buckwheat', -- 'buddhism', -- 'buddhist', -- 'budding', -- 'buddy', -- 'budget', -- 'buffalo', -- 'buffed', -- 'buffer', -- 'buffing', -- 'buffoon', -- 'buggy', -- 'bulb', -- 'bulge', -- 'bulginess', -- 'bulgur', -- 'bulk', -- 'bulldog', -- 'bulldozer', -- 'bullfight', -- 'bullfrog', -- 'bullhorn', -- 'bullion', -- 'bullish', -- 'bullpen', -- 'bullring', -- 'bullseye', -- 'bullwhip', -- 'bully', -- 'bunch', -- 'bundle', -- 'bungee', -- 'bunion', -- 'bunkbed', -- 'bunkhouse', -- 'bunkmate', -- 'bunny', -- 'bunt', -- 'busboy', -- 'bush', -- 'busily', -- 'busload', -- 'bust', -- 'busybody', -- 'buzz', -- 'cabana', -- 'cabbage', -- 'cabbie', -- 'cabdriver', -- 'cable', -- 'caboose', -- 'cache', -- 'cackle', -- 'cacti', -- 'cactus', -- 'caddie', -- 'caddy', -- 'cadet', -- 'cadillac', -- 'cadmium', -- 'cage', -- 'cahoots', -- 'cake', -- 'calamari', -- 'calamity', -- 'calcium', -- 'calculate', -- 'calculus', -- 'caliber', -- 'calibrate', -- 'calm', -- 'caloric', -- 'calorie', -- 'calzone', -- 'camcorder', -- 'cameo', -- 'camera', -- 'camisole', -- 'camper', -- 'campfire', -- 'camping', -- 'campsite', -- 'campus', -- 'canal', -- 'canary', -- 'cancel', -- 'candied', -- 'candle', -- 'candy', -- 'cane', -- 'canine', -- 'canister', -- 'cannabis', -- 'canned', -- 'canning', -- 'cannon', -- 'cannot', -- 'canola', -- 'canon', -- 'canopener', -- 'canopy', -- 'canteen', -- 'canyon', -- 'capable', -- 'capably', -- 'capacity', -- 'cape', -- 'capillary', -- 'capital', -- 'capitol', -- 'capped', -- 'capricorn', -- 'capsize', -- 'capsule', -- 'caption', -- 'captivate', -- 'captive', -- 'captivity', -- 'capture', -- 'caramel', -- 'carat', -- 'caravan', -- 'carbon', -- 'cardboard', -- 'carded', -- 'cardiac', -- 'cardigan', -- 'cardinal', -- 'cardstock', -- 'carefully', -- 'caregiver', -- 'careless', -- 'caress', -- 'caretaker', -- 'cargo', -- 'caring', -- 'carless', -- 'carload', -- 'carmaker', -- 'carnage', -- 'carnation', -- 'carnival', -- 'carnivore', -- 'carol', -- 'carpenter', -- 'carpentry', -- 'carpool', -- 'carport', -- 'carried', -- 'carrot', -- 'carrousel', -- 'carry', -- 'cartel', -- 'cartload', -- 'carton', -- 'cartoon', -- 'cartridge', -- 'cartwheel', -- 'carve', -- 'carving', -- 'carwash', -- 'cascade', -- 'case', -- 'cash', -- 'casing', -- 'casino', -- 'casket', -- 'cassette', -- 'casually', -- 'casualty', -- 'catacomb', -- 'catalog', -- 'catalyst', -- 'catalyze', -- 'catapult', -- 'cataract', -- 'catatonic', -- 'catcall', -- 'catchable', -- 'catcher', -- 'catching', -- 'catchy', -- 'caterer', -- 'catering', -- 'catfight', -- 'catfish', -- 'cathedral', -- 'cathouse', -- 'catlike', -- 'catnap', -- 'catnip', -- 'catsup', -- 'cattail', -- 'cattishly', -- 'cattle', -- 'catty', -- 'catwalk', -- 'caucasian', -- 'caucus', -- 'causal', -- 'causation', -- 'cause', -- 'causing', -- 'cauterize', -- 'caution', -- 'cautious', -- 'cavalier', -- 'cavalry', -- 'caviar', -- 'cavity', -- 'cedar', -- 'celery', -- 'celestial', -- 'celibacy', -- 'celibate', -- 'celtic', -- 'cement', -- 'census', -- 'ceramics', -- 'ceremony', -- 'certainly', -- 'certainty', -- 'certified', -- 'certify', -- 'cesarean', -- 'cesspool', -- 'chafe', -- 'chaffing', -- 'chain', -- 'chair', -- 'chalice', -- 'challenge', -- 'chamber', -- 'chamomile', -- 'champion', -- 'chance', -- 'change', -- 'channel', -- 'chant', -- 'chaos', -- 'chaperone', -- 'chaplain', -- 'chapped', -- 'chaps', -- 'chapter', -- 'character', -- 'charbroil', -- 'charcoal', -- 'charger', -- 'charging', -- 'chariot', -- 'charity', -- 'charm', -- 'charred', -- 'charter', -- 'charting', -- 'chase', -- 'chasing', -- 'chaste', -- 'chastise', -- 'chastity', -- 'chatroom', -- 'chatter', -- 'chatting', -- 'chatty', -- 'cheating', -- 'cheddar', -- 'cheek', -- 'cheer', -- 'cheese', -- 'cheesy', -- 'chef', -- 'chemicals', -- 'chemist', -- 'chemo', -- 'cherisher', -- 'cherub', -- 'chess', -- 'chest', -- 'chevron', -- 'chevy', -- 'chewable', -- 'chewer', -- 'chewing', -- 'chewy', -- 'chief', -- 'chihuahua', -- 'childcare', -- 'childhood', -- 'childish', -- 'childless', -- 'childlike', -- 'chili', -- 'chill', -- 'chimp', -- 'chip', -- 'chirping', -- 'chirpy', -- 'chitchat', -- 'chivalry', -- 'chive', -- 'chloride', -- 'chlorine', -- 'choice', -- 'chokehold', -- 'choking', -- 'chomp', -- 'chooser', -- 'choosing', -- 'choosy', -- 'chop', -- 'chosen', -- 'chowder', -- 'chowtime', -- 'chrome', -- 'chubby', -- 'chuck', -- 'chug', -- 'chummy', -- 'chump', -- 'chunk', -- 'churn', -- 'chute', -- 'cider', -- 'cilantro', -- 'cinch', -- 'cinema', -- 'cinnamon', -- 'circle', -- 'circling', -- 'circular', -- 'circulate', -- 'circus', -- 'citable', -- 'citadel', -- 'citation', -- 'citizen', -- 'citric', -- 'citrus', -- 'city', -- 'civic', -- 'civil', -- 'clad', -- 'claim', -- 'clambake', -- 'clammy', -- 'clamor', -- 'clamp', -- 'clamshell', -- 'clang', -- 'clanking', -- 'clapped', -- 'clapper', -- 'clapping', -- 'clarify', -- 'clarinet', -- 'clarity', -- 'clash', -- 'clasp', -- 'class', -- 'clatter', -- 'clause', -- 'clavicle', -- 'claw', -- 'clay', -- 'clean', -- 'clear', -- 'cleat', -- 'cleaver', -- 'cleft', -- 'clench', -- 'clergyman', -- 'clerical', -- 'clerk', -- 'clever', -- 'clicker', -- 'client', -- 'climate', -- 'climatic', -- 'cling', -- 'clinic', -- 'clinking', -- 'clip', -- 'clique', -- 'cloak', -- 'clobber', -- 'clock', -- 'clone', -- 'cloning', -- 'closable', -- 'closure', -- 'clothes', -- 'clothing', -- 'cloud', -- 'clover', -- 'clubbed', -- 'clubbing', -- 'clubhouse', -- 'clump', -- 'clumsily', -- 'clumsy', -- 'clunky', -- 'clustered', -- 'clutch', -- 'clutter', -- 'coach', -- 'coagulant', -- 'coastal', -- 'coaster', -- 'coasting', -- 'coastland', -- 'coastline', -- 'coat', -- 'coauthor', -- 'cobalt', -- 'cobbler', -- 'cobweb', -- 'cocoa', -- 'coconut', -- 'cod', -- 'coeditor', -- 'coerce', -- 'coexist', -- 'coffee', -- 'cofounder', -- 'cognition', -- 'cognitive', -- 'cogwheel', -- 'coherence', -- 'coherent', -- 'cohesive', -- 'coil', -- 'coke', -- 'cola', -- 'cold', -- 'coleslaw', -- 'coliseum', -- 'collage', -- 'collapse', -- 'collar', -- 'collected', -- 'collector', -- 'collide', -- 'collie', -- 'collision', -- 'colonial', -- 'colonist', -- 'colonize', -- 'colony', -- 'colossal', -- 'colt', -- 'coma', -- 'come', -- 'comfort', -- 'comfy', -- 'comic', -- 'coming', -- 'comma', -- 'commence', -- 'commend', -- 'comment', -- 'commerce', -- 'commode', -- 'commodity', -- 'commodore', -- 'common', -- 'commotion', -- 'commute', -- 'commuting', -- 'compacted', -- 'compacter', -- 'compactly', -- 'compactor', -- 'companion', -- 'company', -- 'compare', -- 'compel', -- 'compile', -- 'comply', -- 'component', -- 'composed', -- 'composer', -- 'composite', -- 'compost', -- 'composure', -- 'compound', -- 'compress', -- 'comprised', -- 'computer', -- 'computing', -- 'comrade', -- 'concave', -- 'conceal', -- 'conceded', -- 'concept', -- 'concerned', -- 'concert', -- 'conch', -- 'concierge', -- 'concise', -- 'conclude', -- 'concrete', -- 'concur', -- 'condense', -- 'condiment', -- 'condition', -- 'condone', -- 'conducive', -- 'conductor', -- 'conduit', -- 'cone', -- 'confess', -- 'confetti', -- 'confidant', -- 'confident', -- 'confider', -- 'confiding', -- 'configure', -- 'confined', -- 'confining', -- 'confirm', -- 'conflict', -- 'conform', -- 'confound', -- 'confront', -- 'confused', -- 'confusing', -- 'confusion', -- 'congenial', -- 'congested', -- 'congrats', -- 'congress', -- 'conical', -- 'conjoined', -- 'conjure', -- 'conjuror', -- 'connected', -- 'connector', -- 'consensus', -- 'consent', -- 'console', -- 'consoling', -- 'consonant', -- 'constable', -- 'constant', -- 'constrain', -- 'constrict', -- 'construct', -- 'consult', -- 'consumer', -- 'consuming', -- 'contact', -- 'container', -- 'contempt', -- 'contend', -- 'contented', -- 'contently', -- 'contents', -- 'contest', -- 'context', -- 'contort', -- 'contour', -- 'contrite', -- 'control', -- 'contusion', -- 'convene', -- 'convent', -- 'copartner', -- 'cope', -- 'copied', -- 'copier', -- 'copilot', -- 'coping', -- 'copious', -- 'copper', -- 'copy', -- 'coral', -- 'cork', -- 'cornball', -- 'cornbread', -- 'corncob', -- 'cornea', -- 'corned', -- 'corner', -- 'cornfield', -- 'cornflake', -- 'cornhusk', -- 'cornmeal', -- 'cornstalk', -- 'corny', -- 'coronary', -- 'coroner', -- 'corporal', -- 'corporate', -- 'corral', -- 'correct', -- 'corridor', -- 'corrode', -- 'corroding', -- 'corrosive', -- 'corsage', -- 'corset', -- 'cortex', -- 'cosigner', -- 'cosmetics', -- 'cosmic', -- 'cosmos', -- 'cosponsor', -- 'cost', -- 'cottage', -- 'cotton', -- 'couch', -- 'cough', -- 'could', -- 'countable', -- 'countdown', -- 'counting', -- 'countless', -- 'country', -- 'county', -- 'courier', -- 'covenant', -- 'cover', -- 'coveted', -- 'coveting', -- 'coyness', -- 'cozily', -- 'coziness', -- 'cozy', -- 'crabbing', -- 'crabgrass', -- 'crablike', -- 'crabmeat', -- 'cradle', -- 'cradling', -- 'crafter', -- 'craftily', -- 'craftsman', -- 'craftwork', -- 'crafty', -- 'cramp', -- 'cranberry', -- 'crane', -- 'cranial', -- 'cranium', -- 'crank', -- 'crate', -- 'crave', -- 'craving', -- 'crawfish', -- 'crawlers', -- 'crawling', -- 'crayfish', -- 'crayon', -- 'crazed', -- 'crazily', -- 'craziness', -- 'crazy', -- 'creamed', -- 'creamer', -- 'creamlike', -- 'crease', -- 'creasing', -- 'creatable', -- 'create', -- 'creation', -- 'creative', -- 'creature', -- 'credible', -- 'credibly', -- 'credit', -- 'creed', -- 'creme', -- 'creole', -- 'crepe', -- 'crept', -- 'crescent', -- 'crested', -- 'cresting', -- 'crestless', -- 'crevice', -- 'crewless', -- 'crewman', -- 'crewmate', -- 'crib', -- 'cricket', -- 'cried', -- 'crier', -- 'crimp', -- 'crimson', -- 'cringe', -- 'cringing', -- 'crinkle', -- 'crinkly', -- 'crisped', -- 'crisping', -- 'crisply', -- 'crispness', -- 'crispy', -- 'criteria', -- 'critter', -- 'croak', -- 'crock', -- 'crook', -- 'croon', -- 'crop', -- 'cross', -- 'crouch', -- 'crouton', -- 'crowbar', -- 'crowd', -- 'crown', -- 'crucial', -- 'crudely', -- 'crudeness', -- 'cruelly', -- 'cruelness', -- 'cruelty', -- 'crumb', -- 'crummiest', -- 'crummy', -- 'crumpet', -- 'crumpled', -- 'cruncher', -- 'crunching', -- 'crunchy', -- 'crusader', -- 'crushable', -- 'crushed', -- 'crusher', -- 'crushing', -- 'crust', -- 'crux', -- 'crying', -- 'cryptic', -- 'crystal', -- 'cubbyhole', -- 'cube', -- 'cubical', -- 'cubicle', -- 'cucumber', -- 'cuddle', -- 'cuddly', -- 'cufflink', -- 'culinary', -- 'culminate', -- 'culpable', -- 'culprit', -- 'cultivate', -- 'cultural', -- 'culture', -- 'cupbearer', -- 'cupcake', -- 'cupid', -- 'cupped', -- 'cupping', -- 'curable', -- 'curator', -- 'curdle', -- 'cure', -- 'curfew', -- 'curing', -- 'curled', -- 'curler', -- 'curliness', -- 'curling', -- 'curly', -- 'curry', -- 'curse', -- 'cursive', -- 'cursor', -- 'curtain', -- 'curtly', -- 'curtsy', -- 'curvature', -- 'curve', -- 'curvy', -- 'cushy', -- 'cusp', -- 'cussed', -- 'custard', -- 'custodian', -- 'custody', -- 'customary', -- 'customer', -- 'customize', -- 'customs', -- 'cut', -- 'cycle', -- 'cyclic', -- 'cycling', -- 'cyclist', -- 'cylinder', -- 'cymbal', -- 'cytoplasm', -- 'cytoplast', -- 'dab', -- 'dad', -- 'daffodil', -- 'dagger', -- 'daily', -- 'daintily', -- 'dainty', -- 'dairy', -- 'daisy', -- 'dallying', -- 'dance', -- 'dancing', -- 'dandelion', -- 'dander', -- 'dandruff', -- 'dandy', -- 'danger', -- 'dangle', -- 'dangling', -- 'daredevil', -- 'dares', -- 'daringly', -- 'darkened', -- 'darkening', -- 'darkish', -- 'darkness', -- 'darkroom', -- 'darling', -- 'darn', -- 'dart', -- 'darwinism', -- 'dash', -- 'dastardly', -- 'data', -- 'datebook', -- 'dating', -- 'daughter', -- 'daunting', -- 'dawdler', -- 'dawn', -- 'daybed', -- 'daybreak', -- 'daycare', -- 'daydream', -- 'daylight', -- 'daylong', -- 'dayroom', -- 'daytime', -- 'dazzler', -- 'dazzling', -- 'deacon', -- 'deafening', -- 'deafness', -- 'dealer', -- 'dealing', -- 'dealmaker', -- 'dealt', -- 'dean', -- 'debatable', -- 'debate', -- 'debating', -- 'debit', -- 'debrief', -- 'debtless', -- 'debtor', -- 'debug', -- 'debunk', -- 'decade', -- 'decaf', -- 'decal', -- 'decathlon', -- 'decay', -- 'deceased', -- 'deceit', -- 'deceiver', -- 'deceiving', -- 'december', -- 'decency', -- 'decent', -- 'deception', -- 'deceptive', -- 'decibel', -- 'decidable', -- 'decimal', -- 'decimeter', -- 'decipher', -- 'deck', -- 'declared', -- 'decline', -- 'decode', -- 'decompose', -- 'decorated', -- 'decorator', -- 'decoy', -- 'decrease', -- 'decree', -- 'dedicate', -- 'dedicator', -- 'deduce', -- 'deduct', -- 'deed', -- 'deem', -- 'deepen', -- 'deeply', -- 'deepness', -- 'deface', -- 'defacing', -- 'defame', -- 'default', -- 'defeat', -- 'defection', -- 'defective', -- 'defendant', -- 'defender', -- 'defense', -- 'defensive', -- 'deferral', -- 'deferred', -- 'defiance', -- 'defiant', -- 'defile', -- 'defiling', -- 'define', -- 'definite', -- 'deflate', -- 'deflation', -- 'deflator', -- 'deflected', -- 'deflector', -- 'defog', -- 'deforest', -- 'defraud', -- 'defrost', -- 'deftly', -- 'defuse', -- 'defy', -- 'degraded', -- 'degrading', -- 'degrease', -- 'degree', -- 'dehydrate', -- 'deity', -- 'dejected', -- 'delay', -- 'delegate', -- 'delegator', -- 'delete', -- 'deletion', -- 'delicacy', -- 'delicate', -- 'delicious', -- 'delighted', -- 'delirious', -- 'delirium', -- 'deliverer', -- 'delivery', -- 'delouse', -- 'delta', -- 'deluge', -- 'delusion', -- 'deluxe', -- 'demanding', -- 'demeaning', -- 'demeanor', -- 'demise', -- 'democracy', -- 'democrat', -- 'demote', -- 'demotion', -- 'demystify', -- 'denatured', -- 'deniable', -- 'denial', -- 'denim', -- 'denote', -- 'dense', -- 'density', -- 'dental', -- 'dentist', -- 'denture', -- 'deny', -- 'deodorant', -- 'deodorize', -- 'departed', -- 'departure', -- 'depict', -- 'deplete', -- 'depletion', -- 'deplored', -- 'deploy', -- 'deport', -- 'depose', -- 'depraved', -- 'depravity', -- 'deprecate', -- 'depress', -- 'deprive', -- 'depth', -- 'deputize', -- 'deputy', -- 'derail', -- 'deranged', -- 'derby', -- 'derived', -- 'desecrate', -- 'deserve', -- 'deserving', -- 'designate', -- 'designed', -- 'designer', -- 'designing', -- 'deskbound', -- 'desktop', -- 'deskwork', -- 'desolate', -- 'despair', -- 'despise', -- 'despite', -- 'destiny', -- 'destitute', -- 'destruct', -- 'detached', -- 'detail', -- 'detection', -- 'detective', -- 'detector', -- 'detention', -- 'detergent', -- 'detest', -- 'detonate', -- 'detonator', -- 'detoxify', -- 'detract', -- 'deuce', -- 'devalue', -- 'deviancy', -- 'deviant', -- 'deviate', -- 'deviation', -- 'deviator', -- 'device', -- 'devious', -- 'devotedly', -- 'devotee', -- 'devotion', -- 'devourer', -- 'devouring', -- 'devoutly', -- 'dexterity', -- 'dexterous', -- 'diabetes', -- 'diabetic', -- 'diabolic', -- 'diagnoses', -- 'diagnosis', -- 'diagram', -- 'dial', -- 'diameter', -- 'diaper', -- 'diaphragm', -- 'diary', -- 'dice', -- 'dicing', -- 'dictate', -- 'dictation', -- 'dictator', -- 'difficult', -- 'diffused', -- 'diffuser', -- 'diffusion', -- 'diffusive', -- 'dig', -- 'dilation', -- 'diligence', -- 'diligent', -- 'dill', -- 'dilute', -- 'dime', -- 'diminish', -- 'dimly', -- 'dimmed', -- 'dimmer', -- 'dimness', -- 'dimple', -- 'diner', -- 'dingbat', -- 'dinghy', -- 'dinginess', -- 'dingo', -- 'dingy', -- 'dining', -- 'dinner', -- 'diocese', -- 'dioxide', -- 'diploma', -- 'dipped', -- 'dipper', -- 'dipping', -- 'directed', -- 'direction', -- 'directive', -- 'directly', -- 'directory', -- 'direness', -- 'dirtiness', -- 'disabled', -- 'disagree', -- 'disallow', -- 'disarm', -- 'disarray', -- 'disaster', -- 'disband', -- 'disbelief', -- 'disburse', -- 'discard', -- 'discern', -- 'discharge', -- 'disclose', -- 'discolor', -- 'discount', -- 'discourse', -- 'discover', -- 'discuss', -- 'disdain', -- 'disengage', -- 'disfigure', -- 'disgrace', -- 'dish', -- 'disinfect', -- 'disjoin', -- 'disk', -- 'dislike', -- 'disliking', -- 'dislocate', -- 'dislodge', -- 'disloyal', -- 'dismantle', -- 'dismay', -- 'dismiss', -- 'dismount', -- 'disobey', -- 'disorder', -- 'disown', -- 'disparate', -- 'disparity', -- 'dispatch', -- 'dispense', -- 'dispersal', -- 'dispersed', -- 'disperser', -- 'displace', -- 'display', -- 'displease', -- 'disposal', -- 'dispose', -- 'disprove', -- 'dispute', -- 'disregard', -- 'disrupt', -- 'dissuade', -- 'distance', -- 'distant', -- 'distaste', -- 'distill', -- 'distinct', -- 'distort', -- 'distract', -- 'distress', -- 'district', -- 'distrust', -- 'ditch', -- 'ditto', -- 'ditzy', -- 'dividable', -- 'divided', -- 'dividend', -- 'dividers', -- 'dividing', -- 'divinely', -- 'diving', -- 'divinity', -- 'divisible', -- 'divisibly', -- 'division', -- 'divisive', -- 'divorcee', -- 'dizziness', -- 'dizzy', -- 'doable', -- 'docile', -- 'dock', -- 'doctrine', -- 'document', -- 'dodge', -- 'dodgy', -- 'doily', -- 'doing', -- 'dole', -- 'dollar', -- 'dollhouse', -- 'dollop', -- 'dolly', -- 'dolphin', -- 'domain', -- 'domelike', -- 'domestic', -- 'dominion', -- 'dominoes', -- 'donated', -- 'donation', -- 'donator', -- 'donor', -- 'donut', -- 'doodle', -- 'doorbell', -- 'doorframe', -- 'doorknob', -- 'doorman', -- 'doormat', -- 'doornail', -- 'doorpost', -- 'doorstep', -- 'doorstop', -- 'doorway', -- 'doozy', -- 'dork', -- 'dormitory', -- 'dorsal', -- 'dosage', -- 'dose', -- 'dotted', -- 'doubling', -- 'douche', -- 'dove', -- 'down', -- 'dowry', -- 'doze', -- 'drab', -- 'dragging', -- 'dragonfly', -- 'dragonish', -- 'dragster', -- 'drainable', -- 'drainage', -- 'drained', -- 'drainer', -- 'drainpipe', -- 'dramatic', -- 'dramatize', -- 'drank', -- 'drapery', -- 'drastic', -- 'draw', -- 'dreaded', -- 'dreadful', -- 'dreadlock', -- 'dreamboat', -- 'dreamily', -- 'dreamland', -- 'dreamless', -- 'dreamlike', -- 'dreamt', -- 'dreamy', -- 'drearily', -- 'dreary', -- 'drench', -- 'dress', -- 'drew', -- 'dribble', -- 'dried', -- 'drier', -- 'drift', -- 'driller', -- 'drilling', -- 'drinkable', -- 'drinking', -- 'dripping', -- 'drippy', -- 'drivable', -- 'driven', -- 'driver', -- 'driveway', -- 'driving', -- 'drizzle', -- 'drizzly', -- 'drone', -- 'drool', -- 'droop', -- 'drop-down', -- 'dropbox', -- 'dropkick', -- 'droplet', -- 'dropout', -- 'dropper', -- 'drove', -- 'drown', -- 'drowsily', -- 'drudge', -- 'drum', -- 'dry', -- 'dubbed', -- 'dubiously', -- 'duchess', -- 'duckbill', -- 'ducking', -- 'duckling', -- 'ducktail', -- 'ducky', -- 'duct', -- 'dude', -- 'duffel', -- 'dugout', -- 'duh', -- 'duke', -- 'duller', -- 'dullness', -- 'duly', -- 'dumping', -- 'dumpling', -- 'dumpster', -- 'duo', -- 'dupe', -- 'duplex', -- 'duplicate', -- 'duplicity', -- 'durable', -- 'durably', -- 'duration', -- 'duress', -- 'during', -- 'dusk', -- 'dust', -- 'dutiful', -- 'duty', -- 'duvet', -- 'dwarf', -- 'dweeb', -- 'dwelled', -- 'dweller', -- 'dwelling', -- 'dwindle', -- 'dwindling', -- 'dynamic', -- 'dynamite', -- 'dynasty', -- 'dyslexia', -- 'dyslexic', -- 'each', -- 'eagle', -- 'earache', -- 'eardrum', -- 'earflap', -- 'earful', -- 'earlobe', -- 'early', -- 'earmark', -- 'earmuff', -- 'earphone', -- 'earpiece', -- 'earplugs', -- 'earring', -- 'earshot', -- 'earthen', -- 'earthlike', -- 'earthling', -- 'earthly', -- 'earthworm', -- 'earthy', -- 'earwig', -- 'easeful', -- 'easel', -- 'easiest', -- 'easily', -- 'easiness', -- 'easing', -- 'eastbound', -- 'eastcoast', -- 'easter', -- 'eastward', -- 'eatable', -- 'eaten', -- 'eatery', -- 'eating', -- 'eats', -- 'ebay', -- 'ebony', -- 'ebook', -- 'ecard', -- 'eccentric', -- 'echo', -- 'eclair', -- 'eclipse', -- 'ecologist', -- 'ecology', -- 'economic', -- 'economist', -- 'economy', -- 'ecosphere', -- 'ecosystem', -- 'edge', -- 'edginess', -- 'edging', -- 'edgy', -- 'edition', -- 'editor', -- 'educated', -- 'education', -- 'educator', -- 'eel', -- 'effective', -- 'effects', -- 'efficient', -- 'effort', -- 'eggbeater', -- 'egging', -- 'eggnog', -- 'eggplant', -- 'eggshell', -- 'egomaniac', -- 'egotism', -- 'egotistic', -- 'either', -- 'eject', -- 'elaborate', -- 'elastic', -- 'elated', -- 'elbow', -- 'eldercare', -- 'elderly', -- 'eldest', -- 'electable', -- 'election', -- 'elective', -- 'elephant', -- 'elevate', -- 'elevating', -- 'elevation', -- 'elevator', -- 'eleven', -- 'elf', -- 'eligible', -- 'eligibly', -- 'eliminate', -- 'elite', -- 'elitism', -- 'elixir', -- 'elk', -- 'ellipse', -- 'elliptic', -- 'elm', -- 'elongated', -- 'elope', -- 'eloquence', -- 'eloquent', -- 'elsewhere', -- 'elude', -- 'elusive', -- 'elves', -- 'email', -- 'embargo', -- 'embark', -- 'embassy', -- 'embattled', -- 'embellish', -- 'ember', -- 'embezzle', -- 'emblaze', -- 'emblem', -- 'embody', -- 'embolism', -- 'emboss', -- 'embroider', -- 'emcee', -- 'emerald', -- 'emergency', -- 'emission', -- 'emit', -- 'emote', -- 'emoticon', -- 'emotion', -- 'empathic', -- 'empathy', -- 'emperor', -- 'emphases', -- 'emphasis', -- 'emphasize', -- 'emphatic', -- 'empirical', -- 'employed', -- 'employee', -- 'employer', -- 'emporium', -- 'empower', -- 'emptier', -- 'emptiness', -- 'empty', -- 'emu', -- 'enable', -- 'enactment', -- 'enamel', -- 'enchanted', -- 'enchilada', -- 'encircle', -- 'enclose', -- 'enclosure', -- 'encode', -- 'encore', -- 'encounter', -- 'encourage', -- 'encroach', -- 'encrust', -- 'encrypt', -- 'endanger', -- 'endeared', -- 'endearing', -- 'ended', -- 'ending', -- 'endless', -- 'endnote', -- 'endocrine', -- 'endorphin', -- 'endorse', -- 'endowment', -- 'endpoint', -- 'endurable', -- 'endurance', -- 'enduring', -- 'energetic', -- 'energize', -- 'energy', -- 'enforced', -- 'enforcer', -- 'engaged', -- 'engaging', -- 'engine', -- 'engorge', -- 'engraved', -- 'engraver', -- 'engraving', -- 'engross', -- 'engulf', -- 'enhance', -- 'enigmatic', -- 'enjoyable', -- 'enjoyably', -- 'enjoyer', -- 'enjoying', -- 'enjoyment', -- 'enlarged', -- 'enlarging', -- 'enlighten', -- 'enlisted', -- 'enquirer', -- 'enrage', -- 'enrich', -- 'enroll', -- 'enslave', -- 'ensnare', -- 'ensure', -- 'entail', -- 'entangled', -- 'entering', -- 'entertain', -- 'enticing', -- 'entire', -- 'entitle', -- 'entity', -- 'entomb', -- 'entourage', -- 'entrap', -- 'entree', -- 'entrench', -- 'entrust', -- 'entryway', -- 'entwine', -- 'enunciate', -- 'envelope', -- 'enviable', -- 'enviably', -- 'envious', -- 'envision', -- 'envoy', -- 'envy', -- 'enzyme', -- 'epic', -- 'epidemic', -- 'epidermal', -- 'epidermis', -- 'epidural', -- 'epilepsy', -- 'epileptic', -- 'epilogue', -- 'epiphany', -- 'episode', -- 'equal', -- 'equate', -- 'equation', -- 'equator', -- 'equinox', -- 'equipment', -- 'equity', -- 'equivocal', -- 'eradicate', -- 'erasable', -- 'erased', -- 'eraser', -- 'erasure', -- 'ergonomic', -- 'errand', -- 'errant', -- 'erratic', -- 'error', -- 'erupt', -- 'escalate', -- 'escalator', -- 'escapable', -- 'escapade', -- 'escapist', -- 'escargot', -- 'eskimo', -- 'esophagus', -- 'espionage', -- 'espresso', -- 'esquire', -- 'essay', -- 'essence', -- 'essential', -- 'establish', -- 'estate', -- 'esteemed', -- 'estimate', -- 'estimator', -- 'estranged', -- 'estrogen', -- 'etching', -- 'eternal', -- 'eternity', -- 'ethanol', -- 'ether', -- 'ethically', -- 'ethics', -- 'euphemism', -- 'evacuate', -- 'evacuee', -- 'evade', -- 'evaluate', -- 'evaluator', -- 'evaporate', -- 'evasion', -- 'evasive', -- 'even', -- 'everglade', -- 'evergreen', -- 'everybody', -- 'everyday', -- 'everyone', -- 'evict', -- 'evidence', -- 'evident', -- 'evil', -- 'evoke', -- 'evolution', -- 'evolve', -- 'exact', -- 'exalted', -- 'example', -- 'excavate', -- 'excavator', -- 'exceeding', -- 'exception', -- 'excess', -- 'exchange', -- 'excitable', -- 'exciting', -- 'exclaim', -- 'exclude', -- 'excluding', -- 'exclusion', -- 'exclusive', -- 'excretion', -- 'excretory', -- 'excursion', -- 'excusable', -- 'excusably', -- 'excuse', -- 'exemplary', -- 'exemplify', -- 'exemption', -- 'exerciser', -- 'exert', -- 'exes', -- 'exfoliate', -- 'exhale', -- 'exhaust', -- 'exhume', -- 'exile', -- 'existing', -- 'exit', -- 'exodus', -- 'exonerate', -- 'exorcism', -- 'exorcist', -- 'expand', -- 'expanse', -- 'expansion', -- 'expansive', -- 'expectant', -- 'expedited', -- 'expediter', -- 'expel', -- 'expend', -- 'expenses', -- 'expensive', -- 'expert', -- 'expire', -- 'expiring', -- 'explain', -- 'expletive', -- 'explicit', -- 'explode', -- 'exploit', -- 'explore', -- 'exploring', -- 'exponent', -- 'exporter', -- 'exposable', -- 'expose', -- 'exposure', -- 'express', -- 'expulsion', -- 'exquisite', -- 'extended', -- 'extending', -- 'extent', -- 'extenuate', -- 'exterior', -- 'external', -- 'extinct', -- 'extortion', -- 'extradite', -- 'extras', -- 'extrovert', -- 'extrude', -- 'extruding', -- 'exuberant', -- 'fable', -- 'fabric', -- 'fabulous', -- 'facebook', -- 'facecloth', -- 'facedown', -- 'faceless', -- 'facelift', -- 'faceplate', -- 'faceted', -- 'facial', -- 'facility', -- 'facing', -- 'facsimile', -- 'faction', -- 'factoid', -- 'factor', -- 'factsheet', -- 'factual', -- 'faculty', -- 'fade', -- 'fading', -- 'failing', -- 'falcon', -- 'fall', -- 'false', -- 'falsify', -- 'fame', -- 'familiar', -- 'family', -- 'famine', -- 'famished', -- 'fanatic', -- 'fancied', -- 'fanciness', -- 'fancy', -- 'fanfare', -- 'fang', -- 'fanning', -- 'fantasize', -- 'fantastic', -- 'fantasy', -- 'fascism', -- 'fastball', -- 'faster', -- 'fasting', -- 'fastness', -- 'faucet', -- 'favorable', -- 'favorably', -- 'favored', -- 'favoring', -- 'favorite', -- 'fax', -- 'feast', -- 'federal', -- 'fedora', -- 'feeble', -- 'feed', -- 'feel', -- 'feisty', -- 'feline', -- 'felt-tip', -- 'feminine', -- 'feminism', -- 'feminist', -- 'feminize', -- 'femur', -- 'fence', -- 'fencing', -- 'fender', -- 'ferment', -- 'fernlike', -- 'ferocious', -- 'ferocity', -- 'ferret', -- 'ferris', -- 'ferry', -- 'fervor', -- 'fester', -- 'festival', -- 'festive', -- 'festivity', -- 'fetal', -- 'fetch', -- 'fever', -- 'fiber', -- 'fiction', -- 'fiddle', -- 'fiddling', -- 'fidelity', -- 'fidgeting', -- 'fidgety', -- 'fifteen', -- 'fifth', -- 'fiftieth', -- 'fifty', -- 'figment', -- 'figure', -- 'figurine', -- 'filing', -- 'filled', -- 'filler', -- 'filling', -- 'film', -- 'filter', -- 'filth', -- 'filtrate', -- 'finale', -- 'finalist', -- 'finalize', -- 'finally', -- 'finance', -- 'financial', -- 'finch', -- 'fineness', -- 'finer', -- 'finicky', -- 'finished', -- 'finisher', -- 'finishing', -- 'finite', -- 'finless', -- 'finlike', -- 'fiscally', -- 'fit', -- 'five', -- 'flaccid', -- 'flagman', -- 'flagpole', -- 'flagship', -- 'flagstick', -- 'flagstone', -- 'flail', -- 'flakily', -- 'flaky', -- 'flame', -- 'flammable', -- 'flanked', -- 'flanking', -- 'flannels', -- 'flap', -- 'flaring', -- 'flashback', -- 'flashbulb', -- 'flashcard', -- 'flashily', -- 'flashing', -- 'flashy', -- 'flask', -- 'flatbed', -- 'flatfoot', -- 'flatly', -- 'flatness', -- 'flatten', -- 'flattered', -- 'flatterer', -- 'flattery', -- 'flattop', -- 'flatware', -- 'flatworm', -- 'flavored', -- 'flavorful', -- 'flavoring', -- 'flaxseed', -- 'fled', -- 'fleshed', -- 'fleshy', -- 'flick', -- 'flier', -- 'flight', -- 'flinch', -- 'fling', -- 'flint', -- 'flip', -- 'flirt', -- 'float', -- 'flock', -- 'flogging', -- 'flop', -- 'floral', -- 'florist', -- 'floss', -- 'flounder', -- 'flyable', -- 'flyaway', -- 'flyer', -- 'flying', -- 'flyover', -- 'flypaper', -- 'foam', -- 'foe', -- 'fog', -- 'foil', -- 'folic', -- 'folk', -- 'follicle', -- 'follow', -- 'fondling', -- 'fondly', -- 'fondness', -- 'fondue', -- 'font', -- 'food', -- 'fool', -- 'footage', -- 'football', -- 'footbath', -- 'footboard', -- 'footer', -- 'footgear', -- 'foothill', -- 'foothold', -- 'footing', -- 'footless', -- 'footman', -- 'footnote', -- 'footpad', -- 'footpath', -- 'footprint', -- 'footrest', -- 'footsie', -- 'footsore', -- 'footwear', -- 'footwork', -- 'fossil', -- 'foster', -- 'founder', -- 'founding', -- 'fountain', -- 'fox', -- 'foyer', -- 'fraction', -- 'fracture', -- 'fragile', -- 'fragility', -- 'fragment', -- 'fragrance', -- 'fragrant', -- 'frail', -- 'frame', -- 'framing', -- 'frantic', -- 'fraternal', -- 'frayed', -- 'fraying', -- 'frays', -- 'freckled', -- 'freckles', -- 'freebase', -- 'freebee', -- 'freebie', -- 'freedom', -- 'freefall', -- 'freehand', -- 'freeing', -- 'freeload', -- 'freely', -- 'freemason', -- 'freeness', -- 'freestyle', -- 'freeware', -- 'freeway', -- 'freewill', -- 'freezable', -- 'freezing', -- 'freight', -- 'french', -- 'frenzied', -- 'frenzy', -- 'frequency', -- 'frequent', -- 'fresh', -- 'fretful', -- 'fretted', -- 'friction', -- 'friday', -- 'fridge', -- 'fried', -- 'friend', -- 'frighten', -- 'frightful', -- 'frigidity', -- 'frigidly', -- 'frill', -- 'fringe', -- 'frisbee', -- 'frisk', -- 'fritter', -- 'frivolous', -- 'frolic', -- 'from', -- 'front', -- 'frostbite', -- 'frosted', -- 'frostily', -- 'frosting', -- 'frostlike', -- 'frosty', -- 'froth', -- 'frown', -- 'frozen', -- 'fructose', -- 'frugality', -- 'frugally', -- 'fruit', -- 'frustrate', -- 'frying', -- 'gab', -- 'gaffe', -- 'gag', -- 'gainfully', -- 'gaining', -- 'gains', -- 'gala', -- 'gallantly', -- 'galleria', -- 'gallery', -- 'galley', -- 'gallon', -- 'gallows', -- 'gallstone', -- 'galore', -- 'galvanize', -- 'gambling', -- 'game', -- 'gaming', -- 'gamma', -- 'gander', -- 'gangly', -- 'gangrene', -- 'gangway', -- 'gap', -- 'garage', -- 'garbage', -- 'garden', -- 'gargle', -- 'garland', -- 'garlic', -- 'garment', -- 'garnet', -- 'garnish', -- 'garter', -- 'gas', -- 'gatherer', -- 'gathering', -- 'gating', -- 'gauging', -- 'gauntlet', -- 'gauze', -- 'gave', -- 'gawk', -- 'gazing', -- 'gear', -- 'gecko', -- 'geek', -- 'geiger', -- 'gem', -- 'gender', -- 'generic', -- 'generous', -- 'genetics', -- 'genre', -- 'gentile', -- 'gentleman', -- 'gently', -- 'gents', -- 'geography', -- 'geologic', -- 'geologist', -- 'geology', -- 'geometric', -- 'geometry', -- 'geranium', -- 'gerbil', -- 'geriatric', -- 'germicide', -- 'germinate', -- 'germless', -- 'germproof', -- 'gestate', -- 'gestation', -- 'gesture', -- 'getaway', -- 'getting', -- 'getup', -- 'giant', -- 'gibberish', -- 'giblet', -- 'giddily', -- 'giddiness', -- 'giddy', -- 'gift', -- 'gigabyte', -- 'gigahertz', -- 'gigantic', -- 'giggle', -- 'giggling', -- 'giggly', -- 'gigolo', -- 'gilled', -- 'gills', -- 'gimmick', -- 'girdle', -- 'giveaway', -- 'given', -- 'giver', -- 'giving', -- 'gizmo', -- 'gizzard', -- 'glacial', -- 'glacier', -- 'glade', -- 'gladiator', -- 'gladly', -- 'glamorous', -- 'glamour', -- 'glance', -- 'glancing', -- 'glandular', -- 'glare', -- 'glaring', -- 'glass', -- 'glaucoma', -- 'glazing', -- 'gleaming', -- 'gleeful', -- 'glider', -- 'gliding', -- 'glimmer', -- 'glimpse', -- 'glisten', -- 'glitch', -- 'glitter', -- 'glitzy', -- 'gloater', -- 'gloating', -- 'gloomily', -- 'gloomy', -- 'glorified', -- 'glorifier', -- 'glorify', -- 'glorious', -- 'glory', -- 'gloss', -- 'glove', -- 'glowing', -- 'glowworm', -- 'glucose', -- 'glue', -- 'gluten', -- 'glutinous', -- 'glutton', -- 'gnarly', -- 'gnat', -- 'goal', -- 'goatskin', -- 'goes', -- 'goggles', -- 'going', -- 'goldfish', -- 'goldmine', -- 'goldsmith', -- 'golf', -- 'goliath', -- 'gonad', -- 'gondola', -- 'gone', -- 'gong', -- 'good', -- 'gooey', -- 'goofball', -- 'goofiness', -- 'goofy', -- 'google', -- 'goon', -- 'gopher', -- 'gore', -- 'gorged', -- 'gorgeous', -- 'gory', -- 'gosling', -- 'gossip', -- 'gothic', -- 'gotten', -- 'gout', -- 'gown', -- 'grab', -- 'graceful', -- 'graceless', -- 'gracious', -- 'gradation', -- 'graded', -- 'grader', -- 'gradient', -- 'grading', -- 'gradually', -- 'graduate', -- 'graffiti', -- 'grafted', -- 'grafting', -- 'grain', -- 'granddad', -- 'grandkid', -- 'grandly', -- 'grandma', -- 'grandpa', -- 'grandson', -- 'granite', -- 'granny', -- 'granola', -- 'grant', -- 'granular', -- 'grape', -- 'graph', -- 'grapple', -- 'grappling', -- 'grasp', -- 'grass', -- 'gratified', -- 'gratify', -- 'grating', -- 'gratitude', -- 'gratuity', -- 'gravel', -- 'graveness', -- 'graves', -- 'graveyard', -- 'gravitate', -- 'gravity', -- 'gravy', -- 'gray', -- 'grazing', -- 'greasily', -- 'greedily', -- 'greedless', -- 'greedy', -- 'green', -- 'greeter', -- 'greeting', -- 'grew', -- 'greyhound', -- 'grid', -- 'grief', -- 'grievance', -- 'grieving', -- 'grievous', -- 'grill', -- 'grimace', -- 'grimacing', -- 'grime', -- 'griminess', -- 'grimy', -- 'grinch', -- 'grinning', -- 'grip', -- 'gristle', -- 'grit', -- 'groggily', -- 'groggy', -- 'groin', -- 'groom', -- 'groove', -- 'grooving', -- 'groovy', -- 'grope', -- 'ground', -- 'grouped', -- 'grout', -- 'grove', -- 'grower', -- 'growing', -- 'growl', -- 'grub', -- 'grudge', -- 'grudging', -- 'grueling', -- 'gruffly', -- 'grumble', -- 'grumbling', -- 'grumbly', -- 'grumpily', -- 'grunge', -- 'grunt', -- 'guacamole', -- 'guidable', -- 'guidance', -- 'guide', -- 'guiding', -- 'guileless', -- 'guise', -- 'gulf', -- 'gullible', -- 'gully', -- 'gulp', -- 'gumball', -- 'gumdrop', -- 'gumminess', -- 'gumming', -- 'gummy', -- 'gurgle', -- 'gurgling', -- 'guru', -- 'gush', -- 'gusto', -- 'gusty', -- 'gutless', -- 'guts', -- 'gutter', -- 'guy', -- 'guzzler', -- 'gyration', -- 'habitable', -- 'habitant', -- 'habitat', -- 'habitual', -- 'hacked', -- 'hacker', -- 'hacking', -- 'hacksaw', -- 'had', -- 'haggler', -- 'haiku', -- 'half', -- 'halogen', -- 'halt', -- 'halved', -- 'halves', -- 'hamburger', -- 'hamlet', -- 'hammock', -- 'hamper', -- 'hamster', -- 'hamstring', -- 'handbag', -- 'handball', -- 'handbook', -- 'handbrake', -- 'handcart', -- 'handclap', -- 'handclasp', -- 'handcraft', -- 'handcuff', -- 'handed', -- 'handful', -- 'handgrip', -- 'handgun', -- 'handheld', -- 'handiness', -- 'handiwork', -- 'handlebar', -- 'handled', -- 'handler', -- 'handling', -- 'handmade', -- 'handoff', -- 'handpick', -- 'handprint', -- 'handrail', -- 'handsaw', -- 'handset', -- 'handsfree', -- 'handshake', -- 'handstand', -- 'handwash', -- 'handwork', -- 'handwoven', -- 'handwrite', -- 'handyman', -- 'hangnail', -- 'hangout', -- 'hangover', -- 'hangup', -- 'hankering', -- 'hankie', -- 'hanky', -- 'haphazard', -- 'happening', -- 'happier', -- 'happiest', -- 'happily', -- 'happiness', -- 'happy', -- 'harbor', -- 'hardcopy', -- 'hardcore', -- 'hardcover', -- 'harddisk', -- 'hardened', -- 'hardener', -- 'hardening', -- 'hardhat', -- 'hardhead', -- 'hardiness', -- 'hardly', -- 'hardness', -- 'hardship', -- 'hardware', -- 'hardwired', -- 'hardwood', -- 'hardy', -- 'harmful', -- 'harmless', -- 'harmonica', -- 'harmonics', -- 'harmonize', -- 'harmony', -- 'harness', -- 'harpist', -- 'harsh', -- 'harvest', -- 'hash', -- 'hassle', -- 'haste', -- 'hastily', -- 'hastiness', -- 'hasty', -- 'hatbox', -- 'hatchback', -- 'hatchery', -- 'hatchet', -- 'hatching', -- 'hatchling', -- 'hate', -- 'hatless', -- 'hatred', -- 'haunt', -- 'haven', -- 'hazard', -- 'hazelnut', -- 'hazily', -- 'haziness', -- 'hazing', -- 'hazy', -- 'headache', -- 'headband', -- 'headboard', -- 'headcount', -- 'headdress', -- 'headed', -- 'header', -- 'headfirst', -- 'headgear', -- 'heading', -- 'headlamp', -- 'headless', -- 'headlock', -- 'headphone', -- 'headpiece', -- 'headrest', -- 'headroom', -- 'headscarf', -- 'headset', -- 'headsman', -- 'headstand', -- 'headstone', -- 'headway', -- 'headwear', -- 'heap', -- 'heat', -- 'heave', -- 'heavily', -- 'heaviness', -- 'heaving', -- 'hedge', -- 'hedging', -- 'heftiness', -- 'hefty', -- 'helium', -- 'helmet', -- 'helper', -- 'helpful', -- 'helping', -- 'helpless', -- 'helpline', -- 'hemlock', -- 'hemstitch', -- 'hence', -- 'henchman', -- 'henna', -- 'herald', -- 'herbal', -- 'herbicide', -- 'herbs', -- 'heritage', -- 'hermit', -- 'heroics', -- 'heroism', -- 'herring', -- 'herself', -- 'hertz', -- 'hesitancy', -- 'hesitant', -- 'hesitate', -- 'hexagon', -- 'hexagram', -- 'hubcap', -- 'huddle', -- 'huddling', -- 'huff', -- 'hug', -- 'hula', -- 'hulk', -- 'hull', -- 'human', -- 'humble', -- 'humbling', -- 'humbly', -- 'humid', -- 'humiliate', -- 'humility', -- 'humming', -- 'hummus', -- 'humongous', -- 'humorist', -- 'humorless', -- 'humorous', -- 'humpback', -- 'humped', -- 'humvee', -- 'hunchback', -- 'hundredth', -- 'hunger', -- 'hungrily', -- 'hungry', -- 'hunk', -- 'hunter', -- 'hunting', -- 'huntress', -- 'huntsman', -- 'hurdle', -- 'hurled', -- 'hurler', -- 'hurling', -- 'hurray', -- 'hurricane', -- 'hurried', -- 'hurry', -- 'hurt', -- 'husband', -- 'hush', -- 'husked', -- 'huskiness', -- 'hut', -- 'hybrid', -- 'hydrant', -- 'hydrated', -- 'hydration', -- 'hydrogen', -- 'hydroxide', -- 'hyperlink', -- 'hypertext', -- 'hyphen', -- 'hypnoses', -- 'hypnosis', -- 'hypnotic', -- 'hypnotism', -- 'hypnotist', -- 'hypnotize', -- 'hypocrisy', -- 'hypocrite', -- 'ibuprofen', -- 'ice', -- 'iciness', -- 'icing', -- 'icky', -- 'icon', -- 'icy', -- 'idealism', -- 'idealist', -- 'idealize', -- 'ideally', -- 'idealness', -- 'identical', -- 'identify', -- 'identity', -- 'ideology', -- 'idiocy', -- 'idiom', -- 'idly', -- 'igloo', -- 'ignition', -- 'ignore', -- 'iguana', -- 'illicitly', -- 'illusion', -- 'illusive', -- 'image', -- 'imaginary', -- 'imagines', -- 'imaging', -- 'imbecile', -- 'imitate', -- 'imitation', -- 'immature', -- 'immerse', -- 'immersion', -- 'imminent', -- 'immobile', -- 'immodest', -- 'immorally', -- 'immortal', -- 'immovable', -- 'immovably', -- 'immunity', -- 'immunize', -- 'impaired', -- 'impale', -- 'impart', -- 'impatient', -- 'impeach', -- 'impeding', -- 'impending', -- 'imperfect', -- 'imperial', -- 'impish', -- 'implant', -- 'implement', -- 'implicate', -- 'implicit', -- 'implode', -- 'implosion', -- 'implosive', -- 'imply', -- 'impolite', -- 'important', -- 'importer', -- 'impose', -- 'imposing', -- 'impotence', -- 'impotency', -- 'impotent', -- 'impound', -- 'imprecise', -- 'imprint', -- 'imprison', -- 'impromptu', -- 'improper', -- 'improve', -- 'improving', -- 'improvise', -- 'imprudent', -- 'impulse', -- 'impulsive', -- 'impure', -- 'impurity', -- 'iodine', -- 'iodize', -- 'ion', -- 'ipad', -- 'iphone', -- 'ipod', -- 'irate', -- 'irk', -- 'iron', -- 'irregular', -- 'irrigate', -- 'irritable', -- 'irritably', -- 'irritant', -- 'irritate', -- 'islamic', -- 'islamist', -- 'isolated', -- 'isolating', -- 'isolation', -- 'isotope', -- 'issue', -- 'issuing', -- 'italicize', -- 'italics', -- 'item', -- 'itinerary', -- 'itunes', -- 'ivory', -- 'ivy', -- 'jab', -- 'jackal', -- 'jacket', -- 'jackknife', -- 'jackpot', -- 'jailbird', -- 'jailbreak', -- 'jailer', -- 'jailhouse', -- 'jalapeno', -- 'jam', -- 'janitor', -- 'january', -- 'jargon', -- 'jarring', -- 'jasmine', -- 'jaundice', -- 'jaunt', -- 'java', -- 'jawed', -- 'jawless', -- 'jawline', -- 'jaws', -- 'jaybird', -- 'jaywalker', -- 'jazz', -- 'jeep', -- 'jeeringly', -- 'jellied', -- 'jelly', -- 'jersey', -- 'jester', -- 'jet', -- 'jiffy', -- 'jigsaw', -- 'jimmy', -- 'jingle', -- 'jingling', -- 'jinx', -- 'jitters', -- 'jittery', -- 'job', -- 'jockey', -- 'jockstrap', -- 'jogger', -- 'jogging', -- 'john', -- 'joining', -- 'jokester', -- 'jokingly', -- 'jolliness', -- 'jolly', -- 'jolt', -- 'jot', -- 'jovial', -- 'joyfully', -- 'joylessly', -- 'joyous', -- 'joyride', -- 'joystick', -- 'jubilance', -- 'jubilant', -- 'judge', -- 'judgingly', -- 'judicial', -- 'judiciary', -- 'judo', -- 'juggle', -- 'juggling', -- 'jugular', -- 'juice', -- 'juiciness', -- 'juicy', -- 'jujitsu', -- 'jukebox', -- 'july', -- 'jumble', -- 'jumbo', -- 'jump', -- 'junction', -- 'juncture', -- 'june', -- 'junior', -- 'juniper', -- 'junkie', -- 'junkman', -- 'junkyard', -- 'jurist', -- 'juror', -- 'jury', -- 'justice', -- 'justifier', -- 'justify', -- 'justly', -- 'justness', -- 'juvenile', -- 'kabob', -- 'kangaroo', -- 'karaoke', -- 'karate', -- 'karma', -- 'kebab', -- 'keenly', -- 'keenness', -- 'keep', -- 'keg', -- 'kelp', -- 'kennel', -- 'kept', -- 'kerchief', -- 'kerosene', -- 'kettle', -- 'kick', -- 'kiln', -- 'kilobyte', -- 'kilogram', -- 'kilometer', -- 'kilowatt', -- 'kilt', -- 'kimono', -- 'kindle', -- 'kindling', -- 'kindly', -- 'kindness', -- 'kindred', -- 'kinetic', -- 'kinfolk', -- 'king', -- 'kinship', -- 'kinsman', -- 'kinswoman', -- 'kissable', -- 'kisser', -- 'kissing', -- 'kitchen', -- 'kite', -- 'kitten', -- 'kitty', -- 'kiwi', -- 'kleenex', -- 'knapsack', -- 'knee', -- 'knelt', -- 'knickers', -- 'knoll', -- 'koala', -- 'kooky', -- 'kosher', -- 'krypton', -- 'kudos', -- 'kung', -- 'labored', -- 'laborer', -- 'laboring', -- 'laborious', -- 'labrador', -- 'ladder', -- 'ladies', -- 'ladle', -- 'ladybug', -- 'ladylike', -- 'lagged', -- 'lagging', -- 'lagoon', -- 'lair', -- 'lake', -- 'lance', -- 'landed', -- 'landfall', -- 'landfill', -- 'landing', -- 'landlady', -- 'landless', -- 'landline', -- 'landlord', -- 'landmark', -- 'landmass', -- 'landmine', -- 'landowner', -- 'landscape', -- 'landside', -- 'landslide', -- 'language', -- 'lankiness', -- 'lanky', -- 'lantern', -- 'lapdog', -- 'lapel', -- 'lapped', -- 'lapping', -- 'laptop', -- 'lard', -- 'large', -- 'lark', -- 'lash', -- 'lasso', -- 'last', -- 'latch', -- 'late', -- 'lather', -- 'latitude', -- 'latrine', -- 'latter', -- 'latticed', -- 'launch', -- 'launder', -- 'laundry', -- 'laurel', -- 'lavender', -- 'lavish', -- 'laxative', -- 'lazily', -- 'laziness', -- 'lazy', -- 'lecturer', -- 'left', -- 'legacy', -- 'legal', -- 'legend', -- 'legged', -- 'leggings', -- 'legible', -- 'legibly', -- 'legislate', -- 'lego', -- 'legroom', -- 'legume', -- 'legwarmer', -- 'legwork', -- 'lemon', -- 'lend', -- 'length', -- 'lens', -- 'lent', -- 'leotard', -- 'lesser', -- 'letdown', -- 'lethargic', -- 'lethargy', -- 'letter', -- 'lettuce', -- 'level', -- 'leverage', -- 'levers', -- 'levitate', -- 'levitator', -- 'liability', -- 'liable', -- 'liberty', -- 'librarian', -- 'library', -- 'licking', -- 'licorice', -- 'lid', -- 'life', -- 'lifter', -- 'lifting', -- 'liftoff', -- 'ligament', -- 'likely', -- 'likeness', -- 'likewise', -- 'liking', -- 'lilac', -- 'lilly', -- 'lily', -- 'limb', -- 'limeade', -- 'limelight', -- 'limes', -- 'limit', -- 'limping', -- 'limpness', -- 'line', -- 'lingo', -- 'linguini', -- 'linguist', -- 'lining', -- 'linked', -- 'linoleum', -- 'linseed', -- 'lint', -- 'lion', -- 'lip', -- 'liquefy', -- 'liqueur', -- 'liquid', -- 'lisp', -- 'list', -- 'litigate', -- 'litigator', -- 'litmus', -- 'litter', -- 'little', -- 'livable', -- 'lived', -- 'lively', -- 'liver', -- 'livestock', -- 'lividly', -- 'living', -- 'lizard', -- 'lubricant', -- 'lubricate', -- 'lucid', -- 'luckily', -- 'luckiness', -- 'luckless', -- 'lucrative', -- 'ludicrous', -- 'lugged', -- 'lukewarm', -- 'lullaby', -- 'lumber', -- 'luminance', -- 'luminous', -- 'lumpiness', -- 'lumping', -- 'lumpish', -- 'lunacy', -- 'lunar', -- 'lunchbox', -- 'luncheon', -- 'lunchroom', -- 'lunchtime', -- 'lung', -- 'lurch', -- 'lure', -- 'luridness', -- 'lurk', -- 'lushly', -- 'lushness', -- 'luster', -- 'lustfully', -- 'lustily', -- 'lustiness', -- 'lustrous', -- 'lusty', -- 'luxurious', -- 'luxury', -- 'lying', -- 'lyrically', -- 'lyricism', -- 'lyricist', -- 'lyrics', -- 'macarena', -- 'macaroni', -- 'macaw', -- 'mace', -- 'machine', -- 'machinist', -- 'magazine', -- 'magenta', -- 'maggot', -- 'magical', -- 'magician', -- 'magma', -- 'magnesium', -- 'magnetic', -- 'magnetism', -- 'magnetize', -- 'magnifier', -- 'magnify', -- 'magnitude', -- 'magnolia', -- 'mahogany', -- 'maimed', -- 'majestic', -- 'majesty', -- 'majorette', -- 'majority', -- 'makeover', -- 'maker', -- 'makeshift', -- 'making', -- 'malformed', -- 'malt', -- 'mama', -- 'mammal', -- 'mammary', -- 'mammogram', -- 'manager', -- 'managing', -- 'manatee', -- 'mandarin', -- 'mandate', -- 'mandatory', -- 'mandolin', -- 'manger', -- 'mangle', -- 'mango', -- 'mangy', -- 'manhandle', -- 'manhole', -- 'manhood', -- 'manhunt', -- 'manicotti', -- 'manicure', -- 'manifesto', -- 'manila', -- 'mankind', -- 'manlike', -- 'manliness', -- 'manly', -- 'manmade', -- 'manned', -- 'mannish', -- 'manor', -- 'manpower', -- 'mantis', -- 'mantra', -- 'manual', -- 'many', -- 'map', -- 'marathon', -- 'marauding', -- 'marbled', -- 'marbles', -- 'marbling', -- 'march', -- 'mardi', -- 'margarine', -- 'margarita', -- 'margin', -- 'marigold', -- 'marina', -- 'marine', -- 'marital', -- 'maritime', -- 'marlin', -- 'marmalade', -- 'maroon', -- 'married', -- 'marrow', -- 'marry', -- 'marshland', -- 'marshy', -- 'marsupial', -- 'marvelous', -- 'marxism', -- 'mascot', -- 'masculine', -- 'mashed', -- 'mashing', -- 'massager', -- 'masses', -- 'massive', -- 'mastiff', -- 'matador', -- 'matchbook', -- 'matchbox', -- 'matcher', -- 'matching', -- 'matchless', -- 'material', -- 'maternal', -- 'maternity', -- 'math', -- 'mating', -- 'matriarch', -- 'matrimony', -- 'matrix', -- 'matron', -- 'matted', -- 'matter', -- 'maturely', -- 'maturing', -- 'maturity', -- 'mauve', -- 'maverick', -- 'maximize', -- 'maximum', -- 'maybe', -- 'mayday', -- 'mayflower', -- 'moaner', -- 'moaning', -- 'mobile', -- 'mobility', -- 'mobilize', -- 'mobster', -- 'mocha', -- 'mocker', -- 'mockup', -- 'modified', -- 'modify', -- 'modular', -- 'modulator', -- 'module', -- 'moisten', -- 'moistness', -- 'moisture', -- 'molar', -- 'molasses', -- 'mold', -- 'molecular', -- 'molecule', -- 'molehill', -- 'mollusk', -- 'mom', -- 'monastery', -- 'monday', -- 'monetary', -- 'monetize', -- 'moneybags', -- 'moneyless', -- 'moneywise', -- 'mongoose', -- 'mongrel', -- 'monitor', -- 'monkhood', -- 'monogamy', -- 'monogram', -- 'monologue', -- 'monopoly', -- 'monorail', -- 'monotone', -- 'monotype', -- 'monoxide', -- 'monsieur', -- 'monsoon', -- 'monstrous', -- 'monthly', -- 'monument', -- 'moocher', -- 'moodiness', -- 'moody', -- 'mooing', -- 'moonbeam', -- 'mooned', -- 'moonlight', -- 'moonlike', -- 'moonlit', -- 'moonrise', -- 'moonscape', -- 'moonshine', -- 'moonstone', -- 'moonwalk', -- 'mop', -- 'morale', -- 'morality', -- 'morally', -- 'morbidity', -- 'morbidly', -- 'morphine', -- 'morphing', -- 'morse', -- 'mortality', -- 'mortally', -- 'mortician', -- 'mortified', -- 'mortify', -- 'mortuary', -- 'mosaic', -- 'mossy', -- 'most', -- 'mothball', -- 'mothproof', -- 'motion', -- 'motivate', -- 'motivator', -- 'motive', -- 'motocross', -- 'motor', -- 'motto', -- 'mountable', -- 'mountain', -- 'mounted', -- 'mounting', -- 'mourner', -- 'mournful', -- 'mouse', -- 'mousiness', -- 'moustache', -- 'mousy', -- 'mouth', -- 'movable', -- 'move', -- 'movie', -- 'moving', -- 'mower', -- 'mowing', -- 'much', -- 'muck', -- 'mud', -- 'mug', -- 'mulberry', -- 'mulch', -- 'mule', -- 'mulled', -- 'mullets', -- 'multiple', -- 'multiply', -- 'multitask', -- 'multitude', -- 'mumble', -- 'mumbling', -- 'mumbo', -- 'mummified', -- 'mummify', -- 'mummy', -- 'mumps', -- 'munchkin', -- 'mundane', -- 'municipal', -- 'muppet', -- 'mural', -- 'murkiness', -- 'murky', -- 'murmuring', -- 'muscular', -- 'museum', -- 'mushily', -- 'mushiness', -- 'mushroom', -- 'mushy', -- 'music', -- 'musket', -- 'muskiness', -- 'musky', -- 'mustang', -- 'mustard', -- 'muster', -- 'mustiness', -- 'musty', -- 'mutable', -- 'mutate', -- 'mutation', -- 'mute', -- 'mutilated', -- 'mutilator', -- 'mutiny', -- 'mutt', -- 'mutual', -- 'muzzle', -- 'myself', -- 'myspace', -- 'mystified', -- 'mystify', -- 'myth', -- 'nacho', -- 'nag', -- 'nail', -- 'name', -- 'naming', -- 'nanny', -- 'nanometer', -- 'nape', -- 'napkin', -- 'napped', -- 'napping', -- 'nappy', -- 'narrow', -- 'nastily', -- 'nastiness', -- 'national', -- 'native', -- 'nativity', -- 'natural', -- 'nature', -- 'naturist', -- 'nautical', -- 'navigate', -- 'navigator', -- 'navy', -- 'nearby', -- 'nearest', -- 'nearly', -- 'nearness', -- 'neatly', -- 'neatness', -- 'nebula', -- 'nebulizer', -- 'nectar', -- 'negate', -- 'negation', -- 'negative', -- 'neglector', -- 'negligee', -- 'negligent', -- 'negotiate', -- 'nemeses', -- 'nemesis', -- 'neon', -- 'nephew', -- 'nerd', -- 'nervous', -- 'nervy', -- 'nest', -- 'net', -- 'neurology', -- 'neuron', -- 'neurosis', -- 'neurotic', -- 'neuter', -- 'neutron', -- 'never', -- 'next', -- 'nibble', -- 'nickname', -- 'nicotine', -- 'niece', -- 'nifty', -- 'nimble', -- 'nimbly', -- 'nineteen', -- 'ninetieth', -- 'ninja', -- 'nintendo', -- 'ninth', -- 'nuclear', -- 'nuclei', -- 'nucleus', -- 'nugget', -- 'nullify', -- 'number', -- 'numbing', -- 'numbly', -- 'numbness', -- 'numeral', -- 'numerate', -- 'numerator', -- 'numeric', -- 'numerous', -- 'nuptials', -- 'nursery', -- 'nursing', -- 'nurture', -- 'nutcase', -- 'nutlike', -- 'nutmeg', -- 'nutrient', -- 'nutshell', -- 'nuttiness', -- 'nutty', -- 'nuzzle', -- 'nylon', -- 'oaf', -- 'oak', -- 'oasis', -- 'oat', -- 'obedience', -- 'obedient', -- 'obituary', -- 'object', -- 'obligate', -- 'obliged', -- 'oblivion', -- 'oblivious', -- 'oblong', -- 'obnoxious', -- 'oboe', -- 'obscure', -- 'obscurity', -- 'observant', -- 'observer', -- 'observing', -- 'obsessed', -- 'obsession', -- 'obsessive', -- 'obsolete', -- 'obstacle', -- 'obstinate', -- 'obstruct', -- 'obtain', -- 'obtrusive', -- 'obtuse', -- 'obvious', -- 'occultist', -- 'occupancy', -- 'occupant', -- 'occupier', -- 'occupy', -- 'ocean', -- 'ocelot', -- 'octagon', -- 'octane', -- 'october', -- 'octopus', -- 'ogle', -- 'oil', -- 'oink', -- 'ointment', -- 'okay', -- 'old', -- 'olive', -- 'olympics', -- 'omega', -- 'omen', -- 'ominous', -- 'omission', -- 'omit', -- 'omnivore', -- 'onboard', -- 'oncoming', -- 'ongoing', -- 'onion', -- 'online', -- 'onlooker', -- 'only', -- 'onscreen', -- 'onset', -- 'onshore', -- 'onslaught', -- 'onstage', -- 'onto', -- 'onward', -- 'onyx', -- 'oops', -- 'ooze', -- 'oozy', -- 'opacity', -- 'opal', -- 'open', -- 'operable', -- 'operate', -- 'operating', -- 'operation', -- 'operative', -- 'operator', -- 'opium', -- 'opossum', -- 'opponent', -- 'oppose', -- 'opposing', -- 'opposite', -- 'oppressed', -- 'oppressor', -- 'opt', -- 'opulently', -- 'osmosis', -- 'other', -- 'otter', -- 'ouch', -- 'ought', -- 'ounce', -- 'outage', -- 'outback', -- 'outbid', -- 'outboard', -- 'outbound', -- 'outbreak', -- 'outburst', -- 'outcast', -- 'outclass', -- 'outcome', -- 'outdated', -- 'outdoors', -- 'outer', -- 'outfield', -- 'outfit', -- 'outflank', -- 'outgoing', -- 'outgrow', -- 'outhouse', -- 'outing', -- 'outlast', -- 'outlet', -- 'outline', -- 'outlook', -- 'outlying', -- 'outmatch', -- 'outmost', -- 'outnumber', -- 'outplayed', -- 'outpost', -- 'outpour', -- 'output', -- 'outrage', -- 'outrank', -- 'outreach', -- 'outright', -- 'outscore', -- 'outsell', -- 'outshine', -- 'outshoot', -- 'outsider', -- 'outskirts', -- 'outsmart', -- 'outsource', -- 'outspoken', -- 'outtakes', -- 'outthink', -- 'outward', -- 'outweigh', -- 'outwit', -- 'oval', -- 'ovary', -- 'oven', -- 'overact', -- 'overall', -- 'overarch', -- 'overbid', -- 'overbill', -- 'overbite', -- 'overblown', -- 'overboard', -- 'overbook', -- 'overbuilt', -- 'overcast', -- 'overcoat', -- 'overcome', -- 'overcook', -- 'overcrowd', -- 'overdraft', -- 'overdrawn', -- 'overdress', -- 'overdrive', -- 'overdue', -- 'overeager', -- 'overeater', -- 'overexert', -- 'overfed', -- 'overfeed', -- 'overfill', -- 'overflow', -- 'overfull', -- 'overgrown', -- 'overhand', -- 'overhang', -- 'overhaul', -- 'overhead', -- 'overhear', -- 'overheat', -- 'overhung', -- 'overjoyed', -- 'overkill', -- 'overlabor', -- 'overlaid', -- 'overlap', -- 'overlay', -- 'overload', -- 'overlook', -- 'overlord', -- 'overlying', -- 'overnight', -- 'overpass', -- 'overpay', -- 'overplant', -- 'overplay', -- 'overpower', -- 'overprice', -- 'overrate', -- 'overreach', -- 'overreact', -- 'override', -- 'overripe', -- 'overrule', -- 'overrun', -- 'overshoot', -- 'overshot', -- 'oversight', -- 'oversized', -- 'oversleep', -- 'oversold', -- 'overspend', -- 'overstate', -- 'overstay', -- 'overstep', -- 'overstock', -- 'overstuff', -- 'oversweet', -- 'overtake', -- 'overthrow', -- 'overtime', -- 'overtly', -- 'overtone', -- 'overture', -- 'overturn', -- 'overuse', -- 'overvalue', -- 'overview', -- 'overwrite', -- 'owl', -- 'oxford', -- 'oxidant', -- 'oxidation', -- 'oxidize', -- 'oxidizing', -- 'oxygen', -- 'oxymoron', -- 'oyster', -- 'ozone', -- 'paced', -- 'pacemaker', -- 'pacific', -- 'pacifier', -- 'pacifism', -- 'pacifist', -- 'pacify', -- 'padded', -- 'padding', -- 'paddle', -- 'paddling', -- 'padlock', -- 'pagan', -- 'pager', -- 'paging', -- 'pajamas', -- 'palace', -- 'palatable', -- 'palm', -- 'palpable', -- 'palpitate', -- 'paltry', -- 'pampered', -- 'pamperer', -- 'pampers', -- 'pamphlet', -- 'panama', -- 'pancake', -- 'pancreas', -- 'panda', -- 'pandemic', -- 'pang', -- 'panhandle', -- 'panic', -- 'panning', -- 'panorama', -- 'panoramic', -- 'panther', -- 'pantomime', -- 'pantry', -- 'pants', -- 'pantyhose', -- 'paparazzi', -- 'papaya', -- 'paper', -- 'paprika', -- 'papyrus', -- 'parabola', -- 'parachute', -- 'parade', -- 'paradox', -- 'paragraph', -- 'parakeet', -- 'paralegal', -- 'paralyses', -- 'paralysis', -- 'paralyze', -- 'paramedic', -- 'parameter', -- 'paramount', -- 'parasail', -- 'parasite', -- 'parasitic', -- 'parcel', -- 'parched', -- 'parchment', -- 'pardon', -- 'parish', -- 'parka', -- 'parking', -- 'parkway', -- 'parlor', -- 'parmesan', -- 'parole', -- 'parrot', -- 'parsley', -- 'parsnip', -- 'partake', -- 'parted', -- 'parting', -- 'partition', -- 'partly', -- 'partner', -- 'partridge', -- 'party', -- 'passable', -- 'passably', -- 'passage', -- 'passcode', -- 'passenger', -- 'passerby', -- 'passing', -- 'passion', -- 'passive', -- 'passivism', -- 'passover', -- 'passport', -- 'password', -- 'pasta', -- 'pasted', -- 'pastel', -- 'pastime', -- 'pastor', -- 'pastrami', -- 'pasture', -- 'pasty', -- 'patchwork', -- 'patchy', -- 'paternal', -- 'paternity', -- 'path', -- 'patience', -- 'patient', -- 'patio', -- 'patriarch', -- 'patriot', -- 'patrol', -- 'patronage', -- 'patronize', -- 'pauper', -- 'pavement', -- 'paver', -- 'pavestone', -- 'pavilion', -- 'paving', -- 'pawing', -- 'payable', -- 'payback', -- 'paycheck', -- 'payday', -- 'payee', -- 'payer', -- 'paying', -- 'payment', -- 'payphone', -- 'payroll', -- 'pebble', -- 'pebbly', -- 'pecan', -- 'pectin', -- 'peculiar', -- 'peddling', -- 'pediatric', -- 'pedicure', -- 'pedigree', -- 'pedometer', -- 'pegboard', -- 'pelican', -- 'pellet', -- 'pelt', -- 'pelvis', -- 'penalize', -- 'penalty', -- 'pencil', -- 'pendant', -- 'pending', -- 'penholder', -- 'penknife', -- 'pennant', -- 'penniless', -- 'penny', -- 'penpal', -- 'pension', -- 'pentagon', -- 'pentagram', -- 'pep', -- 'perceive', -- 'percent', -- 'perch', -- 'percolate', -- 'perennial', -- 'perfected', -- 'perfectly', -- 'perfume', -- 'periscope', -- 'perish', -- 'perjurer', -- 'perjury', -- 'perkiness', -- 'perky', -- 'perm', -- 'peroxide', -- 'perpetual', -- 'perplexed', -- 'persecute', -- 'persevere', -- 'persuaded', -- 'persuader', -- 'pesky', -- 'peso', -- 'pessimism', -- 'pessimist', -- 'pester', -- 'pesticide', -- 'petal', -- 'petite', -- 'petition', -- 'petri', -- 'petroleum', -- 'petted', -- 'petticoat', -- 'pettiness', -- 'petty', -- 'petunia', -- 'phantom', -- 'phobia', -- 'phoenix', -- 'phonebook', -- 'phoney', -- 'phonics', -- 'phoniness', -- 'phony', -- 'phosphate', -- 'photo', -- 'phrase', -- 'phrasing', -- 'placard', -- 'placate', -- 'placidly', -- 'plank', -- 'planner', -- 'plant', -- 'plasma', -- 'plaster', -- 'plastic', -- 'plated', -- 'platform', -- 'plating', -- 'platinum', -- 'platonic', -- 'platter', -- 'platypus', -- 'plausible', -- 'plausibly', -- 'playable', -- 'playback', -- 'player', -- 'playful', -- 'playgroup', -- 'playhouse', -- 'playing', -- 'playlist', -- 'playmaker', -- 'playmate', -- 'playoff', -- 'playpen', -- 'playroom', -- 'playset', -- 'plaything', -- 'playtime', -- 'plaza', -- 'pleading', -- 'pleat', -- 'pledge', -- 'plentiful', -- 'plenty', -- 'plethora', -- 'plexiglas', -- 'pliable', -- 'plod', -- 'plop', -- 'plot', -- 'plow', -- 'ploy', -- 'pluck', -- 'plug', -- 'plunder', -- 'plunging', -- 'plural', -- 'plus', -- 'plutonium', -- 'plywood', -- 'poach', -- 'pod', -- 'poem', -- 'poet', -- 'pogo', -- 'pointed', -- 'pointer', -- 'pointing', -- 'pointless', -- 'pointy', -- 'poise', -- 'poison', -- 'poker', -- 'poking', -- 'polar', -- 'police', -- 'policy', -- 'polio', -- 'polish', -- 'politely', -- 'polka', -- 'polo', -- 'polyester', -- 'polygon', -- 'polygraph', -- 'polymer', -- 'poncho', -- 'pond', -- 'pony', -- 'popcorn', -- 'pope', -- 'poplar', -- 'popper', -- 'poppy', -- 'popsicle', -- 'populace', -- 'popular', -- 'populate', -- 'porcupine', -- 'pork', -- 'porous', -- 'porridge', -- 'portable', -- 'portal', -- 'portfolio', -- 'porthole', -- 'portion', -- 'portly', -- 'portside', -- 'poser', -- 'posh', -- 'posing', -- 'possible', -- 'possibly', -- 'possum', -- 'postage', -- 'postal', -- 'postbox', -- 'postcard', -- 'posted', -- 'poster', -- 'posting', -- 'postnasal', -- 'posture', -- 'postwar', -- 'pouch', -- 'pounce', -- 'pouncing', -- 'pound', -- 'pouring', -- 'pout', -- 'powdered', -- 'powdering', -- 'powdery', -- 'power', -- 'powwow', -- 'pox', -- 'praising', -- 'prance', -- 'prancing', -- 'pranker', -- 'prankish', -- 'prankster', -- 'prayer', -- 'praying', -- 'preacher', -- 'preaching', -- 'preachy', -- 'preamble', -- 'precinct', -- 'precise', -- 'precision', -- 'precook', -- 'precut', -- 'predator', -- 'predefine', -- 'predict', -- 'preface', -- 'prefix', -- 'preflight', -- 'preformed', -- 'pregame', -- 'pregnancy', -- 'pregnant', -- 'preheated', -- 'prelaunch', -- 'prelaw', -- 'prelude', -- 'premiere', -- 'premises', -- 'premium', -- 'prenatal', -- 'preoccupy', -- 'preorder', -- 'prepaid', -- 'prepay', -- 'preplan', -- 'preppy', -- 'preschool', -- 'prescribe', -- 'preseason', -- 'preset', -- 'preshow', -- 'president', -- 'presoak', -- 'press', -- 'presume', -- 'presuming', -- 'preteen', -- 'pretended', -- 'pretender', -- 'pretense', -- 'pretext', -- 'pretty', -- 'pretzel', -- 'prevail', -- 'prevalent', -- 'prevent', -- 'preview', -- 'previous', -- 'prewar', -- 'prewashed', -- 'prideful', -- 'pried', -- 'primal', -- 'primarily', -- 'primary', -- 'primate', -- 'primer', -- 'primp', -- 'princess', -- 'print', -- 'prior', -- 'prism', -- 'prison', -- 'prissy', -- 'pristine', -- 'privacy', -- 'private', -- 'privatize', -- 'prize', -- 'proactive', -- 'probable', -- 'probably', -- 'probation', -- 'probe', -- 'probing', -- 'probiotic', -- 'problem', -- 'procedure', -- 'process', -- 'proclaim', -- 'procreate', -- 'procurer', -- 'prodigal', -- 'prodigy', -- 'produce', -- 'product', -- 'profane', -- 'profanity', -- 'professed', -- 'professor', -- 'profile', -- 'profound', -- 'profusely', -- 'progeny', -- 'prognosis', -- 'program', -- 'progress', -- 'projector', -- 'prologue', -- 'prolonged', -- 'promenade', -- 'prominent', -- 'promoter', -- 'promotion', -- 'prompter', -- 'promptly', -- 'prone', -- 'prong', -- 'pronounce', -- 'pronto', -- 'proofing', -- 'proofread', -- 'proofs', -- 'propeller', -- 'properly', -- 'property', -- 'proponent', -- 'proposal', -- 'propose', -- 'props', -- 'prorate', -- 'protector', -- 'protegee', -- 'proton', -- 'prototype', -- 'protozoan', -- 'protract', -- 'protrude', -- 'proud', -- 'provable', -- 'proved', -- 'proven', -- 'provided', -- 'provider', -- 'providing', -- 'province', -- 'proving', -- 'provoke', -- 'provoking', -- 'provolone', -- 'prowess', -- 'prowler', -- 'prowling', -- 'proximity', -- 'proxy', -- 'prozac', -- 'prude', -- 'prudishly', -- 'prune', -- 'pruning', -- 'pry', -- 'psychic', -- 'public', -- 'publisher', -- 'pucker', -- 'pueblo', -- 'pug', -- 'pull', -- 'pulmonary', -- 'pulp', -- 'pulsate', -- 'pulse', -- 'pulverize', -- 'puma', -- 'pumice', -- 'pummel', -- 'punch', -- 'punctual', -- 'punctuate', -- 'punctured', -- 'pungent', -- 'punisher', -- 'punk', -- 'pupil', -- 'puppet', -- 'puppy', -- 'purchase', -- 'pureblood', -- 'purebred', -- 'purely', -- 'pureness', -- 'purgatory', -- 'purge', -- 'purging', -- 'purifier', -- 'purify', -- 'purist', -- 'puritan', -- 'purity', -- 'purple', -- 'purplish', -- 'purposely', -- 'purr', -- 'purse', -- 'pursuable', -- 'pursuant', -- 'pursuit', -- 'purveyor', -- 'pushcart', -- 'pushchair', -- 'pusher', -- 'pushiness', -- 'pushing', -- 'pushover', -- 'pushpin', -- 'pushup', -- 'pushy', -- 'putdown', -- 'putt', -- 'puzzle', -- 'puzzling', -- 'pyramid', -- 'pyromania', -- 'python', -- 'quack', -- 'quadrant', -- 'quail', -- 'quaintly', -- 'quake', -- 'quaking', -- 'qualified', -- 'qualifier', -- 'qualify', -- 'quality', -- 'qualm', -- 'quantum', -- 'quarrel', -- 'quarry', -- 'quartered', -- 'quarterly', -- 'quarters', -- 'quartet', -- 'quench', -- 'query', -- 'quicken', -- 'quickly', -- 'quickness', -- 'quicksand', -- 'quickstep', -- 'quiet', -- 'quill', -- 'quilt', -- 'quintet', -- 'quintuple', -- 'quirk', -- 'quit', -- 'quiver', -- 'quizzical', -- 'quotable', -- 'quotation', -- 'quote', -- 'rabid', -- 'race', -- 'racing', -- 'racism', -- 'rack', -- 'racoon', -- 'radar', -- 'radial', -- 'radiance', -- 'radiantly', -- 'radiated', -- 'radiation', -- 'radiator', -- 'radio', -- 'radish', -- 'raffle', -- 'raft', -- 'rage', -- 'ragged', -- 'raging', -- 'ragweed', -- 'raider', -- 'railcar', -- 'railing', -- 'railroad', -- 'railway', -- 'raisin', -- 'rake', -- 'raking', -- 'rally', -- 'ramble', -- 'rambling', -- 'ramp', -- 'ramrod', -- 'ranch', -- 'rancidity', -- 'random', -- 'ranged', -- 'ranger', -- 'ranging', -- 'ranked', -- 'ranking', -- 'ransack', -- 'ranting', -- 'rants', -- 'rare', -- 'rarity', -- 'rascal', -- 'rash', -- 'rasping', -- 'ravage', -- 'raven', -- 'ravine', -- 'raving', -- 'ravioli', -- 'ravishing', -- 'reabsorb', -- 'reach', -- 'reacquire', -- 'reaction', -- 'reactive', -- 'reactor', -- 'reaffirm', -- 'ream', -- 'reanalyze', -- 'reappear', -- 'reapply', -- 'reappoint', -- 'reapprove', -- 'rearrange', -- 'rearview', -- 'reason', -- 'reassign', -- 'reassure', -- 'reattach', -- 'reawake', -- 'rebalance', -- 'rebate', -- 'rebel', -- 'rebirth', -- 'reboot', -- 'reborn', -- 'rebound', -- 'rebuff', -- 'rebuild', -- 'rebuilt', -- 'reburial', -- 'rebuttal', -- 'recall', -- 'recant', -- 'recapture', -- 'recast', -- 'recede', -- 'recent', -- 'recess', -- 'recharger', -- 'recipient', -- 'recital', -- 'recite', -- 'reckless', -- 'reclaim', -- 'recliner', -- 'reclining', -- 'recluse', -- 'reclusive', -- 'recognize', -- 'recoil', -- 'recollect', -- 'recolor', -- 'reconcile', -- 'reconfirm', -- 'reconvene', -- 'recopy', -- 'record', -- 'recount', -- 'recoup', -- 'recovery', -- 'recreate', -- 'rectal', -- 'rectangle', -- 'rectified', -- 'rectify', -- 'recycled', -- 'recycler', -- 'recycling', -- 'reemerge', -- 'reenact', -- 'reenter', -- 'reentry', -- 'reexamine', -- 'referable', -- 'referee', -- 'reference', -- 'refill', -- 'refinance', -- 'refined', -- 'refinery', -- 'refining', -- 'refinish', -- 'reflected', -- 'reflector', -- 'reflex', -- 'reflux', -- 'refocus', -- 'refold', -- 'reforest', -- 'reformat', -- 'reformed', -- 'reformer', -- 'reformist', -- 'refract', -- 'refrain', -- 'refreeze', -- 'refresh', -- 'refried', -- 'refueling', -- 'refund', -- 'refurbish', -- 'refurnish', -- 'refusal', -- 'refuse', -- 'refusing', -- 'refutable', -- 'refute', -- 'regain', -- 'regalia', -- 'regally', -- 'reggae', -- 'regime', -- 'region', -- 'register', -- 'registrar', -- 'registry', -- 'regress', -- 'regretful', -- 'regroup', -- 'regular', -- 'regulate', -- 'regulator', -- 'rehab', -- 'reheat', -- 'rehire', -- 'rehydrate', -- 'reimburse', -- 'reissue', -- 'reiterate', -- 'rejoice', -- 'rejoicing', -- 'rejoin', -- 'rekindle', -- 'relapse', -- 'relapsing', -- 'relatable', -- 'related', -- 'relation', -- 'relative', -- 'relax', -- 'relay', -- 'relearn', -- 'release', -- 'relenting', -- 'reliable', -- 'reliably', -- 'reliance', -- 'reliant', -- 'relic', -- 'relieve', -- 'relieving', -- 'relight', -- 'relish', -- 'relive', -- 'reload', -- 'relocate', -- 'relock', -- 'reluctant', -- 'rely', -- 'remake', -- 'remark', -- 'remarry', -- 'rematch', -- 'remedial', -- 'remedy', -- 'remember', -- 'reminder', -- 'remindful', -- 'remission', -- 'remix', -- 'remnant', -- 'remodeler', -- 'remold', -- 'remorse', -- 'remote', -- 'removable', -- 'removal', -- 'removed', -- 'remover', -- 'removing', -- 'rename', -- 'renderer', -- 'rendering', -- 'rendition', -- 'renegade', -- 'renewable', -- 'renewably', -- 'renewal', -- 'renewed', -- 'renounce', -- 'renovate', -- 'renovator', -- 'rentable', -- 'rental', -- 'rented', -- 'renter', -- 'reoccupy', -- 'reoccur', -- 'reopen', -- 'reorder', -- 'repackage', -- 'repacking', -- 'repaint', -- 'repair', -- 'repave', -- 'repaying', -- 'repayment', -- 'repeal', -- 'repeated', -- 'repeater', -- 'repent', -- 'rephrase', -- 'replace', -- 'replay', -- 'replica', -- 'reply', -- 'reporter', -- 'repose', -- 'repossess', -- 'repost', -- 'repressed', -- 'reprimand', -- 'reprint', -- 'reprise', -- 'reproach', -- 'reprocess', -- 'reproduce', -- 'reprogram', -- 'reps', -- 'reptile', -- 'reptilian', -- 'repugnant', -- 'repulsion', -- 'repulsive', -- 'repurpose', -- 'reputable', -- 'reputably', -- 'request', -- 'require', -- 'requisite', -- 'reroute', -- 'rerun', -- 'resale', -- 'resample', -- 'rescuer', -- 'reseal', -- 'research', -- 'reselect', -- 'reseller', -- 'resemble', -- 'resend', -- 'resent', -- 'reset', -- 'reshape', -- 'reshoot', -- 'reshuffle', -- 'residence', -- 'residency', -- 'resident', -- 'residual', -- 'residue', -- 'resigned', -- 'resilient', -- 'resistant', -- 'resisting', -- 'resize', -- 'resolute', -- 'resolved', -- 'resonant', -- 'resonate', -- 'resort', -- 'resource', -- 'respect', -- 'resubmit', -- 'result', -- 'resume', -- 'resupply', -- 'resurface', -- 'resurrect', -- 'retail', -- 'retainer', -- 'retaining', -- 'retake', -- 'retaliate', -- 'retention', -- 'rethink', -- 'retinal', -- 'retired', -- 'retiree', -- 'retiring', -- 'retold', -- 'retool', -- 'retorted', -- 'retouch', -- 'retrace', -- 'retract', -- 'retrain', -- 'retread', -- 'retreat', -- 'retrial', -- 'retrieval', -- 'retriever', -- 'retry', -- 'return', -- 'retying', -- 'retype', -- 'reunion', -- 'reunite', -- 'reusable', -- 'reuse', -- 'reveal', -- 'reveler', -- 'revenge', -- 'revenue', -- 'reverb', -- 'revered', -- 'reverence', -- 'reverend', -- 'reversal', -- 'reverse', -- 'reversing', -- 'reversion', -- 'revert', -- 'revisable', -- 'revise', -- 'revision', -- 'revisit', -- 'revivable', -- 'revival', -- 'reviver', -- 'reviving', -- 'revocable', -- 'revoke', -- 'revolt', -- 'revolver', -- 'revolving', -- 'reward', -- 'rewash', -- 'rewind', -- 'rewire', -- 'reword', -- 'rework', -- 'rewrap', -- 'rewrite', -- 'rhyme', -- 'ribbon', -- 'ribcage', -- 'rice', -- 'riches', -- 'richly', -- 'richness', -- 'rickety', -- 'ricotta', -- 'riddance', -- 'ridden', -- 'ride', -- 'riding', -- 'rifling', -- 'rift', -- 'rigging', -- 'rigid', -- 'rigor', -- 'rimless', -- 'rimmed', -- 'rind', -- 'rink', -- 'rinse', -- 'rinsing', -- 'riot', -- 'ripcord', -- 'ripeness', -- 'ripening', -- 'ripping', -- 'ripple', -- 'rippling', -- 'riptide', -- 'rise', -- 'rising', -- 'risk', -- 'risotto', -- 'ritalin', -- 'ritzy', -- 'rival', -- 'riverbank', -- 'riverbed', -- 'riverboat', -- 'riverside', -- 'riveter', -- 'riveting', -- 'roamer', -- 'roaming', -- 'roast', -- 'robbing', -- 'robe', -- 'robin', -- 'robotics', -- 'robust', -- 'rockband', -- 'rocker', -- 'rocket', -- 'rockfish', -- 'rockiness', -- 'rocking', -- 'rocklike', -- 'rockslide', -- 'rockstar', -- 'rocky', -- 'rogue', -- 'roman', -- 'romp', -- 'rope', -- 'roping', -- 'roster', -- 'rosy', -- 'rotten', -- 'rotting', -- 'rotunda', -- 'roulette', -- 'rounding', -- 'roundish', -- 'roundness', -- 'roundup', -- 'roundworm', -- 'routine', -- 'routing', -- 'rover', -- 'roving', -- 'royal', -- 'rubbed', -- 'rubber', -- 'rubbing', -- 'rubble', -- 'rubdown', -- 'ruby', -- 'ruckus', -- 'rudder', -- 'rug', -- 'ruined', -- 'rule', -- 'rumble', -- 'rumbling', -- 'rummage', -- 'rumor', -- 'runaround', -- 'rundown', -- 'runner', -- 'running', -- 'runny', -- 'runt', -- 'runway', -- 'rupture', -- 'rural', -- 'ruse', -- 'rush', -- 'rust', -- 'rut', -- 'sabbath', -- 'sabotage', -- 'sacrament', -- 'sacred', -- 'sacrifice', -- 'sadden', -- 'saddlebag', -- 'saddled', -- 'saddling', -- 'sadly', -- 'sadness', -- 'safari', -- 'safeguard', -- 'safehouse', -- 'safely', -- 'safeness', -- 'saffron', -- 'saga', -- 'sage', -- 'sagging', -- 'saggy', -- 'said', -- 'saint', -- 'sake', -- 'salad', -- 'salami', -- 'salaried', -- 'salary', -- 'saline', -- 'salon', -- 'saloon', -- 'salsa', -- 'salt', -- 'salutary', -- 'salute', -- 'salvage', -- 'salvaging', -- 'salvation', -- 'same', -- 'sample', -- 'sampling', -- 'sanction', -- 'sanctity', -- 'sanctuary', -- 'sandal', -- 'sandbag', -- 'sandbank', -- 'sandbar', -- 'sandblast', -- 'sandbox', -- 'sanded', -- 'sandfish', -- 'sanding', -- 'sandlot', -- 'sandpaper', -- 'sandpit', -- 'sandstone', -- 'sandstorm', -- 'sandworm', -- 'sandy', -- 'sanitary', -- 'sanitizer', -- 'sank', -- 'santa', -- 'sapling', -- 'sappiness', -- 'sappy', -- 'sarcasm', -- 'sarcastic', -- 'sardine', -- 'sash', -- 'sasquatch', -- 'sassy', -- 'satchel', -- 'satiable', -- 'satin', -- 'satirical', -- 'satisfied', -- 'satisfy', -- 'saturate', -- 'saturday', -- 'sauciness', -- 'saucy', -- 'sauna', -- 'savage', -- 'savanna', -- 'saved', -- 'savings', -- 'savior', -- 'savor', -- 'saxophone', -- 'say', -- 'scabbed', -- 'scabby', -- 'scalded', -- 'scalding', -- 'scale', -- 'scaling', -- 'scallion', -- 'scallop', -- 'scalping', -- 'scam', -- 'scandal', -- 'scanner', -- 'scanning', -- 'scant', -- 'scapegoat', -- 'scarce', -- 'scarcity', -- 'scarecrow', -- 'scared', -- 'scarf', -- 'scarily', -- 'scariness', -- 'scarring', -- 'scary', -- 'scavenger', -- 'scenic', -- 'schedule', -- 'schematic', -- 'scheme', -- 'scheming', -- 'schilling', -- 'schnapps', -- 'scholar', -- 'science', -- 'scientist', -- 'scion', -- 'scoff', -- 'scolding', -- 'scone', -- 'scoop', -- 'scooter', -- 'scope', -- 'scorch', -- 'scorebook', -- 'scorecard', -- 'scored', -- 'scoreless', -- 'scorer', -- 'scoring', -- 'scorn', -- 'scorpion', -- 'scotch', -- 'scoundrel', -- 'scoured', -- 'scouring', -- 'scouting', -- 'scouts', -- 'scowling', -- 'scrabble', -- 'scraggly', -- 'scrambled', -- 'scrambler', -- 'scrap', -- 'scratch', -- 'scrawny', -- 'screen', -- 'scribble', -- 'scribe', -- 'scribing', -- 'scrimmage', -- 'script', -- 'scroll', -- 'scrooge', -- 'scrounger', -- 'scrubbed', -- 'scrubber', -- 'scruffy', -- 'scrunch', -- 'scrutiny', -- 'scuba', -- 'scuff', -- 'sculptor', -- 'sculpture', -- 'scurvy', -- 'scuttle', -- 'secluded', -- 'secluding', -- 'seclusion', -- 'second', -- 'secrecy', -- 'secret', -- 'sectional', -- 'sector', -- 'secular', -- 'securely', -- 'security', -- 'sedan', -- 'sedate', -- 'sedation', -- 'sedative', -- 'sediment', -- 'seduce', -- 'seducing', -- 'segment', -- 'seismic', -- 'seizing', -- 'seldom', -- 'selected', -- 'selection', -- 'selective', -- 'selector', -- 'self', -- 'seltzer', -- 'semantic', -- 'semester', -- 'semicolon', -- 'semifinal', -- 'seminar', -- 'semisoft', -- 'semisweet', -- 'senate', -- 'senator', -- 'send', -- 'senior', -- 'senorita', -- 'sensation', -- 'sensitive', -- 'sensitize', -- 'sensually', -- 'sensuous', -- 'sepia', -- 'september', -- 'septic', -- 'septum', -- 'sequel', -- 'sequence', -- 'sequester', -- 'series', -- 'sermon', -- 'serotonin', -- 'serpent', -- 'serrated', -- 'serve', -- 'service', -- 'serving', -- 'sesame', -- 'sessions', -- 'setback', -- 'setting', -- 'settle', -- 'settling', -- 'setup', -- 'sevenfold', -- 'seventeen', -- 'seventh', -- 'seventy', -- 'severity', -- 'shabby', -- 'shack', -- 'shaded', -- 'shadily', -- 'shadiness', -- 'shading', -- 'shadow', -- 'shady', -- 'shaft', -- 'shakable', -- 'shakily', -- 'shakiness', -- 'shaking', -- 'shaky', -- 'shale', -- 'shallot', -- 'shallow', -- 'shame', -- 'shampoo', -- 'shamrock', -- 'shank', -- 'shanty', -- 'shape', -- 'shaping', -- 'share', -- 'sharpener', -- 'sharper', -- 'sharpie', -- 'sharply', -- 'sharpness', -- 'shawl', -- 'sheath', -- 'shed', -- 'sheep', -- 'sheet', -- 'shelf', -- 'shell', -- 'shelter', -- 'shelve', -- 'shelving', -- 'sherry', -- 'shield', -- 'shifter', -- 'shifting', -- 'shiftless', -- 'shifty', -- 'shimmer', -- 'shimmy', -- 'shindig', -- 'shine', -- 'shingle', -- 'shininess', -- 'shining', -- 'shiny', -- 'ship', -- 'shirt', -- 'shivering', -- 'shock', -- 'shone', -- 'shoplift', -- 'shopper', -- 'shopping', -- 'shoptalk', -- 'shore', -- 'shortage', -- 'shortcake', -- 'shortcut', -- 'shorten', -- 'shorter', -- 'shorthand', -- 'shortlist', -- 'shortly', -- 'shortness', -- 'shorts', -- 'shortwave', -- 'shorty', -- 'shout', -- 'shove', -- 'showbiz', -- 'showcase', -- 'showdown', -- 'shower', -- 'showgirl', -- 'showing', -- 'showman', -- 'shown', -- 'showoff', -- 'showpiece', -- 'showplace', -- 'showroom', -- 'showy', -- 'shrank', -- 'shrapnel', -- 'shredder', -- 'shredding', -- 'shrewdly', -- 'shriek', -- 'shrill', -- 'shrimp', -- 'shrine', -- 'shrink', -- 'shrivel', -- 'shrouded', -- 'shrubbery', -- 'shrubs', -- 'shrug', -- 'shrunk', -- 'shucking', -- 'shudder', -- 'shuffle', -- 'shuffling', -- 'shun', -- 'shush', -- 'shut', -- 'shy', -- 'siamese', -- 'siberian', -- 'sibling', -- 'siding', -- 'sierra', -- 'siesta', -- 'sift', -- 'sighing', -- 'silenced', -- 'silencer', -- 'silent', -- 'silica', -- 'silicon', -- 'silk', -- 'silliness', -- 'silly', -- 'silo', -- 'silt', -- 'silver', -- 'similarly', -- 'simile', -- 'simmering', -- 'simple', -- 'simplify', -- 'simply', -- 'sincere', -- 'sincerity', -- 'singer', -- 'singing', -- 'single', -- 'singular', -- 'sinister', -- 'sinless', -- 'sinner', -- 'sinuous', -- 'sip', -- 'siren', -- 'sister', -- 'sitcom', -- 'sitter', -- 'sitting', -- 'situated', -- 'situation', -- 'sixfold', -- 'sixteen', -- 'sixth', -- 'sixties', -- 'sixtieth', -- 'sixtyfold', -- 'sizable', -- 'sizably', -- 'size', -- 'sizing', -- 'sizzle', -- 'sizzling', -- 'skater', -- 'skating', -- 'skedaddle', -- 'skeletal', -- 'skeleton', -- 'skeptic', -- 'sketch', -- 'skewed', -- 'skewer', -- 'skid', -- 'skied', -- 'skier', -- 'skies', -- 'skiing', -- 'skilled', -- 'skillet', -- 'skillful', -- 'skimmed', -- 'skimmer', -- 'skimming', -- 'skimpily', -- 'skincare', -- 'skinhead', -- 'skinless', -- 'skinning', -- 'skinny', -- 'skintight', -- 'skipper', -- 'skipping', -- 'skirmish', -- 'skirt', -- 'skittle', -- 'skydiver', -- 'skylight', -- 'skyline', -- 'skype', -- 'skyrocket', -- 'skyward', -- 'slab', -- 'slacked', -- 'slacker', -- 'slacking', -- 'slackness', -- 'slacks', -- 'slain', -- 'slam', -- 'slander', -- 'slang', -- 'slapping', -- 'slapstick', -- 'slashed', -- 'slashing', -- 'slate', -- 'slather', -- 'slaw', -- 'sled', -- 'sleek', -- 'sleep', -- 'sleet', -- 'sleeve', -- 'slept', -- 'sliceable', -- 'sliced', -- 'slicer', -- 'slicing', -- 'slick', -- 'slider', -- 'slideshow', -- 'sliding', -- 'slighted', -- 'slighting', -- 'slightly', -- 'slimness', -- 'slimy', -- 'slinging', -- 'slingshot', -- 'slinky', -- 'slip', -- 'slit', -- 'sliver', -- 'slobbery', -- 'slogan', -- 'sloped', -- 'sloping', -- 'sloppily', -- 'sloppy', -- 'slot', -- 'slouching', -- 'slouchy', -- 'sludge', -- 'slug', -- 'slum', -- 'slurp', -- 'slush', -- 'sly', -- 'small', -- 'smartly', -- 'smartness', -- 'smasher', -- 'smashing', -- 'smashup', -- 'smell', -- 'smelting', -- 'smile', -- 'smilingly', -- 'smirk', -- 'smite', -- 'smith', -- 'smitten', -- 'smock', -- 'smog', -- 'smoked', -- 'smokeless', -- 'smokiness', -- 'smoking', -- 'smoky', -- 'smolder', -- 'smooth', -- 'smother', -- 'smudge', -- 'smudgy', -- 'smuggler', -- 'smuggling', -- 'smugly', -- 'smugness', -- 'snack', -- 'snagged', -- 'snaking', -- 'snap', -- 'snare', -- 'snarl', -- 'snazzy', -- 'sneak', -- 'sneer', -- 'sneeze', -- 'sneezing', -- 'snide', -- 'sniff', -- 'snippet', -- 'snipping', -- 'snitch', -- 'snooper', -- 'snooze', -- 'snore', -- 'snoring', -- 'snorkel', -- 'snort', -- 'snout', -- 'snowbird', -- 'snowboard', -- 'snowbound', -- 'snowcap', -- 'snowdrift', -- 'snowdrop', -- 'snowfall', -- 'snowfield', -- 'snowflake', -- 'snowiness', -- 'snowless', -- 'snowman', -- 'snowplow', -- 'snowshoe', -- 'snowstorm', -- 'snowsuit', -- 'snowy', -- 'snub', -- 'snuff', -- 'snuggle', -- 'snugly', -- 'snugness', -- 'speak', -- 'spearfish', -- 'spearhead', -- 'spearman', -- 'spearmint', -- 'species', -- 'specimen', -- 'specked', -- 'speckled', -- 'specks', -- 'spectacle', -- 'spectator', -- 'spectrum', -- 'speculate', -- 'speech', -- 'speed', -- 'spellbind', -- 'speller', -- 'spelling', -- 'spendable', -- 'spender', -- 'spending', -- 'spent', -- 'spew', -- 'sphere', -- 'spherical', -- 'sphinx', -- 'spider', -- 'spied', -- 'spiffy', -- 'spill', -- 'spilt', -- 'spinach', -- 'spinal', -- 'spindle', -- 'spinner', -- 'spinning', -- 'spinout', -- 'spinster', -- 'spiny', -- 'spiral', -- 'spirited', -- 'spiritism', -- 'spirits', -- 'spiritual', -- 'splashed', -- 'splashing', -- 'splashy', -- 'splatter', -- 'spleen', -- 'splendid', -- 'splendor', -- 'splice', -- 'splicing', -- 'splinter', -- 'splotchy', -- 'splurge', -- 'spoilage', -- 'spoiled', -- 'spoiler', -- 'spoiling', -- 'spoils', -- 'spoken', -- 'spokesman', -- 'sponge', -- 'spongy', -- 'sponsor', -- 'spoof', -- 'spookily', -- 'spooky', -- 'spool', -- 'spoon', -- 'spore', -- 'sporting', -- 'sports', -- 'sporty', -- 'spotless', -- 'spotlight', -- 'spotted', -- 'spotter', -- 'spotting', -- 'spotty', -- 'spousal', -- 'spouse', -- 'spout', -- 'sprain', -- 'sprang', -- 'sprawl', -- 'spray', -- 'spree', -- 'sprig', -- 'spring', -- 'sprinkled', -- 'sprinkler', -- 'sprint', -- 'sprite', -- 'sprout', -- 'spruce', -- 'sprung', -- 'spry', -- 'spud', -- 'spur', -- 'sputter', -- 'spyglass', -- 'squabble', -- 'squad', -- 'squall', -- 'squander', -- 'squash', -- 'squatted', -- 'squatter', -- 'squatting', -- 'squeak', -- 'squealer', -- 'squealing', -- 'squeamish', -- 'squeegee', -- 'squeeze', -- 'squeezing', -- 'squid', -- 'squiggle', -- 'squiggly', -- 'squint', -- 'squire', -- 'squirt', -- 'squishier', -- 'squishy', -- 'stability', -- 'stabilize', -- 'stable', -- 'stack', -- 'stadium', -- 'staff', -- 'stage', -- 'staging', -- 'stagnant', -- 'stagnate', -- 'stainable', -- 'stained', -- 'staining', -- 'stainless', -- 'stalemate', -- 'staleness', -- 'stalling', -- 'stallion', -- 'stamina', -- 'stammer', -- 'stamp', -- 'stand', -- 'stank', -- 'staple', -- 'stapling', -- 'starboard', -- 'starch', -- 'stardom', -- 'stardust', -- 'starfish', -- 'stargazer', -- 'staring', -- 'stark', -- 'starless', -- 'starlet', -- 'starlight', -- 'starlit', -- 'starring', -- 'starry', -- 'starship', -- 'starter', -- 'starting', -- 'startle', -- 'startling', -- 'startup', -- 'starved', -- 'starving', -- 'stash', -- 'state', -- 'static', -- 'statistic', -- 'statue', -- 'stature', -- 'status', -- 'statute', -- 'statutory', -- 'staunch', -- 'stays', -- 'steadfast', -- 'steadier', -- 'steadily', -- 'steadying', -- 'steam', -- 'steed', -- 'steep', -- 'steerable', -- 'steering', -- 'steersman', -- 'stegosaur', -- 'stellar', -- 'stem', -- 'stench', -- 'stencil', -- 'step', -- 'stereo', -- 'sterile', -- 'sterility', -- 'sterilize', -- 'sterling', -- 'sternness', -- 'sternum', -- 'stew', -- 'stick', -- 'stiffen', -- 'stiffly', -- 'stiffness', -- 'stifle', -- 'stifling', -- 'stillness', -- 'stilt', -- 'stimulant', -- 'stimulate', -- 'stimuli', -- 'stimulus', -- 'stinger', -- 'stingily', -- 'stinging', -- 'stingray', -- 'stingy', -- 'stinking', -- 'stinky', -- 'stipend', -- 'stipulate', -- 'stir', -- 'stitch', -- 'stock', -- 'stoic', -- 'stoke', -- 'stole', -- 'stomp', -- 'stonewall', -- 'stoneware', -- 'stonework', -- 'stoning', -- 'stony', -- 'stood', -- 'stooge', -- 'stool', -- 'stoop', -- 'stoplight', -- 'stoppable', -- 'stoppage', -- 'stopped', -- 'stopper', -- 'stopping', -- 'stopwatch', -- 'storable', -- 'storage', -- 'storeroom', -- 'storewide', -- 'storm', -- 'stout', -- 'stove', -- 'stowaway', -- 'stowing', -- 'straddle', -- 'straggler', -- 'strained', -- 'strainer', -- 'straining', -- 'strangely', -- 'stranger', -- 'strangle', -- 'strategic', -- 'strategy', -- 'stratus', -- 'straw', -- 'stray', -- 'streak', -- 'stream', -- 'street', -- 'strength', -- 'strenuous', -- 'strep', -- 'stress', -- 'stretch', -- 'strewn', -- 'stricken', -- 'strict', -- 'stride', -- 'strife', -- 'strike', -- 'striking', -- 'strive', -- 'striving', -- 'strobe', -- 'strode', -- 'stroller', -- 'strongbox', -- 'strongly', -- 'strongman', -- 'struck', -- 'structure', -- 'strudel', -- 'struggle', -- 'strum', -- 'strung', -- 'strut', -- 'stubbed', -- 'stubble', -- 'stubbly', -- 'stubborn', -- 'stucco', -- 'stuck', -- 'student', -- 'studied', -- 'studio', -- 'study', -- 'stuffed', -- 'stuffing', -- 'stuffy', -- 'stumble', -- 'stumbling', -- 'stump', -- 'stung', -- 'stunned', -- 'stunner', -- 'stunning', -- 'stunt', -- 'stupor', -- 'sturdily', -- 'sturdy', -- 'styling', -- 'stylishly', -- 'stylist', -- 'stylized', -- 'stylus', -- 'suave', -- 'subarctic', -- 'subatomic', -- 'subdivide', -- 'subdued', -- 'subduing', -- 'subfloor', -- 'subgroup', -- 'subheader', -- 'subject', -- 'sublease', -- 'sublet', -- 'sublevel', -- 'sublime', -- 'submarine', -- 'submerge', -- 'submersed', -- 'submitter', -- 'subpanel', -- 'subpar', -- 'subplot', -- 'subprime', -- 'subscribe', -- 'subscript', -- 'subsector', -- 'subside', -- 'subsiding', -- 'subsidize', -- 'subsidy', -- 'subsoil', -- 'subsonic', -- 'substance', -- 'subsystem', -- 'subtext', -- 'subtitle', -- 'subtly', -- 'subtotal', -- 'subtract', -- 'subtype', -- 'suburb', -- 'subway', -- 'subwoofer', -- 'subzero', -- 'succulent', -- 'such', -- 'suction', -- 'sudden', -- 'sudoku', -- 'suds', -- 'sufferer', -- 'suffering', -- 'suffice', -- 'suffix', -- 'suffocate', -- 'suffrage', -- 'sugar', -- 'suggest', -- 'suing', -- 'suitable', -- 'suitably', -- 'suitcase', -- 'suitor', -- 'sulfate', -- 'sulfide', -- 'sulfite', -- 'sulfur', -- 'sulk', -- 'sullen', -- 'sulphate', -- 'sulphuric', -- 'sultry', -- 'superbowl', -- 'superglue', -- 'superhero', -- 'superior', -- 'superjet', -- 'superman', -- 'supermom', -- 'supernova', -- 'supervise', -- 'supper', -- 'supplier', -- 'supply', -- 'support', -- 'supremacy', -- 'supreme', -- 'surcharge', -- 'surely', -- 'sureness', -- 'surface', -- 'surfacing', -- 'surfboard', -- 'surfer', -- 'surgery', -- 'surgical', -- 'surging', -- 'surname', -- 'surpass', -- 'surplus', -- 'surprise', -- 'surreal', -- 'surrender', -- 'surrogate', -- 'surround', -- 'survey', -- 'survival', -- 'survive', -- 'surviving', -- 'survivor', -- 'sushi', -- 'suspect', -- 'suspend', -- 'suspense', -- 'sustained', -- 'sustainer', -- 'swab', -- 'swaddling', -- 'swagger', -- 'swampland', -- 'swan', -- 'swapping', -- 'swarm', -- 'sway', -- 'swear', -- 'sweat', -- 'sweep', -- 'swell', -- 'swept', -- 'swerve', -- 'swifter', -- 'swiftly', -- 'swiftness', -- 'swimmable', -- 'swimmer', -- 'swimming', -- 'swimsuit', -- 'swimwear', -- 'swinger', -- 'swinging', -- 'swipe', -- 'swirl', -- 'switch', -- 'swivel', -- 'swizzle', -- 'swooned', -- 'swoop', -- 'swoosh', -- 'swore', -- 'sworn', -- 'swung', -- 'sycamore', -- 'sympathy', -- 'symphonic', -- 'symphony', -- 'symptom', -- 'synapse', -- 'syndrome', -- 'synergy', -- 'synopses', -- 'synopsis', -- 'synthesis', -- 'synthetic', -- 'syrup', -- 'system', -- 't-shirt', -- 'tabasco', -- 'tabby', -- 'tableful', -- 'tables', -- 'tablet', -- 'tableware', -- 'tabloid', -- 'tackiness', -- 'tacking', -- 'tackle', -- 'tackling', -- 'tacky', -- 'taco', -- 'tactful', -- 'tactical', -- 'tactics', -- 'tactile', -- 'tactless', -- 'tadpole', -- 'taekwondo', -- 'tag', -- 'tainted', -- 'take', -- 'taking', -- 'talcum', -- 'talisman', -- 'tall', -- 'talon', -- 'tamale', -- 'tameness', -- 'tamer', -- 'tamper', -- 'tank', -- 'tanned', -- 'tannery', -- 'tanning', -- 'tantrum', -- 'tapeless', -- 'tapered', -- 'tapering', -- 'tapestry', -- 'tapioca', -- 'tapping', -- 'taps', -- 'tarantula', -- 'target', -- 'tarmac', -- 'tarnish', -- 'tarot', -- 'tartar', -- 'tartly', -- 'tartness', -- 'task', -- 'tassel', -- 'taste', -- 'tastiness', -- 'tasting', -- 'tasty', -- 'tattered', -- 'tattle', -- 'tattling', -- 'tattoo', -- 'taunt', -- 'tavern', -- 'thank', -- 'that', -- 'thaw', -- 'theater', -- 'theatrics', -- 'thee', -- 'theft', -- 'theme', -- 'theology', -- 'theorize', -- 'thermal', -- 'thermos', -- 'thesaurus', -- 'these', -- 'thesis', -- 'thespian', -- 'thicken', -- 'thicket', -- 'thickness', -- 'thieving', -- 'thievish', -- 'thigh', -- 'thimble', -- 'thing', -- 'think', -- 'thinly', -- 'thinner', -- 'thinness', -- 'thinning', -- 'thirstily', -- 'thirsting', -- 'thirsty', -- 'thirteen', -- 'thirty', -- 'thong', -- 'thorn', -- 'those', -- 'thousand', -- 'thrash', -- 'thread', -- 'threaten', -- 'threefold', -- 'thrift', -- 'thrill', -- 'thrive', -- 'thriving', -- 'throat', -- 'throbbing', -- 'throng', -- 'throttle', -- 'throwaway', -- 'throwback', -- 'thrower', -- 'throwing', -- 'thud', -- 'thumb', -- 'thumping', -- 'thursday', -- 'thus', -- 'thwarting', -- 'thyself', -- 'tiara', -- 'tibia', -- 'tidal', -- 'tidbit', -- 'tidiness', -- 'tidings', -- 'tidy', -- 'tiger', -- 'tighten', -- 'tightly', -- 'tightness', -- 'tightrope', -- 'tightwad', -- 'tigress', -- 'tile', -- 'tiling', -- 'till', -- 'tilt', -- 'timid', -- 'timing', -- 'timothy', -- 'tinderbox', -- 'tinfoil', -- 'tingle', -- 'tingling', -- 'tingly', -- 'tinker', -- 'tinkling', -- 'tinsel', -- 'tinsmith', -- 'tint', -- 'tinwork', -- 'tiny', -- 'tipoff', -- 'tipped', -- 'tipper', -- 'tipping', -- 'tiptoeing', -- 'tiptop', -- 'tiring', -- 'tissue', -- 'trace', -- 'tracing', -- 'track', -- 'traction', -- 'tractor', -- 'trade', -- 'trading', -- 'tradition', -- 'traffic', -- 'tragedy', -- 'trailing', -- 'trailside', -- 'train', -- 'traitor', -- 'trance', -- 'tranquil', -- 'transfer', -- 'transform', -- 'translate', -- 'transpire', -- 'transport', -- 'transpose', -- 'trapdoor', -- 'trapeze', -- 'trapezoid', -- 'trapped', -- 'trapper', -- 'trapping', -- 'traps', -- 'trash', -- 'travel', -- 'traverse', -- 'travesty', -- 'tray', -- 'treachery', -- 'treading', -- 'treadmill', -- 'treason', -- 'treat', -- 'treble', -- 'tree', -- 'trekker', -- 'tremble', -- 'trembling', -- 'tremor', -- 'trench', -- 'trend', -- 'trespass', -- 'triage', -- 'trial', -- 'triangle', -- 'tribesman', -- 'tribunal', -- 'tribune', -- 'tributary', -- 'tribute', -- 'triceps', -- 'trickery', -- 'trickily', -- 'tricking', -- 'trickle', -- 'trickster', -- 'tricky', -- 'tricolor', -- 'tricycle', -- 'trident', -- 'tried', -- 'trifle', -- 'trifocals', -- 'trillion', -- 'trilogy', -- 'trimester', -- 'trimmer', -- 'trimming', -- 'trimness', -- 'trinity', -- 'trio', -- 'tripod', -- 'tripping', -- 'triumph', -- 'trivial', -- 'trodden', -- 'trolling', -- 'trombone', -- 'trophy', -- 'tropical', -- 'tropics', -- 'trouble', -- 'troubling', -- 'trough', -- 'trousers', -- 'trout', -- 'trowel', -- 'truce', -- 'truck', -- 'truffle', -- 'trump', -- 'trunks', -- 'trustable', -- 'trustee', -- 'trustful', -- 'trusting', -- 'trustless', -- 'truth', -- 'try', -- 'tubby', -- 'tubeless', -- 'tubular', -- 'tucking', -- 'tuesday', -- 'tug', -- 'tuition', -- 'tulip', -- 'tumble', -- 'tumbling', -- 'tummy', -- 'turban', -- 'turbine', -- 'turbofan', -- 'turbojet', -- 'turbulent', -- 'turf', -- 'turkey', -- 'turmoil', -- 'turret', -- 'turtle', -- 'tusk', -- 'tutor', -- 'tutu', -- 'tux', -- 'tweak', -- 'tweed', -- 'tweet', -- 'tweezers', -- 'twelve', -- 'twentieth', -- 'twenty', -- 'twerp', -- 'twice', -- 'twiddle', -- 'twiddling', -- 'twig', -- 'twilight', -- 'twine', -- 'twins', -- 'twirl', -- 'twistable', -- 'twisted', -- 'twister', -- 'twisting', -- 'twisty', -- 'twitch', -- 'twitter', -- 'tycoon', -- 'tying', -- 'tyke', -- 'udder', -- 'ultimate', -- 'ultimatum', -- 'ultra', -- 'umbilical', -- 'umbrella', -- 'umpire', -- 'unabashed', -- 'unable', -- 'unadorned', -- 'unadvised', -- 'unafraid', -- 'unaired', -- 'unaligned', -- 'unaltered', -- 'unarmored', -- 'unashamed', -- 'unaudited', -- 'unawake', -- 'unaware', -- 'unbaked', -- 'unbalance', -- 'unbeaten', -- 'unbend', -- 'unbent', -- 'unbiased', -- 'unbitten', -- 'unblended', -- 'unblessed', -- 'unblock', -- 'unbolted', -- 'unbounded', -- 'unboxed', -- 'unbraided', -- 'unbridle', -- 'unbroken', -- 'unbuckled', -- 'unbundle', -- 'unburned', -- 'unbutton', -- 'uncanny', -- 'uncapped', -- 'uncaring', -- 'uncertain', -- 'unchain', -- 'unchanged', -- 'uncharted', -- 'uncheck', -- 'uncivil', -- 'unclad', -- 'unclaimed', -- 'unclamped', -- 'unclasp', -- 'uncle', -- 'unclip', -- 'uncloak', -- 'unclog', -- 'unclothed', -- 'uncoated', -- 'uncoiled', -- 'uncolored', -- 'uncombed', -- 'uncommon', -- 'uncooked', -- 'uncork', -- 'uncorrupt', -- 'uncounted', -- 'uncouple', -- 'uncouth', -- 'uncover', -- 'uncross', -- 'uncrown', -- 'uncrushed', -- 'uncured', -- 'uncurious', -- 'uncurled', -- 'uncut', -- 'undamaged', -- 'undated', -- 'undaunted', -- 'undead', -- 'undecided', -- 'undefined', -- 'underage', -- 'underarm', -- 'undercoat', -- 'undercook', -- 'undercut', -- 'underdog', -- 'underdone', -- 'underfed', -- 'underfeed', -- 'underfoot', -- 'undergo', -- 'undergrad', -- 'underhand', -- 'underline', -- 'underling', -- 'undermine', -- 'undermost', -- 'underpaid', -- 'underpass', -- 'underpay', -- 'underrate', -- 'undertake', -- 'undertone', -- 'undertook', -- 'undertow', -- 'underuse', -- 'underwear', -- 'underwent', -- 'underwire', -- 'undesired', -- 'undiluted', -- 'undivided', -- 'undocked', -- 'undoing', -- 'undone', -- 'undrafted', -- 'undress', -- 'undrilled', -- 'undusted', -- 'undying', -- 'unearned', -- 'unearth', -- 'unease', -- 'uneasily', -- 'uneasy', -- 'uneatable', -- 'uneaten', -- 'unedited', -- 'unelected', -- 'unending', -- 'unengaged', -- 'unenvied', -- 'unequal', -- 'unethical', -- 'uneven', -- 'unexpired', -- 'unexposed', -- 'unfailing', -- 'unfair', -- 'unfasten', -- 'unfazed', -- 'unfeeling', -- 'unfiled', -- 'unfilled', -- 'unfitted', -- 'unfitting', -- 'unfixable', -- 'unfixed', -- 'unflawed', -- 'unfocused', -- 'unfold', -- 'unfounded', -- 'unframed', -- 'unfreeze', -- 'unfrosted', -- 'unfrozen', -- 'unfunded', -- 'unglazed', -- 'ungloved', -- 'unglue', -- 'ungodly', -- 'ungraded', -- 'ungreased', -- 'unguarded', -- 'unguided', -- 'unhappily', -- 'unhappy', -- 'unharmed', -- 'unhealthy', -- 'unheard', -- 'unhearing', -- 'unheated', -- 'unhelpful', -- 'unhidden', -- 'unhinge', -- 'unhitched', -- 'unholy', -- 'unhook', -- 'unicorn', -- 'unicycle', -- 'unified', -- 'unifier', -- 'uniformed', -- 'uniformly', -- 'unify', -- 'unimpeded', -- 'uninjured', -- 'uninstall', -- 'uninsured', -- 'uninvited', -- 'union', -- 'uniquely', -- 'unisexual', -- 'unison', -- 'unissued', -- 'unit', -- 'universal', -- 'universe', -- 'unjustly', -- 'unkempt', -- 'unkind', -- 'unknotted', -- 'unknowing', -- 'unknown', -- 'unlaced', -- 'unlatch', -- 'unlawful', -- 'unleaded', -- 'unlearned', -- 'unleash', -- 'unless', -- 'unleveled', -- 'unlighted', -- 'unlikable', -- 'unlimited', -- 'unlined', -- 'unlinked', -- 'unlisted', -- 'unlit', -- 'unlivable', -- 'unloaded', -- 'unloader', -- 'unlocked', -- 'unlocking', -- 'unlovable', -- 'unloved', -- 'unlovely', -- 'unloving', -- 'unluckily', -- 'unlucky', -- 'unmade', -- 'unmanaged', -- 'unmanned', -- 'unmapped', -- 'unmarked', -- 'unmasked', -- 'unmasking', -- 'unmatched', -- 'unmindful', -- 'unmixable', -- 'unmixed', -- 'unmolded', -- 'unmoral', -- 'unmovable', -- 'unmoved', -- 'unmoving', -- 'unnamable', -- 'unnamed', -- 'unnatural', -- 'unneeded', -- 'unnerve', -- 'unnerving', -- 'unnoticed', -- 'unopened', -- 'unopposed', -- 'unpack', -- 'unpadded', -- 'unpaid', -- 'unpainted', -- 'unpaired', -- 'unpaved', -- 'unpeeled', -- 'unpicked', -- 'unpiloted', -- 'unpinned', -- 'unplanned', -- 'unplanted', -- 'unpleased', -- 'unpledged', -- 'unplowed', -- 'unplug', -- 'unpopular', -- 'unproven', -- 'unquote', -- 'unranked', -- 'unrated', -- 'unraveled', -- 'unreached', -- 'unread', -- 'unreal', -- 'unreeling', -- 'unrefined', -- 'unrelated', -- 'unrented', -- 'unrest', -- 'unretired', -- 'unrevised', -- 'unrigged', -- 'unripe', -- 'unrivaled', -- 'unroasted', -- 'unrobed', -- 'unroll', -- 'unruffled', -- 'unruly', -- 'unrushed', -- 'unsaddle', -- 'unsafe', -- 'unsaid', -- 'unsalted', -- 'unsaved', -- 'unsavory', -- 'unscathed', -- 'unscented', -- 'unscrew', -- 'unsealed', -- 'unseated', -- 'unsecured', -- 'unseeing', -- 'unseemly', -- 'unseen', -- 'unselect', -- 'unselfish', -- 'unsent', -- 'unsettled', -- 'unshackle', -- 'unshaken', -- 'unshaved', -- 'unshaven', -- 'unsheathe', -- 'unshipped', -- 'unsightly', -- 'unsigned', -- 'unskilled', -- 'unsliced', -- 'unsmooth', -- 'unsnap', -- 'unsocial', -- 'unsoiled', -- 'unsold', -- 'unsolved', -- 'unsorted', -- 'unspoiled', -- 'unspoken', -- 'unstable', -- 'unstaffed', -- 'unstamped', -- 'unsteady', -- 'unsterile', -- 'unstirred', -- 'unstitch', -- 'unstopped', -- 'unstuck', -- 'unstuffed', -- 'unstylish', -- 'unsubtle', -- 'unsubtly', -- 'unsuited', -- 'unsure', -- 'unsworn', -- 'untagged', -- 'untainted', -- 'untaken', -- 'untamed', -- 'untangled', -- 'untapped', -- 'untaxed', -- 'unthawed', -- 'unthread', -- 'untidy', -- 'untie', -- 'until', -- 'untimed', -- 'untimely', -- 'untitled', -- 'untoasted', -- 'untold', -- 'untouched', -- 'untracked', -- 'untrained', -- 'untreated', -- 'untried', -- 'untrimmed', -- 'untrue', -- 'untruth', -- 'unturned', -- 'untwist', -- 'untying', -- 'unusable', -- 'unused', -- 'unusual', -- 'unvalued', -- 'unvaried', -- 'unvarying', -- 'unveiled', -- 'unveiling', -- 'unvented', -- 'unviable', -- 'unvisited', -- 'unvocal', -- 'unwanted', -- 'unwarlike', -- 'unwary', -- 'unwashed', -- 'unwatched', -- 'unweave', -- 'unwed', -- 'unwelcome', -- 'unwell', -- 'unwieldy', -- 'unwilling', -- 'unwind', -- 'unwired', -- 'unwitting', -- 'unwomanly', -- 'unworldly', -- 'unworn', -- 'unworried', -- 'unworthy', -- 'unwound', -- 'unwoven', -- 'unwrapped', -- 'unwritten', -- 'unzip', -- 'upbeat', -- 'upchuck', -- 'upcoming', -- 'upcountry', -- 'update', -- 'upfront', -- 'upgrade', -- 'upheaval', -- 'upheld', -- 'uphill', -- 'uphold', -- 'uplifted', -- 'uplifting', -- 'upload', -- 'upon', -- 'upper', -- 'upright', -- 'uprising', -- 'upriver', -- 'uproar', -- 'uproot', -- 'upscale', -- 'upside', -- 'upstage', -- 'upstairs', -- 'upstart', -- 'upstate', -- 'upstream', -- 'upstroke', -- 'upswing', -- 'uptake', -- 'uptight', -- 'uptown', -- 'upturned', -- 'upward', -- 'upwind', -- 'uranium', -- 'urban', -- 'urchin', -- 'urethane', -- 'urgency', -- 'urgent', -- 'urging', -- 'urologist', -- 'urology', -- 'usable', -- 'usage', -- 'useable', -- 'used', -- 'uselessly', -- 'user', -- 'usher', -- 'usual', -- 'utensil', -- 'utility', -- 'utilize', -- 'utmost', -- 'utopia', -- 'utter', -- 'vacancy', -- 'vacant', -- 'vacate', -- 'vacation', -- 'vagabond', -- 'vagrancy', -- 'vagrantly', -- 'vaguely', -- 'vagueness', -- 'valiant', -- 'valid', -- 'valium', -- 'valley', -- 'valuables', -- 'value', -- 'vanilla', -- 'vanish', -- 'vanity', -- 'vanquish', -- 'vantage', -- 'vaporizer', -- 'variable', -- 'variably', -- 'varied', -- 'variety', -- 'various', -- 'varmint', -- 'varnish', -- 'varsity', -- 'varying', -- 'vascular', -- 'vaseline', -- 'vastly', -- 'vastness', -- 'veal', -- 'vegan', -- 'veggie', -- 'vehicular', -- 'velcro', -- 'velocity', -- 'velvet', -- 'vendetta', -- 'vending', -- 'vendor', -- 'veneering', -- 'vengeful', -- 'venomous', -- 'ventricle', -- 'venture', -- 'venue', -- 'venus', -- 'verbalize', -- 'verbally', -- 'verbose', -- 'verdict', -- 'verify', -- 'verse', -- 'version', -- 'versus', -- 'vertebrae', -- 'vertical', -- 'vertigo', -- 'very', -- 'vessel', -- 'vest', -- 'veteran', -- 'veto', -- 'vexingly', -- 'viability', -- 'viable', -- 'vibes', -- 'vice', -- 'vicinity', -- 'victory', -- 'video', -- 'viewable', -- 'viewer', -- 'viewing', -- 'viewless', -- 'viewpoint', -- 'vigorous', -- 'village', -- 'villain', -- 'vindicate', -- 'vineyard', -- 'vintage', -- 'violate', -- 'violation', -- 'violator', -- 'violet', -- 'violin', -- 'viper', -- 'viral', -- 'virtual', -- 'virtuous', -- 'virus', -- 'visa', -- 'viscosity', -- 'viscous', -- 'viselike', -- 'visible', -- 'visibly', -- 'vision', -- 'visiting', -- 'visitor', -- 'visor', -- 'vista', -- 'vitality', -- 'vitalize', -- 'vitally', -- 'vitamins', -- 'vivacious', -- 'vividly', -- 'vividness', -- 'vixen', -- 'vocalist', -- 'vocalize', -- 'vocally', -- 'vocation', -- 'voice', -- 'voicing', -- 'void', -- 'volatile', -- 'volley', -- 'voltage', -- 'volumes', -- 'voter', -- 'voting', -- 'voucher', -- 'vowed', -- 'vowel', -- 'voyage', -- 'wackiness', -- 'wad', -- 'wafer', -- 'waffle', -- 'waged', -- 'wager', -- 'wages', -- 'waggle', -- 'wagon', -- 'wake', -- 'waking', -- 'walk', -- 'walmart', -- 'walnut', -- 'walrus', -- 'waltz', -- 'wand', -- 'wannabe', -- 'wanted', -- 'wanting', -- 'wasabi', -- 'washable', -- 'washbasin', -- 'washboard', -- 'washbowl', -- 'washcloth', -- 'washday', -- 'washed', -- 'washer', -- 'washhouse', -- 'washing', -- 'washout', -- 'washroom', -- 'washstand', -- 'washtub', -- 'wasp', -- 'wasting', -- 'watch', -- 'water', -- 'waviness', -- 'waving', -- 'wavy', -- 'whacking', -- 'whacky', -- 'wham', -- 'wharf', -- 'wheat', -- 'whenever', -- 'whiff', -- 'whimsical', -- 'whinny', -- 'whiny', -- 'whisking', -- 'whoever', -- 'whole', -- 'whomever', -- 'whoopee', -- 'whooping', -- 'whoops', -- 'why', -- 'wick', -- 'widely', -- 'widen', -- 'widget', -- 'widow', -- 'width', -- 'wieldable', -- 'wielder', -- 'wife', -- 'wifi', -- 'wikipedia', -- 'wildcard', -- 'wildcat', -- 'wilder', -- 'wildfire', -- 'wildfowl', -- 'wildland', -- 'wildlife', -- 'wildly', -- 'wildness', -- 'willed', -- 'willfully', -- 'willing', -- 'willow', -- 'willpower', -- 'wilt', -- 'wimp', -- 'wince', -- 'wincing', -- 'wind', -- 'wing', -- 'winking', -- 'winner', -- 'winnings', -- 'winter', -- 'wipe', -- 'wired', -- 'wireless', -- 'wiring', -- 'wiry', -- 'wisdom', -- 'wise', -- 'wish', -- 'wisplike', -- 'wispy', -- 'wistful', -- 'wizard', -- 'wobble', -- 'wobbling', -- 'wobbly', -- 'wok', -- 'wolf', -- 'wolverine', -- 'womanhood', -- 'womankind', -- 'womanless', -- 'womanlike', -- 'womanly', -- 'womb', -- 'woof', -- 'wooing', -- 'wool', -- 'woozy', -- 'word', -- 'work', -- 'worried', -- 'worrier', -- 'worrisome', -- 'worry', -- 'worsening', -- 'worshiper', -- 'worst', -- 'wound', -- 'woven', -- 'wow', -- 'wrangle', -- 'wrath', -- 'wreath', -- 'wreckage', -- 'wrecker', -- 'wrecking', -- 'wrench', -- 'wriggle', -- 'wriggly', -- 'wrinkle', -- 'wrinkly', -- 'wrist', -- 'writing', -- 'written', -- 'wrongdoer', -- 'wronged', -- 'wrongful', -- 'wrongly', -- 'wrongness', -- 'wrought', -- 'xbox', -- 'xerox', -- 'yahoo', -- 'yam', -- 'yanking', -- 'yapping', -- 'yard', -- 'yarn', -- 'yeah', -- 'yearbook', -- 'yearling', -- 'yearly', -- 'yearning', -- 'yeast', -- 'yelling', -- 'yelp', -- 'yen', -- 'yesterday', -- 'yiddish', -- 'yield', -- 'yin', -- 'yippee', -- 'yo-yo', -- 'yodel', -- 'yoga', -- 'yogurt', -- 'yonder', -- 'yoyo', -- 'yummy', -- 'zap', -- 'zealous', -- 'zebra', -- 'zen', -- 'zeppelin', -- 'zero', -- 'zestfully', -- 'zesty', -- 'zigzagged', -- 'zipfile', -- 'zipping', -- 'zippy', -- 'zips', -- 'zit', -- 'zodiac', -- 'zombie', -- 'zone', -- 'zoning', -- 'zookeeper', -- 'zoologist', -- 'zoology', -- 'zoom', -+ "abacus", -+ "abdomen", -+ "abdominal", -+ "abide", -+ "abiding", -+ "ability", -+ "ablaze", -+ "able", -+ "abnormal", -+ "abrasion", -+ "abrasive", -+ "abreast", -+ "abridge", -+ "abroad", -+ "abruptly", -+ "absence", -+ "absentee", -+ "absently", -+ "absinthe", -+ "absolute", -+ "absolve", -+ "abstain", -+ "abstract", -+ "absurd", -+ "accent", -+ "acclaim", -+ "acclimate", -+ "accompany", -+ "account", -+ "accuracy", -+ "accurate", -+ "accustom", -+ "acetone", -+ "achiness", -+ "aching", -+ "acid", -+ "acorn", -+ "acquaint", -+ "acquire", -+ "acre", -+ "acrobat", -+ "acronym", -+ "acting", -+ "action", -+ "activate", -+ "activator", -+ "active", -+ "activism", -+ "activist", -+ "activity", -+ "actress", -+ "acts", -+ "acutely", -+ "acuteness", -+ "aeration", -+ "aerobics", -+ "aerosol", -+ "aerospace", -+ "afar", -+ "affair", -+ "affected", -+ "affecting", -+ "affection", -+ "affidavit", -+ "affiliate", -+ "affirm", -+ "affix", -+ "afflicted", -+ "affluent", -+ "afford", -+ "affront", -+ "aflame", -+ "afloat", -+ "aflutter", -+ "afoot", -+ "afraid", -+ "afterglow", -+ "afterlife", -+ "aftermath", -+ "aftermost", -+ "afternoon", -+ "aged", -+ "ageless", -+ "agency", -+ "agenda", -+ "agent", -+ "aggregate", -+ "aghast", -+ "agile", -+ "agility", -+ "aging", -+ "agnostic", -+ "agonize", -+ "agonizing", -+ "agony", -+ "agreeable", -+ "agreeably", -+ "agreed", -+ "agreeing", -+ "agreement", -+ "aground", -+ "ahead", -+ "ahoy", -+ "aide", -+ "aids", -+ "aim", -+ "ajar", -+ "alabaster", -+ "alarm", -+ "albatross", -+ "album", -+ "alfalfa", -+ "algebra", -+ "algorithm", -+ "alias", -+ "alibi", -+ "alienable", -+ "alienate", -+ "aliens", -+ "alike", -+ "alive", -+ "alkaline", -+ "alkalize", -+ "almanac", -+ "almighty", -+ "almost", -+ "aloe", -+ "aloft", -+ "aloha", -+ "alone", -+ "alongside", -+ "aloof", -+ "alphabet", -+ "alright", -+ "although", -+ "altitude", -+ "alto", -+ "aluminum", -+ "alumni", -+ "always", -+ "amaretto", -+ "amaze", -+ "amazingly", -+ "amber", -+ "ambiance", -+ "ambiguity", -+ "ambiguous", -+ "ambition", -+ "ambitious", -+ "ambulance", -+ "ambush", -+ "amendable", -+ "amendment", -+ "amends", -+ "amenity", -+ "amiable", -+ "amicably", -+ "amid", -+ "amigo", -+ "amino", -+ "amiss", -+ "ammonia", -+ "ammonium", -+ "amnesty", -+ "amniotic", -+ "among", -+ "amount", -+ "amperage", -+ "ample", -+ "amplifier", -+ "amplify", -+ "amply", -+ "amuck", -+ "amulet", -+ "amusable", -+ "amused", -+ "amusement", -+ "amuser", -+ "amusing", -+ "anaconda", -+ "anaerobic", -+ "anagram", -+ "anatomist", -+ "anatomy", -+ "anchor", -+ "anchovy", -+ "ancient", -+ "android", -+ "anemia", -+ "anemic", -+ "aneurism", -+ "anew", -+ "angelfish", -+ "angelic", -+ "anger", -+ "angled", -+ "angler", -+ "angles", -+ "angling", -+ "angrily", -+ "angriness", -+ "anguished", -+ "angular", -+ "animal", -+ "animate", -+ "animating", -+ "animation", -+ "animator", -+ "anime", -+ "animosity", -+ "ankle", -+ "annex", -+ "annotate", -+ "announcer", -+ "annoying", -+ "annually", -+ "annuity", -+ "anointer", -+ "another", -+ "answering", -+ "antacid", -+ "antarctic", -+ "anteater", -+ "antelope", -+ "antennae", -+ "anthem", -+ "anthill", -+ "anthology", -+ "antibody", -+ "antics", -+ "antidote", -+ "antihero", -+ "antiquely", -+ "antiques", -+ "antiquity", -+ "antirust", -+ "antitoxic", -+ "antitrust", -+ "antiviral", -+ "antivirus", -+ "antler", -+ "antonym", -+ "antsy", -+ "anvil", -+ "anybody", -+ "anyhow", -+ "anymore", -+ "anyone", -+ "anyplace", -+ "anything", -+ "anytime", -+ "anyway", -+ "anywhere", -+ "aorta", -+ "apache", -+ "apostle", -+ "appealing", -+ "appear", -+ "appease", -+ "appeasing", -+ "appendage", -+ "appendix", -+ "appetite", -+ "appetizer", -+ "applaud", -+ "applause", -+ "apple", -+ "appliance", -+ "applicant", -+ "applied", -+ "apply", -+ "appointee", -+ "appraisal", -+ "appraiser", -+ "apprehend", -+ "approach", -+ "approval", -+ "approve", -+ "apricot", -+ "april", -+ "apron", -+ "aptitude", -+ "aptly", -+ "aqua", -+ "aqueduct", -+ "arbitrary", -+ "arbitrate", -+ "ardently", -+ "area", -+ "arena", -+ "arguable", -+ "arguably", -+ "argue", -+ "arise", -+ "armadillo", -+ "armband", -+ "armchair", -+ "armed", -+ "armful", -+ "armhole", -+ "arming", -+ "armless", -+ "armoire", -+ "armored", -+ "armory", -+ "armrest", -+ "army", -+ "aroma", -+ "arose", -+ "around", -+ "arousal", -+ "arrange", -+ "array", -+ "arrest", -+ "arrival", -+ "arrive", -+ "arrogance", -+ "arrogant", -+ "arson", -+ "art", -+ "ascend", -+ "ascension", -+ "ascent", -+ "ascertain", -+ "ashamed", -+ "ashen", -+ "ashes", -+ "ashy", -+ "aside", -+ "askew", -+ "asleep", -+ "asparagus", -+ "aspect", -+ "aspirate", -+ "aspire", -+ "aspirin", -+ "astonish", -+ "astound", -+ "astride", -+ "astrology", -+ "astronaut", -+ "astronomy", -+ "astute", -+ "atlantic", -+ "atlas", -+ "atom", -+ "atonable", -+ "atop", -+ "atrium", -+ "atrocious", -+ "atrophy", -+ "attach", -+ "attain", -+ "attempt", -+ "attendant", -+ "attendee", -+ "attention", -+ "attentive", -+ "attest", -+ "attic", -+ "attire", -+ "attitude", -+ "attractor", -+ "attribute", -+ "atypical", -+ "auction", -+ "audacious", -+ "audacity", -+ "audible", -+ "audibly", -+ "audience", -+ "audio", -+ "audition", -+ "augmented", -+ "august", -+ "authentic", -+ "author", -+ "autism", -+ "autistic", -+ "autograph", -+ "automaker", -+ "automated", -+ "automatic", -+ "autopilot", -+ "available", -+ "avalanche", -+ "avatar", -+ "avenge", -+ "avenging", -+ "avenue", -+ "average", -+ "aversion", -+ "avert", -+ "aviation", -+ "aviator", -+ "avid", -+ "avoid", -+ "await", -+ "awaken", -+ "award", -+ "aware", -+ "awhile", -+ "awkward", -+ "awning", -+ "awoke", -+ "awry", -+ "axis", -+ "babble", -+ "babbling", -+ "babied", -+ "baboon", -+ "backache", -+ "backboard", -+ "backboned", -+ "backdrop", -+ "backed", -+ "backer", -+ "backfield", -+ "backfire", -+ "backhand", -+ "backing", -+ "backlands", -+ "backlash", -+ "backless", -+ "backlight", -+ "backlit", -+ "backlog", -+ "backpack", -+ "backpedal", -+ "backrest", -+ "backroom", -+ "backshift", -+ "backside", -+ "backslid", -+ "backspace", -+ "backspin", -+ "backstab", -+ "backstage", -+ "backtalk", -+ "backtrack", -+ "backup", -+ "backward", -+ "backwash", -+ "backwater", -+ "backyard", -+ "bacon", -+ "bacteria", -+ "bacterium", -+ "badass", -+ "badge", -+ "badland", -+ "badly", -+ "badness", -+ "baffle", -+ "baffling", -+ "bagel", -+ "bagful", -+ "baggage", -+ "bagged", -+ "baggie", -+ "bagginess", -+ "bagging", -+ "baggy", -+ "bagpipe", -+ "baguette", -+ "baked", -+ "bakery", -+ "bakeshop", -+ "baking", -+ "balance", -+ "balancing", -+ "balcony", -+ "balmy", -+ "balsamic", -+ "bamboo", -+ "banana", -+ "banish", -+ "banister", -+ "banjo", -+ "bankable", -+ "bankbook", -+ "banked", -+ "banker", -+ "banking", -+ "banknote", -+ "bankroll", -+ "banner", -+ "bannister", -+ "banshee", -+ "banter", -+ "barbecue", -+ "barbed", -+ "barbell", -+ "barber", -+ "barcode", -+ "barge", -+ "bargraph", -+ "barista", -+ "baritone", -+ "barley", -+ "barmaid", -+ "barman", -+ "barn", -+ "barometer", -+ "barrack", -+ "barracuda", -+ "barrel", -+ "barrette", -+ "barricade", -+ "barrier", -+ "barstool", -+ "bartender", -+ "barterer", -+ "bash", -+ "basically", -+ "basics", -+ "basil", -+ "basin", -+ "basis", -+ "basket", -+ "batboy", -+ "batch", -+ "bath", -+ "baton", -+ "bats", -+ "battalion", -+ "battered", -+ "battering", -+ "battery", -+ "batting", -+ "battle", -+ "bauble", -+ "bazooka", -+ "blabber", -+ "bladder", -+ "blade", -+ "blah", -+ "blame", -+ "blaming", -+ "blanching", -+ "blandness", -+ "blank", -+ "blaspheme", -+ "blasphemy", -+ "blast", -+ "blatancy", -+ "blatantly", -+ "blazer", -+ "blazing", -+ "bleach", -+ "bleak", -+ "bleep", -+ "blemish", -+ "blend", -+ "bless", -+ "blighted", -+ "blimp", -+ "bling", -+ "blinked", -+ "blinker", -+ "blinking", -+ "blinks", -+ "blip", -+ "blissful", -+ "blitz", -+ "blizzard", -+ "bloated", -+ "bloating", -+ "blob", -+ "blog", -+ "bloomers", -+ "blooming", -+ "blooper", -+ "blot", -+ "blouse", -+ "blubber", -+ "bluff", -+ "bluish", -+ "blunderer", -+ "blunt", -+ "blurb", -+ "blurred", -+ "blurry", -+ "blurt", -+ "blush", -+ "blustery", -+ "boaster", -+ "boastful", -+ "boasting", -+ "boat", -+ "bobbed", -+ "bobbing", -+ "bobble", -+ "bobcat", -+ "bobsled", -+ "bobtail", -+ "bodacious", -+ "body", -+ "bogged", -+ "boggle", -+ "bogus", -+ "boil", -+ "bok", -+ "bolster", -+ "bolt", -+ "bonanza", -+ "bonded", -+ "bonding", -+ "bondless", -+ "boned", -+ "bonehead", -+ "boneless", -+ "bonelike", -+ "boney", -+ "bonfire", -+ "bonnet", -+ "bonsai", -+ "bonus", -+ "bony", -+ "boogeyman", -+ "boogieman", -+ "book", -+ "boondocks", -+ "booted", -+ "booth", -+ "bootie", -+ "booting", -+ "bootlace", -+ "bootleg", -+ "boots", -+ "boozy", -+ "borax", -+ "boring", -+ "borough", -+ "borrower", -+ "borrowing", -+ "boss", -+ "botanical", -+ "botanist", -+ "botany", -+ "botch", -+ "both", -+ "bottle", -+ "bottling", -+ "bottom", -+ "bounce", -+ "bouncing", -+ "bouncy", -+ "bounding", -+ "boundless", -+ "bountiful", -+ "bovine", -+ "boxcar", -+ "boxer", -+ "boxing", -+ "boxlike", -+ "boxy", -+ "breach", -+ "breath", -+ "breeches", -+ "breeching", -+ "breeder", -+ "breeding", -+ "breeze", -+ "breezy", -+ "brethren", -+ "brewery", -+ "brewing", -+ "briar", -+ "bribe", -+ "brick", -+ "bride", -+ "bridged", -+ "brigade", -+ "bright", -+ "brilliant", -+ "brim", -+ "bring", -+ "brink", -+ "brisket", -+ "briskly", -+ "briskness", -+ "bristle", -+ "brittle", -+ "broadband", -+ "broadcast", -+ "broaden", -+ "broadly", -+ "broadness", -+ "broadside", -+ "broadways", -+ "broiler", -+ "broiling", -+ "broken", -+ "broker", -+ "bronchial", -+ "bronco", -+ "bronze", -+ "bronzing", -+ "brook", -+ "broom", -+ "brought", -+ "browbeat", -+ "brownnose", -+ "browse", -+ "browsing", -+ "bruising", -+ "brunch", -+ "brunette", -+ "brunt", -+ "brush", -+ "brussels", -+ "brute", -+ "brutishly", -+ "bubble", -+ "bubbling", -+ "bubbly", -+ "buccaneer", -+ "bucked", -+ "bucket", -+ "buckle", -+ "buckshot", -+ "buckskin", -+ "bucktooth", -+ "buckwheat", -+ "buddhism", -+ "buddhist", -+ "budding", -+ "buddy", -+ "budget", -+ "buffalo", -+ "buffed", -+ "buffer", -+ "buffing", -+ "buffoon", -+ "buggy", -+ "bulb", -+ "bulge", -+ "bulginess", -+ "bulgur", -+ "bulk", -+ "bulldog", -+ "bulldozer", -+ "bullfight", -+ "bullfrog", -+ "bullhorn", -+ "bullion", -+ "bullish", -+ "bullpen", -+ "bullring", -+ "bullseye", -+ "bullwhip", -+ "bully", -+ "bunch", -+ "bundle", -+ "bungee", -+ "bunion", -+ "bunkbed", -+ "bunkhouse", -+ "bunkmate", -+ "bunny", -+ "bunt", -+ "busboy", -+ "bush", -+ "busily", -+ "busload", -+ "bust", -+ "busybody", -+ "buzz", -+ "cabana", -+ "cabbage", -+ "cabbie", -+ "cabdriver", -+ "cable", -+ "caboose", -+ "cache", -+ "cackle", -+ "cacti", -+ "cactus", -+ "caddie", -+ "caddy", -+ "cadet", -+ "cadillac", -+ "cadmium", -+ "cage", -+ "cahoots", -+ "cake", -+ "calamari", -+ "calamity", -+ "calcium", -+ "calculate", -+ "calculus", -+ "caliber", -+ "calibrate", -+ "calm", -+ "caloric", -+ "calorie", -+ "calzone", -+ "camcorder", -+ "cameo", -+ "camera", -+ "camisole", -+ "camper", -+ "campfire", -+ "camping", -+ "campsite", -+ "campus", -+ "canal", -+ "canary", -+ "cancel", -+ "candied", -+ "candle", -+ "candy", -+ "cane", -+ "canine", -+ "canister", -+ "cannabis", -+ "canned", -+ "canning", -+ "cannon", -+ "cannot", -+ "canola", -+ "canon", -+ "canopener", -+ "canopy", -+ "canteen", -+ "canyon", -+ "capable", -+ "capably", -+ "capacity", -+ "cape", -+ "capillary", -+ "capital", -+ "capitol", -+ "capped", -+ "capricorn", -+ "capsize", -+ "capsule", -+ "caption", -+ "captivate", -+ "captive", -+ "captivity", -+ "capture", -+ "caramel", -+ "carat", -+ "caravan", -+ "carbon", -+ "cardboard", -+ "carded", -+ "cardiac", -+ "cardigan", -+ "cardinal", -+ "cardstock", -+ "carefully", -+ "caregiver", -+ "careless", -+ "caress", -+ "caretaker", -+ "cargo", -+ "caring", -+ "carless", -+ "carload", -+ "carmaker", -+ "carnage", -+ "carnation", -+ "carnival", -+ "carnivore", -+ "carol", -+ "carpenter", -+ "carpentry", -+ "carpool", -+ "carport", -+ "carried", -+ "carrot", -+ "carrousel", -+ "carry", -+ "cartel", -+ "cartload", -+ "carton", -+ "cartoon", -+ "cartridge", -+ "cartwheel", -+ "carve", -+ "carving", -+ "carwash", -+ "cascade", -+ "case", -+ "cash", -+ "casing", -+ "casino", -+ "casket", -+ "cassette", -+ "casually", -+ "casualty", -+ "catacomb", -+ "catalog", -+ "catalyst", -+ "catalyze", -+ "catapult", -+ "cataract", -+ "catatonic", -+ "catcall", -+ "catchable", -+ "catcher", -+ "catching", -+ "catchy", -+ "caterer", -+ "catering", -+ "catfight", -+ "catfish", -+ "cathedral", -+ "cathouse", -+ "catlike", -+ "catnap", -+ "catnip", -+ "catsup", -+ "cattail", -+ "cattishly", -+ "cattle", -+ "catty", -+ "catwalk", -+ "caucasian", -+ "caucus", -+ "causal", -+ "causation", -+ "cause", -+ "causing", -+ "cauterize", -+ "caution", -+ "cautious", -+ "cavalier", -+ "cavalry", -+ "caviar", -+ "cavity", -+ "cedar", -+ "celery", -+ "celestial", -+ "celibacy", -+ "celibate", -+ "celtic", -+ "cement", -+ "census", -+ "ceramics", -+ "ceremony", -+ "certainly", -+ "certainty", -+ "certified", -+ "certify", -+ "cesarean", -+ "cesspool", -+ "chafe", -+ "chaffing", -+ "chain", -+ "chair", -+ "chalice", -+ "challenge", -+ "chamber", -+ "chamomile", -+ "champion", -+ "chance", -+ "change", -+ "channel", -+ "chant", -+ "chaos", -+ "chaperone", -+ "chaplain", -+ "chapped", -+ "chaps", -+ "chapter", -+ "character", -+ "charbroil", -+ "charcoal", -+ "charger", -+ "charging", -+ "chariot", -+ "charity", -+ "charm", -+ "charred", -+ "charter", -+ "charting", -+ "chase", -+ "chasing", -+ "chaste", -+ "chastise", -+ "chastity", -+ "chatroom", -+ "chatter", -+ "chatting", -+ "chatty", -+ "cheating", -+ "cheddar", -+ "cheek", -+ "cheer", -+ "cheese", -+ "cheesy", -+ "chef", -+ "chemicals", -+ "chemist", -+ "chemo", -+ "cherisher", -+ "cherub", -+ "chess", -+ "chest", -+ "chevron", -+ "chevy", -+ "chewable", -+ "chewer", -+ "chewing", -+ "chewy", -+ "chief", -+ "chihuahua", -+ "childcare", -+ "childhood", -+ "childish", -+ "childless", -+ "childlike", -+ "chili", -+ "chill", -+ "chimp", -+ "chip", -+ "chirping", -+ "chirpy", -+ "chitchat", -+ "chivalry", -+ "chive", -+ "chloride", -+ "chlorine", -+ "choice", -+ "chokehold", -+ "choking", -+ "chomp", -+ "chooser", -+ "choosing", -+ "choosy", -+ "chop", -+ "chosen", -+ "chowder", -+ "chowtime", -+ "chrome", -+ "chubby", -+ "chuck", -+ "chug", -+ "chummy", -+ "chump", -+ "chunk", -+ "churn", -+ "chute", -+ "cider", -+ "cilantro", -+ "cinch", -+ "cinema", -+ "cinnamon", -+ "circle", -+ "circling", -+ "circular", -+ "circulate", -+ "circus", -+ "citable", -+ "citadel", -+ "citation", -+ "citizen", -+ "citric", -+ "citrus", -+ "city", -+ "civic", -+ "civil", -+ "clad", -+ "claim", -+ "clambake", -+ "clammy", -+ "clamor", -+ "clamp", -+ "clamshell", -+ "clang", -+ "clanking", -+ "clapped", -+ "clapper", -+ "clapping", -+ "clarify", -+ "clarinet", -+ "clarity", -+ "clash", -+ "clasp", -+ "class", -+ "clatter", -+ "clause", -+ "clavicle", -+ "claw", -+ "clay", -+ "clean", -+ "clear", -+ "cleat", -+ "cleaver", -+ "cleft", -+ "clench", -+ "clergyman", -+ "clerical", -+ "clerk", -+ "clever", -+ "clicker", -+ "client", -+ "climate", -+ "climatic", -+ "cling", -+ "clinic", -+ "clinking", -+ "clip", -+ "clique", -+ "cloak", -+ "clobber", -+ "clock", -+ "clone", -+ "cloning", -+ "closable", -+ "closure", -+ "clothes", -+ "clothing", -+ "cloud", -+ "clover", -+ "clubbed", -+ "clubbing", -+ "clubhouse", -+ "clump", -+ "clumsily", -+ "clumsy", -+ "clunky", -+ "clustered", -+ "clutch", -+ "clutter", -+ "coach", -+ "coagulant", -+ "coastal", -+ "coaster", -+ "coasting", -+ "coastland", -+ "coastline", -+ "coat", -+ "coauthor", -+ "cobalt", -+ "cobbler", -+ "cobweb", -+ "cocoa", -+ "coconut", -+ "cod", -+ "coeditor", -+ "coerce", -+ "coexist", -+ "coffee", -+ "cofounder", -+ "cognition", -+ "cognitive", -+ "cogwheel", -+ "coherence", -+ "coherent", -+ "cohesive", -+ "coil", -+ "coke", -+ "cola", -+ "cold", -+ "coleslaw", -+ "coliseum", -+ "collage", -+ "collapse", -+ "collar", -+ "collected", -+ "collector", -+ "collide", -+ "collie", -+ "collision", -+ "colonial", -+ "colonist", -+ "colonize", -+ "colony", -+ "colossal", -+ "colt", -+ "coma", -+ "come", -+ "comfort", -+ "comfy", -+ "comic", -+ "coming", -+ "comma", -+ "commence", -+ "commend", -+ "comment", -+ "commerce", -+ "commode", -+ "commodity", -+ "commodore", -+ "common", -+ "commotion", -+ "commute", -+ "commuting", -+ "compacted", -+ "compacter", -+ "compactly", -+ "compactor", -+ "companion", -+ "company", -+ "compare", -+ "compel", -+ "compile", -+ "comply", -+ "component", -+ "composed", -+ "composer", -+ "composite", -+ "compost", -+ "composure", -+ "compound", -+ "compress", -+ "comprised", -+ "computer", -+ "computing", -+ "comrade", -+ "concave", -+ "conceal", -+ "conceded", -+ "concept", -+ "concerned", -+ "concert", -+ "conch", -+ "concierge", -+ "concise", -+ "conclude", -+ "concrete", -+ "concur", -+ "condense", -+ "condiment", -+ "condition", -+ "condone", -+ "conducive", -+ "conductor", -+ "conduit", -+ "cone", -+ "confess", -+ "confetti", -+ "confidant", -+ "confident", -+ "confider", -+ "confiding", -+ "configure", -+ "confined", -+ "confining", -+ "confirm", -+ "conflict", -+ "conform", -+ "confound", -+ "confront", -+ "confused", -+ "confusing", -+ "confusion", -+ "congenial", -+ "congested", -+ "congrats", -+ "congress", -+ "conical", -+ "conjoined", -+ "conjure", -+ "conjuror", -+ "connected", -+ "connector", -+ "consensus", -+ "consent", -+ "console", -+ "consoling", -+ "consonant", -+ "constable", -+ "constant", -+ "constrain", -+ "constrict", -+ "construct", -+ "consult", -+ "consumer", -+ "consuming", -+ "contact", -+ "container", -+ "contempt", -+ "contend", -+ "contented", -+ "contently", -+ "contents", -+ "contest", -+ "context", -+ "contort", -+ "contour", -+ "contrite", -+ "control", -+ "contusion", -+ "convene", -+ "convent", -+ "copartner", -+ "cope", -+ "copied", -+ "copier", -+ "copilot", -+ "coping", -+ "copious", -+ "copper", -+ "copy", -+ "coral", -+ "cork", -+ "cornball", -+ "cornbread", -+ "corncob", -+ "cornea", -+ "corned", -+ "corner", -+ "cornfield", -+ "cornflake", -+ "cornhusk", -+ "cornmeal", -+ "cornstalk", -+ "corny", -+ "coronary", -+ "coroner", -+ "corporal", -+ "corporate", -+ "corral", -+ "correct", -+ "corridor", -+ "corrode", -+ "corroding", -+ "corrosive", -+ "corsage", -+ "corset", -+ "cortex", -+ "cosigner", -+ "cosmetics", -+ "cosmic", -+ "cosmos", -+ "cosponsor", -+ "cost", -+ "cottage", -+ "cotton", -+ "couch", -+ "cough", -+ "could", -+ "countable", -+ "countdown", -+ "counting", -+ "countless", -+ "country", -+ "county", -+ "courier", -+ "covenant", -+ "cover", -+ "coveted", -+ "coveting", -+ "coyness", -+ "cozily", -+ "coziness", -+ "cozy", -+ "crabbing", -+ "crabgrass", -+ "crablike", -+ "crabmeat", -+ "cradle", -+ "cradling", -+ "crafter", -+ "craftily", -+ "craftsman", -+ "craftwork", -+ "crafty", -+ "cramp", -+ "cranberry", -+ "crane", -+ "cranial", -+ "cranium", -+ "crank", -+ "crate", -+ "crave", -+ "craving", -+ "crawfish", -+ "crawlers", -+ "crawling", -+ "crayfish", -+ "crayon", -+ "crazed", -+ "crazily", -+ "craziness", -+ "crazy", -+ "creamed", -+ "creamer", -+ "creamlike", -+ "crease", -+ "creasing", -+ "creatable", -+ "create", -+ "creation", -+ "creative", -+ "creature", -+ "credible", -+ "credibly", -+ "credit", -+ "creed", -+ "creme", -+ "creole", -+ "crepe", -+ "crept", -+ "crescent", -+ "crested", -+ "cresting", -+ "crestless", -+ "crevice", -+ "crewless", -+ "crewman", -+ "crewmate", -+ "crib", -+ "cricket", -+ "cried", -+ "crier", -+ "crimp", -+ "crimson", -+ "cringe", -+ "cringing", -+ "crinkle", -+ "crinkly", -+ "crisped", -+ "crisping", -+ "crisply", -+ "crispness", -+ "crispy", -+ "criteria", -+ "critter", -+ "croak", -+ "crock", -+ "crook", -+ "croon", -+ "crop", -+ "cross", -+ "crouch", -+ "crouton", -+ "crowbar", -+ "crowd", -+ "crown", -+ "crucial", -+ "crudely", -+ "crudeness", -+ "cruelly", -+ "cruelness", -+ "cruelty", -+ "crumb", -+ "crummiest", -+ "crummy", -+ "crumpet", -+ "crumpled", -+ "cruncher", -+ "crunching", -+ "crunchy", -+ "crusader", -+ "crushable", -+ "crushed", -+ "crusher", -+ "crushing", -+ "crust", -+ "crux", -+ "crying", -+ "cryptic", -+ "crystal", -+ "cubbyhole", -+ "cube", -+ "cubical", -+ "cubicle", -+ "cucumber", -+ "cuddle", -+ "cuddly", -+ "cufflink", -+ "culinary", -+ "culminate", -+ "culpable", -+ "culprit", -+ "cultivate", -+ "cultural", -+ "culture", -+ "cupbearer", -+ "cupcake", -+ "cupid", -+ "cupped", -+ "cupping", -+ "curable", -+ "curator", -+ "curdle", -+ "cure", -+ "curfew", -+ "curing", -+ "curled", -+ "curler", -+ "curliness", -+ "curling", -+ "curly", -+ "curry", -+ "curse", -+ "cursive", -+ "cursor", -+ "curtain", -+ "curtly", -+ "curtsy", -+ "curvature", -+ "curve", -+ "curvy", -+ "cushy", -+ "cusp", -+ "cussed", -+ "custard", -+ "custodian", -+ "custody", -+ "customary", -+ "customer", -+ "customize", -+ "customs", -+ "cut", -+ "cycle", -+ "cyclic", -+ "cycling", -+ "cyclist", -+ "cylinder", -+ "cymbal", -+ "cytoplasm", -+ "cytoplast", -+ "dab", -+ "dad", -+ "daffodil", -+ "dagger", -+ "daily", -+ "daintily", -+ "dainty", -+ "dairy", -+ "daisy", -+ "dallying", -+ "dance", -+ "dancing", -+ "dandelion", -+ "dander", -+ "dandruff", -+ "dandy", -+ "danger", -+ "dangle", -+ "dangling", -+ "daredevil", -+ "dares", -+ "daringly", -+ "darkened", -+ "darkening", -+ "darkish", -+ "darkness", -+ "darkroom", -+ "darling", -+ "darn", -+ "dart", -+ "darwinism", -+ "dash", -+ "dastardly", -+ "data", -+ "datebook", -+ "dating", -+ "daughter", -+ "daunting", -+ "dawdler", -+ "dawn", -+ "daybed", -+ "daybreak", -+ "daycare", -+ "daydream", -+ "daylight", -+ "daylong", -+ "dayroom", -+ "daytime", -+ "dazzler", -+ "dazzling", -+ "deacon", -+ "deafening", -+ "deafness", -+ "dealer", -+ "dealing", -+ "dealmaker", -+ "dealt", -+ "dean", -+ "debatable", -+ "debate", -+ "debating", -+ "debit", -+ "debrief", -+ "debtless", -+ "debtor", -+ "debug", -+ "debunk", -+ "decade", -+ "decaf", -+ "decal", -+ "decathlon", -+ "decay", -+ "deceased", -+ "deceit", -+ "deceiver", -+ "deceiving", -+ "december", -+ "decency", -+ "decent", -+ "deception", -+ "deceptive", -+ "decibel", -+ "decidable", -+ "decimal", -+ "decimeter", -+ "decipher", -+ "deck", -+ "declared", -+ "decline", -+ "decode", -+ "decompose", -+ "decorated", -+ "decorator", -+ "decoy", -+ "decrease", -+ "decree", -+ "dedicate", -+ "dedicator", -+ "deduce", -+ "deduct", -+ "deed", -+ "deem", -+ "deepen", -+ "deeply", -+ "deepness", -+ "deface", -+ "defacing", -+ "defame", -+ "default", -+ "defeat", -+ "defection", -+ "defective", -+ "defendant", -+ "defender", -+ "defense", -+ "defensive", -+ "deferral", -+ "deferred", -+ "defiance", -+ "defiant", -+ "defile", -+ "defiling", -+ "define", -+ "definite", -+ "deflate", -+ "deflation", -+ "deflator", -+ "deflected", -+ "deflector", -+ "defog", -+ "deforest", -+ "defraud", -+ "defrost", -+ "deftly", -+ "defuse", -+ "defy", -+ "degraded", -+ "degrading", -+ "degrease", -+ "degree", -+ "dehydrate", -+ "deity", -+ "dejected", -+ "delay", -+ "delegate", -+ "delegator", -+ "delete", -+ "deletion", -+ "delicacy", -+ "delicate", -+ "delicious", -+ "delighted", -+ "delirious", -+ "delirium", -+ "deliverer", -+ "delivery", -+ "delouse", -+ "delta", -+ "deluge", -+ "delusion", -+ "deluxe", -+ "demanding", -+ "demeaning", -+ "demeanor", -+ "demise", -+ "democracy", -+ "democrat", -+ "demote", -+ "demotion", -+ "demystify", -+ "denatured", -+ "deniable", -+ "denial", -+ "denim", -+ "denote", -+ "dense", -+ "density", -+ "dental", -+ "dentist", -+ "denture", -+ "deny", -+ "deodorant", -+ "deodorize", -+ "departed", -+ "departure", -+ "depict", -+ "deplete", -+ "depletion", -+ "deplored", -+ "deploy", -+ "deport", -+ "depose", -+ "depraved", -+ "depravity", -+ "deprecate", -+ "depress", -+ "deprive", -+ "depth", -+ "deputize", -+ "deputy", -+ "derail", -+ "deranged", -+ "derby", -+ "derived", -+ "desecrate", -+ "deserve", -+ "deserving", -+ "designate", -+ "designed", -+ "designer", -+ "designing", -+ "deskbound", -+ "desktop", -+ "deskwork", -+ "desolate", -+ "despair", -+ "despise", -+ "despite", -+ "destiny", -+ "destitute", -+ "destruct", -+ "detached", -+ "detail", -+ "detection", -+ "detective", -+ "detector", -+ "detention", -+ "detergent", -+ "detest", -+ "detonate", -+ "detonator", -+ "detoxify", -+ "detract", -+ "deuce", -+ "devalue", -+ "deviancy", -+ "deviant", -+ "deviate", -+ "deviation", -+ "deviator", -+ "device", -+ "devious", -+ "devotedly", -+ "devotee", -+ "devotion", -+ "devourer", -+ "devouring", -+ "devoutly", -+ "dexterity", -+ "dexterous", -+ "diabetes", -+ "diabetic", -+ "diabolic", -+ "diagnoses", -+ "diagnosis", -+ "diagram", -+ "dial", -+ "diameter", -+ "diaper", -+ "diaphragm", -+ "diary", -+ "dice", -+ "dicing", -+ "dictate", -+ "dictation", -+ "dictator", -+ "difficult", -+ "diffused", -+ "diffuser", -+ "diffusion", -+ "diffusive", -+ "dig", -+ "dilation", -+ "diligence", -+ "diligent", -+ "dill", -+ "dilute", -+ "dime", -+ "diminish", -+ "dimly", -+ "dimmed", -+ "dimmer", -+ "dimness", -+ "dimple", -+ "diner", -+ "dingbat", -+ "dinghy", -+ "dinginess", -+ "dingo", -+ "dingy", -+ "dining", -+ "dinner", -+ "diocese", -+ "dioxide", -+ "diploma", -+ "dipped", -+ "dipper", -+ "dipping", -+ "directed", -+ "direction", -+ "directive", -+ "directly", -+ "directory", -+ "direness", -+ "dirtiness", -+ "disabled", -+ "disagree", -+ "disallow", -+ "disarm", -+ "disarray", -+ "disaster", -+ "disband", -+ "disbelief", -+ "disburse", -+ "discard", -+ "discern", -+ "discharge", -+ "disclose", -+ "discolor", -+ "discount", -+ "discourse", -+ "discover", -+ "discuss", -+ "disdain", -+ "disengage", -+ "disfigure", -+ "disgrace", -+ "dish", -+ "disinfect", -+ "disjoin", -+ "disk", -+ "dislike", -+ "disliking", -+ "dislocate", -+ "dislodge", -+ "disloyal", -+ "dismantle", -+ "dismay", -+ "dismiss", -+ "dismount", -+ "disobey", -+ "disorder", -+ "disown", -+ "disparate", -+ "disparity", -+ "dispatch", -+ "dispense", -+ "dispersal", -+ "dispersed", -+ "disperser", -+ "displace", -+ "display", -+ "displease", -+ "disposal", -+ "dispose", -+ "disprove", -+ "dispute", -+ "disregard", -+ "disrupt", -+ "dissuade", -+ "distance", -+ "distant", -+ "distaste", -+ "distill", -+ "distinct", -+ "distort", -+ "distract", -+ "distress", -+ "district", -+ "distrust", -+ "ditch", -+ "ditto", -+ "ditzy", -+ "dividable", -+ "divided", -+ "dividend", -+ "dividers", -+ "dividing", -+ "divinely", -+ "diving", -+ "divinity", -+ "divisible", -+ "divisibly", -+ "division", -+ "divisive", -+ "divorcee", -+ "dizziness", -+ "dizzy", -+ "doable", -+ "docile", -+ "dock", -+ "doctrine", -+ "document", -+ "dodge", -+ "dodgy", -+ "doily", -+ "doing", -+ "dole", -+ "dollar", -+ "dollhouse", -+ "dollop", -+ "dolly", -+ "dolphin", -+ "domain", -+ "domelike", -+ "domestic", -+ "dominion", -+ "dominoes", -+ "donated", -+ "donation", -+ "donator", -+ "donor", -+ "donut", -+ "doodle", -+ "doorbell", -+ "doorframe", -+ "doorknob", -+ "doorman", -+ "doormat", -+ "doornail", -+ "doorpost", -+ "doorstep", -+ "doorstop", -+ "doorway", -+ "doozy", -+ "dork", -+ "dormitory", -+ "dorsal", -+ "dosage", -+ "dose", -+ "dotted", -+ "doubling", -+ "douche", -+ "dove", -+ "down", -+ "dowry", -+ "doze", -+ "drab", -+ "dragging", -+ "dragonfly", -+ "dragonish", -+ "dragster", -+ "drainable", -+ "drainage", -+ "drained", -+ "drainer", -+ "drainpipe", -+ "dramatic", -+ "dramatize", -+ "drank", -+ "drapery", -+ "drastic", -+ "draw", -+ "dreaded", -+ "dreadful", -+ "dreadlock", -+ "dreamboat", -+ "dreamily", -+ "dreamland", -+ "dreamless", -+ "dreamlike", -+ "dreamt", -+ "dreamy", -+ "drearily", -+ "dreary", -+ "drench", -+ "dress", -+ "drew", -+ "dribble", -+ "dried", -+ "drier", -+ "drift", -+ "driller", -+ "drilling", -+ "drinkable", -+ "drinking", -+ "dripping", -+ "drippy", -+ "drivable", -+ "driven", -+ "driver", -+ "driveway", -+ "driving", -+ "drizzle", -+ "drizzly", -+ "drone", -+ "drool", -+ "droop", -+ "drop-down", -+ "dropbox", -+ "dropkick", -+ "droplet", -+ "dropout", -+ "dropper", -+ "drove", -+ "drown", -+ "drowsily", -+ "drudge", -+ "drum", -+ "dry", -+ "dubbed", -+ "dubiously", -+ "duchess", -+ "duckbill", -+ "ducking", -+ "duckling", -+ "ducktail", -+ "ducky", -+ "duct", -+ "dude", -+ "duffel", -+ "dugout", -+ "duh", -+ "duke", -+ "duller", -+ "dullness", -+ "duly", -+ "dumping", -+ "dumpling", -+ "dumpster", -+ "duo", -+ "dupe", -+ "duplex", -+ "duplicate", -+ "duplicity", -+ "durable", -+ "durably", -+ "duration", -+ "duress", -+ "during", -+ "dusk", -+ "dust", -+ "dutiful", -+ "duty", -+ "duvet", -+ "dwarf", -+ "dweeb", -+ "dwelled", -+ "dweller", -+ "dwelling", -+ "dwindle", -+ "dwindling", -+ "dynamic", -+ "dynamite", -+ "dynasty", -+ "dyslexia", -+ "dyslexic", -+ "each", -+ "eagle", -+ "earache", -+ "eardrum", -+ "earflap", -+ "earful", -+ "earlobe", -+ "early", -+ "earmark", -+ "earmuff", -+ "earphone", -+ "earpiece", -+ "earplugs", -+ "earring", -+ "earshot", -+ "earthen", -+ "earthlike", -+ "earthling", -+ "earthly", -+ "earthworm", -+ "earthy", -+ "earwig", -+ "easeful", -+ "easel", -+ "easiest", -+ "easily", -+ "easiness", -+ "easing", -+ "eastbound", -+ "eastcoast", -+ "easter", -+ "eastward", -+ "eatable", -+ "eaten", -+ "eatery", -+ "eating", -+ "eats", -+ "ebay", -+ "ebony", -+ "ebook", -+ "ecard", -+ "eccentric", -+ "echo", -+ "eclair", -+ "eclipse", -+ "ecologist", -+ "ecology", -+ "economic", -+ "economist", -+ "economy", -+ "ecosphere", -+ "ecosystem", -+ "edge", -+ "edginess", -+ "edging", -+ "edgy", -+ "edition", -+ "editor", -+ "educated", -+ "education", -+ "educator", -+ "eel", -+ "effective", -+ "effects", -+ "efficient", -+ "effort", -+ "eggbeater", -+ "egging", -+ "eggnog", -+ "eggplant", -+ "eggshell", -+ "egomaniac", -+ "egotism", -+ "egotistic", -+ "either", -+ "eject", -+ "elaborate", -+ "elastic", -+ "elated", -+ "elbow", -+ "eldercare", -+ "elderly", -+ "eldest", -+ "electable", -+ "election", -+ "elective", -+ "elephant", -+ "elevate", -+ "elevating", -+ "elevation", -+ "elevator", -+ "eleven", -+ "elf", -+ "eligible", -+ "eligibly", -+ "eliminate", -+ "elite", -+ "elitism", -+ "elixir", -+ "elk", -+ "ellipse", -+ "elliptic", -+ "elm", -+ "elongated", -+ "elope", -+ "eloquence", -+ "eloquent", -+ "elsewhere", -+ "elude", -+ "elusive", -+ "elves", -+ "email", -+ "embargo", -+ "embark", -+ "embassy", -+ "embattled", -+ "embellish", -+ "ember", -+ "embezzle", -+ "emblaze", -+ "emblem", -+ "embody", -+ "embolism", -+ "emboss", -+ "embroider", -+ "emcee", -+ "emerald", -+ "emergency", -+ "emission", -+ "emit", -+ "emote", -+ "emoticon", -+ "emotion", -+ "empathic", -+ "empathy", -+ "emperor", -+ "emphases", -+ "emphasis", -+ "emphasize", -+ "emphatic", -+ "empirical", -+ "employed", -+ "employee", -+ "employer", -+ "emporium", -+ "empower", -+ "emptier", -+ "emptiness", -+ "empty", -+ "emu", -+ "enable", -+ "enactment", -+ "enamel", -+ "enchanted", -+ "enchilada", -+ "encircle", -+ "enclose", -+ "enclosure", -+ "encode", -+ "encore", -+ "encounter", -+ "encourage", -+ "encroach", -+ "encrust", -+ "encrypt", -+ "endanger", -+ "endeared", -+ "endearing", -+ "ended", -+ "ending", -+ "endless", -+ "endnote", -+ "endocrine", -+ "endorphin", -+ "endorse", -+ "endowment", -+ "endpoint", -+ "endurable", -+ "endurance", -+ "enduring", -+ "energetic", -+ "energize", -+ "energy", -+ "enforced", -+ "enforcer", -+ "engaged", -+ "engaging", -+ "engine", -+ "engorge", -+ "engraved", -+ "engraver", -+ "engraving", -+ "engross", -+ "engulf", -+ "enhance", -+ "enigmatic", -+ "enjoyable", -+ "enjoyably", -+ "enjoyer", -+ "enjoying", -+ "enjoyment", -+ "enlarged", -+ "enlarging", -+ "enlighten", -+ "enlisted", -+ "enquirer", -+ "enrage", -+ "enrich", -+ "enroll", -+ "enslave", -+ "ensnare", -+ "ensure", -+ "entail", -+ "entangled", -+ "entering", -+ "entertain", -+ "enticing", -+ "entire", -+ "entitle", -+ "entity", -+ "entomb", -+ "entourage", -+ "entrap", -+ "entree", -+ "entrench", -+ "entrust", -+ "entryway", -+ "entwine", -+ "enunciate", -+ "envelope", -+ "enviable", -+ "enviably", -+ "envious", -+ "envision", -+ "envoy", -+ "envy", -+ "enzyme", -+ "epic", -+ "epidemic", -+ "epidermal", -+ "epidermis", -+ "epidural", -+ "epilepsy", -+ "epileptic", -+ "epilogue", -+ "epiphany", -+ "episode", -+ "equal", -+ "equate", -+ "equation", -+ "equator", -+ "equinox", -+ "equipment", -+ "equity", -+ "equivocal", -+ "eradicate", -+ "erasable", -+ "erased", -+ "eraser", -+ "erasure", -+ "ergonomic", -+ "errand", -+ "errant", -+ "erratic", -+ "error", -+ "erupt", -+ "escalate", -+ "escalator", -+ "escapable", -+ "escapade", -+ "escapist", -+ "escargot", -+ "eskimo", -+ "esophagus", -+ "espionage", -+ "espresso", -+ "esquire", -+ "essay", -+ "essence", -+ "essential", -+ "establish", -+ "estate", -+ "esteemed", -+ "estimate", -+ "estimator", -+ "estranged", -+ "estrogen", -+ "etching", -+ "eternal", -+ "eternity", -+ "ethanol", -+ "ether", -+ "ethically", -+ "ethics", -+ "euphemism", -+ "evacuate", -+ "evacuee", -+ "evade", -+ "evaluate", -+ "evaluator", -+ "evaporate", -+ "evasion", -+ "evasive", -+ "even", -+ "everglade", -+ "evergreen", -+ "everybody", -+ "everyday", -+ "everyone", -+ "evict", -+ "evidence", -+ "evident", -+ "evil", -+ "evoke", -+ "evolution", -+ "evolve", -+ "exact", -+ "exalted", -+ "example", -+ "excavate", -+ "excavator", -+ "exceeding", -+ "exception", -+ "excess", -+ "exchange", -+ "excitable", -+ "exciting", -+ "exclaim", -+ "exclude", -+ "excluding", -+ "exclusion", -+ "exclusive", -+ "excretion", -+ "excretory", -+ "excursion", -+ "excusable", -+ "excusably", -+ "excuse", -+ "exemplary", -+ "exemplify", -+ "exemption", -+ "exerciser", -+ "exert", -+ "exes", -+ "exfoliate", -+ "exhale", -+ "exhaust", -+ "exhume", -+ "exile", -+ "existing", -+ "exit", -+ "exodus", -+ "exonerate", -+ "exorcism", -+ "exorcist", -+ "expand", -+ "expanse", -+ "expansion", -+ "expansive", -+ "expectant", -+ "expedited", -+ "expediter", -+ "expel", -+ "expend", -+ "expenses", -+ "expensive", -+ "expert", -+ "expire", -+ "expiring", -+ "explain", -+ "expletive", -+ "explicit", -+ "explode", -+ "exploit", -+ "explore", -+ "exploring", -+ "exponent", -+ "exporter", -+ "exposable", -+ "expose", -+ "exposure", -+ "express", -+ "expulsion", -+ "exquisite", -+ "extended", -+ "extending", -+ "extent", -+ "extenuate", -+ "exterior", -+ "external", -+ "extinct", -+ "extortion", -+ "extradite", -+ "extras", -+ "extrovert", -+ "extrude", -+ "extruding", -+ "exuberant", -+ "fable", -+ "fabric", -+ "fabulous", -+ "facebook", -+ "facecloth", -+ "facedown", -+ "faceless", -+ "facelift", -+ "faceplate", -+ "faceted", -+ "facial", -+ "facility", -+ "facing", -+ "facsimile", -+ "faction", -+ "factoid", -+ "factor", -+ "factsheet", -+ "factual", -+ "faculty", -+ "fade", -+ "fading", -+ "failing", -+ "falcon", -+ "fall", -+ "false", -+ "falsify", -+ "fame", -+ "familiar", -+ "family", -+ "famine", -+ "famished", -+ "fanatic", -+ "fancied", -+ "fanciness", -+ "fancy", -+ "fanfare", -+ "fang", -+ "fanning", -+ "fantasize", -+ "fantastic", -+ "fantasy", -+ "fascism", -+ "fastball", -+ "faster", -+ "fasting", -+ "fastness", -+ "faucet", -+ "favorable", -+ "favorably", -+ "favored", -+ "favoring", -+ "favorite", -+ "fax", -+ "feast", -+ "federal", -+ "fedora", -+ "feeble", -+ "feed", -+ "feel", -+ "feisty", -+ "feline", -+ "felt-tip", -+ "feminine", -+ "feminism", -+ "feminist", -+ "feminize", -+ "femur", -+ "fence", -+ "fencing", -+ "fender", -+ "ferment", -+ "fernlike", -+ "ferocious", -+ "ferocity", -+ "ferret", -+ "ferris", -+ "ferry", -+ "fervor", -+ "fester", -+ "festival", -+ "festive", -+ "festivity", -+ "fetal", -+ "fetch", -+ "fever", -+ "fiber", -+ "fiction", -+ "fiddle", -+ "fiddling", -+ "fidelity", -+ "fidgeting", -+ "fidgety", -+ "fifteen", -+ "fifth", -+ "fiftieth", -+ "fifty", -+ "figment", -+ "figure", -+ "figurine", -+ "filing", -+ "filled", -+ "filler", -+ "filling", -+ "film", -+ "filter", -+ "filth", -+ "filtrate", -+ "finale", -+ "finalist", -+ "finalize", -+ "finally", -+ "finance", -+ "financial", -+ "finch", -+ "fineness", -+ "finer", -+ "finicky", -+ "finished", -+ "finisher", -+ "finishing", -+ "finite", -+ "finless", -+ "finlike", -+ "fiscally", -+ "fit", -+ "five", -+ "flaccid", -+ "flagman", -+ "flagpole", -+ "flagship", -+ "flagstick", -+ "flagstone", -+ "flail", -+ "flakily", -+ "flaky", -+ "flame", -+ "flammable", -+ "flanked", -+ "flanking", -+ "flannels", -+ "flap", -+ "flaring", -+ "flashback", -+ "flashbulb", -+ "flashcard", -+ "flashily", -+ "flashing", -+ "flashy", -+ "flask", -+ "flatbed", -+ "flatfoot", -+ "flatly", -+ "flatness", -+ "flatten", -+ "flattered", -+ "flatterer", -+ "flattery", -+ "flattop", -+ "flatware", -+ "flatworm", -+ "flavored", -+ "flavorful", -+ "flavoring", -+ "flaxseed", -+ "fled", -+ "fleshed", -+ "fleshy", -+ "flick", -+ "flier", -+ "flight", -+ "flinch", -+ "fling", -+ "flint", -+ "flip", -+ "flirt", -+ "float", -+ "flock", -+ "flogging", -+ "flop", -+ "floral", -+ "florist", -+ "floss", -+ "flounder", -+ "flyable", -+ "flyaway", -+ "flyer", -+ "flying", -+ "flyover", -+ "flypaper", -+ "foam", -+ "foe", -+ "fog", -+ "foil", -+ "folic", -+ "folk", -+ "follicle", -+ "follow", -+ "fondling", -+ "fondly", -+ "fondness", -+ "fondue", -+ "font", -+ "food", -+ "fool", -+ "footage", -+ "football", -+ "footbath", -+ "footboard", -+ "footer", -+ "footgear", -+ "foothill", -+ "foothold", -+ "footing", -+ "footless", -+ "footman", -+ "footnote", -+ "footpad", -+ "footpath", -+ "footprint", -+ "footrest", -+ "footsie", -+ "footsore", -+ "footwear", -+ "footwork", -+ "fossil", -+ "foster", -+ "founder", -+ "founding", -+ "fountain", -+ "fox", -+ "foyer", -+ "fraction", -+ "fracture", -+ "fragile", -+ "fragility", -+ "fragment", -+ "fragrance", -+ "fragrant", -+ "frail", -+ "frame", -+ "framing", -+ "frantic", -+ "fraternal", -+ "frayed", -+ "fraying", -+ "frays", -+ "freckled", -+ "freckles", -+ "freebase", -+ "freebee", -+ "freebie", -+ "freedom", -+ "freefall", -+ "freehand", -+ "freeing", -+ "freeload", -+ "freely", -+ "freemason", -+ "freeness", -+ "freestyle", -+ "freeware", -+ "freeway", -+ "freewill", -+ "freezable", -+ "freezing", -+ "freight", -+ "french", -+ "frenzied", -+ "frenzy", -+ "frequency", -+ "frequent", -+ "fresh", -+ "fretful", -+ "fretted", -+ "friction", -+ "friday", -+ "fridge", -+ "fried", -+ "friend", -+ "frighten", -+ "frightful", -+ "frigidity", -+ "frigidly", -+ "frill", -+ "fringe", -+ "frisbee", -+ "frisk", -+ "fritter", -+ "frivolous", -+ "frolic", -+ "from", -+ "front", -+ "frostbite", -+ "frosted", -+ "frostily", -+ "frosting", -+ "frostlike", -+ "frosty", -+ "froth", -+ "frown", -+ "frozen", -+ "fructose", -+ "frugality", -+ "frugally", -+ "fruit", -+ "frustrate", -+ "frying", -+ "gab", -+ "gaffe", -+ "gag", -+ "gainfully", -+ "gaining", -+ "gains", -+ "gala", -+ "gallantly", -+ "galleria", -+ "gallery", -+ "galley", -+ "gallon", -+ "gallows", -+ "gallstone", -+ "galore", -+ "galvanize", -+ "gambling", -+ "game", -+ "gaming", -+ "gamma", -+ "gander", -+ "gangly", -+ "gangrene", -+ "gangway", -+ "gap", -+ "garage", -+ "garbage", -+ "garden", -+ "gargle", -+ "garland", -+ "garlic", -+ "garment", -+ "garnet", -+ "garnish", -+ "garter", -+ "gas", -+ "gatherer", -+ "gathering", -+ "gating", -+ "gauging", -+ "gauntlet", -+ "gauze", -+ "gave", -+ "gawk", -+ "gazing", -+ "gear", -+ "gecko", -+ "geek", -+ "geiger", -+ "gem", -+ "gender", -+ "generic", -+ "generous", -+ "genetics", -+ "genre", -+ "gentile", -+ "gentleman", -+ "gently", -+ "gents", -+ "geography", -+ "geologic", -+ "geologist", -+ "geology", -+ "geometric", -+ "geometry", -+ "geranium", -+ "gerbil", -+ "geriatric", -+ "germicide", -+ "germinate", -+ "germless", -+ "germproof", -+ "gestate", -+ "gestation", -+ "gesture", -+ "getaway", -+ "getting", -+ "getup", -+ "giant", -+ "gibberish", -+ "giblet", -+ "giddily", -+ "giddiness", -+ "giddy", -+ "gift", -+ "gigabyte", -+ "gigahertz", -+ "gigantic", -+ "giggle", -+ "giggling", -+ "giggly", -+ "gigolo", -+ "gilled", -+ "gills", -+ "gimmick", -+ "girdle", -+ "giveaway", -+ "given", -+ "giver", -+ "giving", -+ "gizmo", -+ "gizzard", -+ "glacial", -+ "glacier", -+ "glade", -+ "gladiator", -+ "gladly", -+ "glamorous", -+ "glamour", -+ "glance", -+ "glancing", -+ "glandular", -+ "glare", -+ "glaring", -+ "glass", -+ "glaucoma", -+ "glazing", -+ "gleaming", -+ "gleeful", -+ "glider", -+ "gliding", -+ "glimmer", -+ "glimpse", -+ "glisten", -+ "glitch", -+ "glitter", -+ "glitzy", -+ "gloater", -+ "gloating", -+ "gloomily", -+ "gloomy", -+ "glorified", -+ "glorifier", -+ "glorify", -+ "glorious", -+ "glory", -+ "gloss", -+ "glove", -+ "glowing", -+ "glowworm", -+ "glucose", -+ "glue", -+ "gluten", -+ "glutinous", -+ "glutton", -+ "gnarly", -+ "gnat", -+ "goal", -+ "goatskin", -+ "goes", -+ "goggles", -+ "going", -+ "goldfish", -+ "goldmine", -+ "goldsmith", -+ "golf", -+ "goliath", -+ "gonad", -+ "gondola", -+ "gone", -+ "gong", -+ "good", -+ "gooey", -+ "goofball", -+ "goofiness", -+ "goofy", -+ "google", -+ "goon", -+ "gopher", -+ "gore", -+ "gorged", -+ "gorgeous", -+ "gory", -+ "gosling", -+ "gossip", -+ "gothic", -+ "gotten", -+ "gout", -+ "gown", -+ "grab", -+ "graceful", -+ "graceless", -+ "gracious", -+ "gradation", -+ "graded", -+ "grader", -+ "gradient", -+ "grading", -+ "gradually", -+ "graduate", -+ "graffiti", -+ "grafted", -+ "grafting", -+ "grain", -+ "granddad", -+ "grandkid", -+ "grandly", -+ "grandma", -+ "grandpa", -+ "grandson", -+ "granite", -+ "granny", -+ "granola", -+ "grant", -+ "granular", -+ "grape", -+ "graph", -+ "grapple", -+ "grappling", -+ "grasp", -+ "grass", -+ "gratified", -+ "gratify", -+ "grating", -+ "gratitude", -+ "gratuity", -+ "gravel", -+ "graveness", -+ "graves", -+ "graveyard", -+ "gravitate", -+ "gravity", -+ "gravy", -+ "gray", -+ "grazing", -+ "greasily", -+ "greedily", -+ "greedless", -+ "greedy", -+ "green", -+ "greeter", -+ "greeting", -+ "grew", -+ "greyhound", -+ "grid", -+ "grief", -+ "grievance", -+ "grieving", -+ "grievous", -+ "grill", -+ "grimace", -+ "grimacing", -+ "grime", -+ "griminess", -+ "grimy", -+ "grinch", -+ "grinning", -+ "grip", -+ "gristle", -+ "grit", -+ "groggily", -+ "groggy", -+ "groin", -+ "groom", -+ "groove", -+ "grooving", -+ "groovy", -+ "grope", -+ "ground", -+ "grouped", -+ "grout", -+ "grove", -+ "grower", -+ "growing", -+ "growl", -+ "grub", -+ "grudge", -+ "grudging", -+ "grueling", -+ "gruffly", -+ "grumble", -+ "grumbling", -+ "grumbly", -+ "grumpily", -+ "grunge", -+ "grunt", -+ "guacamole", -+ "guidable", -+ "guidance", -+ "guide", -+ "guiding", -+ "guileless", -+ "guise", -+ "gulf", -+ "gullible", -+ "gully", -+ "gulp", -+ "gumball", -+ "gumdrop", -+ "gumminess", -+ "gumming", -+ "gummy", -+ "gurgle", -+ "gurgling", -+ "guru", -+ "gush", -+ "gusto", -+ "gusty", -+ "gutless", -+ "guts", -+ "gutter", -+ "guy", -+ "guzzler", -+ "gyration", -+ "habitable", -+ "habitant", -+ "habitat", -+ "habitual", -+ "hacked", -+ "hacker", -+ "hacking", -+ "hacksaw", -+ "had", -+ "haggler", -+ "haiku", -+ "half", -+ "halogen", -+ "halt", -+ "halved", -+ "halves", -+ "hamburger", -+ "hamlet", -+ "hammock", -+ "hamper", -+ "hamster", -+ "hamstring", -+ "handbag", -+ "handball", -+ "handbook", -+ "handbrake", -+ "handcart", -+ "handclap", -+ "handclasp", -+ "handcraft", -+ "handcuff", -+ "handed", -+ "handful", -+ "handgrip", -+ "handgun", -+ "handheld", -+ "handiness", -+ "handiwork", -+ "handlebar", -+ "handled", -+ "handler", -+ "handling", -+ "handmade", -+ "handoff", -+ "handpick", -+ "handprint", -+ "handrail", -+ "handsaw", -+ "handset", -+ "handsfree", -+ "handshake", -+ "handstand", -+ "handwash", -+ "handwork", -+ "handwoven", -+ "handwrite", -+ "handyman", -+ "hangnail", -+ "hangout", -+ "hangover", -+ "hangup", -+ "hankering", -+ "hankie", -+ "hanky", -+ "haphazard", -+ "happening", -+ "happier", -+ "happiest", -+ "happily", -+ "happiness", -+ "happy", -+ "harbor", -+ "hardcopy", -+ "hardcore", -+ "hardcover", -+ "harddisk", -+ "hardened", -+ "hardener", -+ "hardening", -+ "hardhat", -+ "hardhead", -+ "hardiness", -+ "hardly", -+ "hardness", -+ "hardship", -+ "hardware", -+ "hardwired", -+ "hardwood", -+ "hardy", -+ "harmful", -+ "harmless", -+ "harmonica", -+ "harmonics", -+ "harmonize", -+ "harmony", -+ "harness", -+ "harpist", -+ "harsh", -+ "harvest", -+ "hash", -+ "hassle", -+ "haste", -+ "hastily", -+ "hastiness", -+ "hasty", -+ "hatbox", -+ "hatchback", -+ "hatchery", -+ "hatchet", -+ "hatching", -+ "hatchling", -+ "hate", -+ "hatless", -+ "hatred", -+ "haunt", -+ "haven", -+ "hazard", -+ "hazelnut", -+ "hazily", -+ "haziness", -+ "hazing", -+ "hazy", -+ "headache", -+ "headband", -+ "headboard", -+ "headcount", -+ "headdress", -+ "headed", -+ "header", -+ "headfirst", -+ "headgear", -+ "heading", -+ "headlamp", -+ "headless", -+ "headlock", -+ "headphone", -+ "headpiece", -+ "headrest", -+ "headroom", -+ "headscarf", -+ "headset", -+ "headsman", -+ "headstand", -+ "headstone", -+ "headway", -+ "headwear", -+ "heap", -+ "heat", -+ "heave", -+ "heavily", -+ "heaviness", -+ "heaving", -+ "hedge", -+ "hedging", -+ "heftiness", -+ "hefty", -+ "helium", -+ "helmet", -+ "helper", -+ "helpful", -+ "helping", -+ "helpless", -+ "helpline", -+ "hemlock", -+ "hemstitch", -+ "hence", -+ "henchman", -+ "henna", -+ "herald", -+ "herbal", -+ "herbicide", -+ "herbs", -+ "heritage", -+ "hermit", -+ "heroics", -+ "heroism", -+ "herring", -+ "herself", -+ "hertz", -+ "hesitancy", -+ "hesitant", -+ "hesitate", -+ "hexagon", -+ "hexagram", -+ "hubcap", -+ "huddle", -+ "huddling", -+ "huff", -+ "hug", -+ "hula", -+ "hulk", -+ "hull", -+ "human", -+ "humble", -+ "humbling", -+ "humbly", -+ "humid", -+ "humiliate", -+ "humility", -+ "humming", -+ "hummus", -+ "humongous", -+ "humorist", -+ "humorless", -+ "humorous", -+ "humpback", -+ "humped", -+ "humvee", -+ "hunchback", -+ "hundredth", -+ "hunger", -+ "hungrily", -+ "hungry", -+ "hunk", -+ "hunter", -+ "hunting", -+ "huntress", -+ "huntsman", -+ "hurdle", -+ "hurled", -+ "hurler", -+ "hurling", -+ "hurray", -+ "hurricane", -+ "hurried", -+ "hurry", -+ "hurt", -+ "husband", -+ "hush", -+ "husked", -+ "huskiness", -+ "hut", -+ "hybrid", -+ "hydrant", -+ "hydrated", -+ "hydration", -+ "hydrogen", -+ "hydroxide", -+ "hyperlink", -+ "hypertext", -+ "hyphen", -+ "hypnoses", -+ "hypnosis", -+ "hypnotic", -+ "hypnotism", -+ "hypnotist", -+ "hypnotize", -+ "hypocrisy", -+ "hypocrite", -+ "ibuprofen", -+ "ice", -+ "iciness", -+ "icing", -+ "icky", -+ "icon", -+ "icy", -+ "idealism", -+ "idealist", -+ "idealize", -+ "ideally", -+ "idealness", -+ "identical", -+ "identify", -+ "identity", -+ "ideology", -+ "idiocy", -+ "idiom", -+ "idly", -+ "igloo", -+ "ignition", -+ "ignore", -+ "iguana", -+ "illicitly", -+ "illusion", -+ "illusive", -+ "image", -+ "imaginary", -+ "imagines", -+ "imaging", -+ "imbecile", -+ "imitate", -+ "imitation", -+ "immature", -+ "immerse", -+ "immersion", -+ "imminent", -+ "immobile", -+ "immodest", -+ "immorally", -+ "immortal", -+ "immovable", -+ "immovably", -+ "immunity", -+ "immunize", -+ "impaired", -+ "impale", -+ "impart", -+ "impatient", -+ "impeach", -+ "impeding", -+ "impending", -+ "imperfect", -+ "imperial", -+ "impish", -+ "implant", -+ "implement", -+ "implicate", -+ "implicit", -+ "implode", -+ "implosion", -+ "implosive", -+ "imply", -+ "impolite", -+ "important", -+ "importer", -+ "impose", -+ "imposing", -+ "impotence", -+ "impotency", -+ "impotent", -+ "impound", -+ "imprecise", -+ "imprint", -+ "imprison", -+ "impromptu", -+ "improper", -+ "improve", -+ "improving", -+ "improvise", -+ "imprudent", -+ "impulse", -+ "impulsive", -+ "impure", -+ "impurity", -+ "iodine", -+ "iodize", -+ "ion", -+ "ipad", -+ "iphone", -+ "ipod", -+ "irate", -+ "irk", -+ "iron", -+ "irregular", -+ "irrigate", -+ "irritable", -+ "irritably", -+ "irritant", -+ "irritate", -+ "islamic", -+ "islamist", -+ "isolated", -+ "isolating", -+ "isolation", -+ "isotope", -+ "issue", -+ "issuing", -+ "italicize", -+ "italics", -+ "item", -+ "itinerary", -+ "itunes", -+ "ivory", -+ "ivy", -+ "jab", -+ "jackal", -+ "jacket", -+ "jackknife", -+ "jackpot", -+ "jailbird", -+ "jailbreak", -+ "jailer", -+ "jailhouse", -+ "jalapeno", -+ "jam", -+ "janitor", -+ "january", -+ "jargon", -+ "jarring", -+ "jasmine", -+ "jaundice", -+ "jaunt", -+ "java", -+ "jawed", -+ "jawless", -+ "jawline", -+ "jaws", -+ "jaybird", -+ "jaywalker", -+ "jazz", -+ "jeep", -+ "jeeringly", -+ "jellied", -+ "jelly", -+ "jersey", -+ "jester", -+ "jet", -+ "jiffy", -+ "jigsaw", -+ "jimmy", -+ "jingle", -+ "jingling", -+ "jinx", -+ "jitters", -+ "jittery", -+ "job", -+ "jockey", -+ "jockstrap", -+ "jogger", -+ "jogging", -+ "john", -+ "joining", -+ "jokester", -+ "jokingly", -+ "jolliness", -+ "jolly", -+ "jolt", -+ "jot", -+ "jovial", -+ "joyfully", -+ "joylessly", -+ "joyous", -+ "joyride", -+ "joystick", -+ "jubilance", -+ "jubilant", -+ "judge", -+ "judgingly", -+ "judicial", -+ "judiciary", -+ "judo", -+ "juggle", -+ "juggling", -+ "jugular", -+ "juice", -+ "juiciness", -+ "juicy", -+ "jujitsu", -+ "jukebox", -+ "july", -+ "jumble", -+ "jumbo", -+ "jump", -+ "junction", -+ "juncture", -+ "june", -+ "junior", -+ "juniper", -+ "junkie", -+ "junkman", -+ "junkyard", -+ "jurist", -+ "juror", -+ "jury", -+ "justice", -+ "justifier", -+ "justify", -+ "justly", -+ "justness", -+ "juvenile", -+ "kabob", -+ "kangaroo", -+ "karaoke", -+ "karate", -+ "karma", -+ "kebab", -+ "keenly", -+ "keenness", -+ "keep", -+ "keg", -+ "kelp", -+ "kennel", -+ "kept", -+ "kerchief", -+ "kerosene", -+ "kettle", -+ "kick", -+ "kiln", -+ "kilobyte", -+ "kilogram", -+ "kilometer", -+ "kilowatt", -+ "kilt", -+ "kimono", -+ "kindle", -+ "kindling", -+ "kindly", -+ "kindness", -+ "kindred", -+ "kinetic", -+ "kinfolk", -+ "king", -+ "kinship", -+ "kinsman", -+ "kinswoman", -+ "kissable", -+ "kisser", -+ "kissing", -+ "kitchen", -+ "kite", -+ "kitten", -+ "kitty", -+ "kiwi", -+ "kleenex", -+ "knapsack", -+ "knee", -+ "knelt", -+ "knickers", -+ "knoll", -+ "koala", -+ "kooky", -+ "kosher", -+ "krypton", -+ "kudos", -+ "kung", -+ "labored", -+ "laborer", -+ "laboring", -+ "laborious", -+ "labrador", -+ "ladder", -+ "ladies", -+ "ladle", -+ "ladybug", -+ "ladylike", -+ "lagged", -+ "lagging", -+ "lagoon", -+ "lair", -+ "lake", -+ "lance", -+ "landed", -+ "landfall", -+ "landfill", -+ "landing", -+ "landlady", -+ "landless", -+ "landline", -+ "landlord", -+ "landmark", -+ "landmass", -+ "landmine", -+ "landowner", -+ "landscape", -+ "landside", -+ "landslide", -+ "language", -+ "lankiness", -+ "lanky", -+ "lantern", -+ "lapdog", -+ "lapel", -+ "lapped", -+ "lapping", -+ "laptop", -+ "lard", -+ "large", -+ "lark", -+ "lash", -+ "lasso", -+ "last", -+ "latch", -+ "late", -+ "lather", -+ "latitude", -+ "latrine", -+ "latter", -+ "latticed", -+ "launch", -+ "launder", -+ "laundry", -+ "laurel", -+ "lavender", -+ "lavish", -+ "laxative", -+ "lazily", -+ "laziness", -+ "lazy", -+ "lecturer", -+ "left", -+ "legacy", -+ "legal", -+ "legend", -+ "legged", -+ "leggings", -+ "legible", -+ "legibly", -+ "legislate", -+ "lego", -+ "legroom", -+ "legume", -+ "legwarmer", -+ "legwork", -+ "lemon", -+ "lend", -+ "length", -+ "lens", -+ "lent", -+ "leotard", -+ "lesser", -+ "letdown", -+ "lethargic", -+ "lethargy", -+ "letter", -+ "lettuce", -+ "level", -+ "leverage", -+ "levers", -+ "levitate", -+ "levitator", -+ "liability", -+ "liable", -+ "liberty", -+ "librarian", -+ "library", -+ "licking", -+ "licorice", -+ "lid", -+ "life", -+ "lifter", -+ "lifting", -+ "liftoff", -+ "ligament", -+ "likely", -+ "likeness", -+ "likewise", -+ "liking", -+ "lilac", -+ "lilly", -+ "lily", -+ "limb", -+ "limeade", -+ "limelight", -+ "limes", -+ "limit", -+ "limping", -+ "limpness", -+ "line", -+ "lingo", -+ "linguini", -+ "linguist", -+ "lining", -+ "linked", -+ "linoleum", -+ "linseed", -+ "lint", -+ "lion", -+ "lip", -+ "liquefy", -+ "liqueur", -+ "liquid", -+ "lisp", -+ "list", -+ "litigate", -+ "litigator", -+ "litmus", -+ "litter", -+ "little", -+ "livable", -+ "lived", -+ "lively", -+ "liver", -+ "livestock", -+ "lividly", -+ "living", -+ "lizard", -+ "lubricant", -+ "lubricate", -+ "lucid", -+ "luckily", -+ "luckiness", -+ "luckless", -+ "lucrative", -+ "ludicrous", -+ "lugged", -+ "lukewarm", -+ "lullaby", -+ "lumber", -+ "luminance", -+ "luminous", -+ "lumpiness", -+ "lumping", -+ "lumpish", -+ "lunacy", -+ "lunar", -+ "lunchbox", -+ "luncheon", -+ "lunchroom", -+ "lunchtime", -+ "lung", -+ "lurch", -+ "lure", -+ "luridness", -+ "lurk", -+ "lushly", -+ "lushness", -+ "luster", -+ "lustfully", -+ "lustily", -+ "lustiness", -+ "lustrous", -+ "lusty", -+ "luxurious", -+ "luxury", -+ "lying", -+ "lyrically", -+ "lyricism", -+ "lyricist", -+ "lyrics", -+ "macarena", -+ "macaroni", -+ "macaw", -+ "mace", -+ "machine", -+ "machinist", -+ "magazine", -+ "magenta", -+ "maggot", -+ "magical", -+ "magician", -+ "magma", -+ "magnesium", -+ "magnetic", -+ "magnetism", -+ "magnetize", -+ "magnifier", -+ "magnify", -+ "magnitude", -+ "magnolia", -+ "mahogany", -+ "maimed", -+ "majestic", -+ "majesty", -+ "majorette", -+ "majority", -+ "makeover", -+ "maker", -+ "makeshift", -+ "making", -+ "malformed", -+ "malt", -+ "mama", -+ "mammal", -+ "mammary", -+ "mammogram", -+ "manager", -+ "managing", -+ "manatee", -+ "mandarin", -+ "mandate", -+ "mandatory", -+ "mandolin", -+ "manger", -+ "mangle", -+ "mango", -+ "mangy", -+ "manhandle", -+ "manhole", -+ "manhood", -+ "manhunt", -+ "manicotti", -+ "manicure", -+ "manifesto", -+ "manila", -+ "mankind", -+ "manlike", -+ "manliness", -+ "manly", -+ "manmade", -+ "manned", -+ "mannish", -+ "manor", -+ "manpower", -+ "mantis", -+ "mantra", -+ "manual", -+ "many", -+ "map", -+ "marathon", -+ "marauding", -+ "marbled", -+ "marbles", -+ "marbling", -+ "march", -+ "mardi", -+ "margarine", -+ "margarita", -+ "margin", -+ "marigold", -+ "marina", -+ "marine", -+ "marital", -+ "maritime", -+ "marlin", -+ "marmalade", -+ "maroon", -+ "married", -+ "marrow", -+ "marry", -+ "marshland", -+ "marshy", -+ "marsupial", -+ "marvelous", -+ "marxism", -+ "mascot", -+ "masculine", -+ "mashed", -+ "mashing", -+ "massager", -+ "masses", -+ "massive", -+ "mastiff", -+ "matador", -+ "matchbook", -+ "matchbox", -+ "matcher", -+ "matching", -+ "matchless", -+ "material", -+ "maternal", -+ "maternity", -+ "math", -+ "mating", -+ "matriarch", -+ "matrimony", -+ "matrix", -+ "matron", -+ "matted", -+ "matter", -+ "maturely", -+ "maturing", -+ "maturity", -+ "mauve", -+ "maverick", -+ "maximize", -+ "maximum", -+ "maybe", -+ "mayday", -+ "mayflower", -+ "moaner", -+ "moaning", -+ "mobile", -+ "mobility", -+ "mobilize", -+ "mobster", -+ "mocha", -+ "mocker", -+ "mockup", -+ "modified", -+ "modify", -+ "modular", -+ "modulator", -+ "module", -+ "moisten", -+ "moistness", -+ "moisture", -+ "molar", -+ "molasses", -+ "mold", -+ "molecular", -+ "molecule", -+ "molehill", -+ "mollusk", -+ "mom", -+ "monastery", -+ "monday", -+ "monetary", -+ "monetize", -+ "moneybags", -+ "moneyless", -+ "moneywise", -+ "mongoose", -+ "mongrel", -+ "monitor", -+ "monkhood", -+ "monogamy", -+ "monogram", -+ "monologue", -+ "monopoly", -+ "monorail", -+ "monotone", -+ "monotype", -+ "monoxide", -+ "monsieur", -+ "monsoon", -+ "monstrous", -+ "monthly", -+ "monument", -+ "moocher", -+ "moodiness", -+ "moody", -+ "mooing", -+ "moonbeam", -+ "mooned", -+ "moonlight", -+ "moonlike", -+ "moonlit", -+ "moonrise", -+ "moonscape", -+ "moonshine", -+ "moonstone", -+ "moonwalk", -+ "mop", -+ "morale", -+ "morality", -+ "morally", -+ "morbidity", -+ "morbidly", -+ "morphine", -+ "morphing", -+ "morse", -+ "mortality", -+ "mortally", -+ "mortician", -+ "mortified", -+ "mortify", -+ "mortuary", -+ "mosaic", -+ "mossy", -+ "most", -+ "mothball", -+ "mothproof", -+ "motion", -+ "motivate", -+ "motivator", -+ "motive", -+ "motocross", -+ "motor", -+ "motto", -+ "mountable", -+ "mountain", -+ "mounted", -+ "mounting", -+ "mourner", -+ "mournful", -+ "mouse", -+ "mousiness", -+ "moustache", -+ "mousy", -+ "mouth", -+ "movable", -+ "move", -+ "movie", -+ "moving", -+ "mower", -+ "mowing", -+ "much", -+ "muck", -+ "mud", -+ "mug", -+ "mulberry", -+ "mulch", -+ "mule", -+ "mulled", -+ "mullets", -+ "multiple", -+ "multiply", -+ "multitask", -+ "multitude", -+ "mumble", -+ "mumbling", -+ "mumbo", -+ "mummified", -+ "mummify", -+ "mummy", -+ "mumps", -+ "munchkin", -+ "mundane", -+ "municipal", -+ "muppet", -+ "mural", -+ "murkiness", -+ "murky", -+ "murmuring", -+ "muscular", -+ "museum", -+ "mushily", -+ "mushiness", -+ "mushroom", -+ "mushy", -+ "music", -+ "musket", -+ "muskiness", -+ "musky", -+ "mustang", -+ "mustard", -+ "muster", -+ "mustiness", -+ "musty", -+ "mutable", -+ "mutate", -+ "mutation", -+ "mute", -+ "mutilated", -+ "mutilator", -+ "mutiny", -+ "mutt", -+ "mutual", -+ "muzzle", -+ "myself", -+ "myspace", -+ "mystified", -+ "mystify", -+ "myth", -+ "nacho", -+ "nag", -+ "nail", -+ "name", -+ "naming", -+ "nanny", -+ "nanometer", -+ "nape", -+ "napkin", -+ "napped", -+ "napping", -+ "nappy", -+ "narrow", -+ "nastily", -+ "nastiness", -+ "national", -+ "native", -+ "nativity", -+ "natural", -+ "nature", -+ "naturist", -+ "nautical", -+ "navigate", -+ "navigator", -+ "navy", -+ "nearby", -+ "nearest", -+ "nearly", -+ "nearness", -+ "neatly", -+ "neatness", -+ "nebula", -+ "nebulizer", -+ "nectar", -+ "negate", -+ "negation", -+ "negative", -+ "neglector", -+ "negligee", -+ "negligent", -+ "negotiate", -+ "nemeses", -+ "nemesis", -+ "neon", -+ "nephew", -+ "nerd", -+ "nervous", -+ "nervy", -+ "nest", -+ "net", -+ "neurology", -+ "neuron", -+ "neurosis", -+ "neurotic", -+ "neuter", -+ "neutron", -+ "never", -+ "next", -+ "nibble", -+ "nickname", -+ "nicotine", -+ "niece", -+ "nifty", -+ "nimble", -+ "nimbly", -+ "nineteen", -+ "ninetieth", -+ "ninja", -+ "nintendo", -+ "ninth", -+ "nuclear", -+ "nuclei", -+ "nucleus", -+ "nugget", -+ "nullify", -+ "number", -+ "numbing", -+ "numbly", -+ "numbness", -+ "numeral", -+ "numerate", -+ "numerator", -+ "numeric", -+ "numerous", -+ "nuptials", -+ "nursery", -+ "nursing", -+ "nurture", -+ "nutcase", -+ "nutlike", -+ "nutmeg", -+ "nutrient", -+ "nutshell", -+ "nuttiness", -+ "nutty", -+ "nuzzle", -+ "nylon", -+ "oaf", -+ "oak", -+ "oasis", -+ "oat", -+ "obedience", -+ "obedient", -+ "obituary", -+ "object", -+ "obligate", -+ "obliged", -+ "oblivion", -+ "oblivious", -+ "oblong", -+ "obnoxious", -+ "oboe", -+ "obscure", -+ "obscurity", -+ "observant", -+ "observer", -+ "observing", -+ "obsessed", -+ "obsession", -+ "obsessive", -+ "obsolete", -+ "obstacle", -+ "obstinate", -+ "obstruct", -+ "obtain", -+ "obtrusive", -+ "obtuse", -+ "obvious", -+ "occultist", -+ "occupancy", -+ "occupant", -+ "occupier", -+ "occupy", -+ "ocean", -+ "ocelot", -+ "octagon", -+ "octane", -+ "october", -+ "octopus", -+ "ogle", -+ "oil", -+ "oink", -+ "ointment", -+ "okay", -+ "old", -+ "olive", -+ "olympics", -+ "omega", -+ "omen", -+ "ominous", -+ "omission", -+ "omit", -+ "omnivore", -+ "onboard", -+ "oncoming", -+ "ongoing", -+ "onion", -+ "online", -+ "onlooker", -+ "only", -+ "onscreen", -+ "onset", -+ "onshore", -+ "onslaught", -+ "onstage", -+ "onto", -+ "onward", -+ "onyx", -+ "oops", -+ "ooze", -+ "oozy", -+ "opacity", -+ "opal", -+ "open", -+ "operable", -+ "operate", -+ "operating", -+ "operation", -+ "operative", -+ "operator", -+ "opium", -+ "opossum", -+ "opponent", -+ "oppose", -+ "opposing", -+ "opposite", -+ "oppressed", -+ "oppressor", -+ "opt", -+ "opulently", -+ "osmosis", -+ "other", -+ "otter", -+ "ouch", -+ "ought", -+ "ounce", -+ "outage", -+ "outback", -+ "outbid", -+ "outboard", -+ "outbound", -+ "outbreak", -+ "outburst", -+ "outcast", -+ "outclass", -+ "outcome", -+ "outdated", -+ "outdoors", -+ "outer", -+ "outfield", -+ "outfit", -+ "outflank", -+ "outgoing", -+ "outgrow", -+ "outhouse", -+ "outing", -+ "outlast", -+ "outlet", -+ "outline", -+ "outlook", -+ "outlying", -+ "outmatch", -+ "outmost", -+ "outnumber", -+ "outplayed", -+ "outpost", -+ "outpour", -+ "output", -+ "outrage", -+ "outrank", -+ "outreach", -+ "outright", -+ "outscore", -+ "outsell", -+ "outshine", -+ "outshoot", -+ "outsider", -+ "outskirts", -+ "outsmart", -+ "outsource", -+ "outspoken", -+ "outtakes", -+ "outthink", -+ "outward", -+ "outweigh", -+ "outwit", -+ "oval", -+ "ovary", -+ "oven", -+ "overact", -+ "overall", -+ "overarch", -+ "overbid", -+ "overbill", -+ "overbite", -+ "overblown", -+ "overboard", -+ "overbook", -+ "overbuilt", -+ "overcast", -+ "overcoat", -+ "overcome", -+ "overcook", -+ "overcrowd", -+ "overdraft", -+ "overdrawn", -+ "overdress", -+ "overdrive", -+ "overdue", -+ "overeager", -+ "overeater", -+ "overexert", -+ "overfed", -+ "overfeed", -+ "overfill", -+ "overflow", -+ "overfull", -+ "overgrown", -+ "overhand", -+ "overhang", -+ "overhaul", -+ "overhead", -+ "overhear", -+ "overheat", -+ "overhung", -+ "overjoyed", -+ "overkill", -+ "overlabor", -+ "overlaid", -+ "overlap", -+ "overlay", -+ "overload", -+ "overlook", -+ "overlord", -+ "overlying", -+ "overnight", -+ "overpass", -+ "overpay", -+ "overplant", -+ "overplay", -+ "overpower", -+ "overprice", -+ "overrate", -+ "overreach", -+ "overreact", -+ "override", -+ "overripe", -+ "overrule", -+ "overrun", -+ "overshoot", -+ "overshot", -+ "oversight", -+ "oversized", -+ "oversleep", -+ "oversold", -+ "overspend", -+ "overstate", -+ "overstay", -+ "overstep", -+ "overstock", -+ "overstuff", -+ "oversweet", -+ "overtake", -+ "overthrow", -+ "overtime", -+ "overtly", -+ "overtone", -+ "overture", -+ "overturn", -+ "overuse", -+ "overvalue", -+ "overview", -+ "overwrite", -+ "owl", -+ "oxford", -+ "oxidant", -+ "oxidation", -+ "oxidize", -+ "oxidizing", -+ "oxygen", -+ "oxymoron", -+ "oyster", -+ "ozone", -+ "paced", -+ "pacemaker", -+ "pacific", -+ "pacifier", -+ "pacifism", -+ "pacifist", -+ "pacify", -+ "padded", -+ "padding", -+ "paddle", -+ "paddling", -+ "padlock", -+ "pagan", -+ "pager", -+ "paging", -+ "pajamas", -+ "palace", -+ "palatable", -+ "palm", -+ "palpable", -+ "palpitate", -+ "paltry", -+ "pampered", -+ "pamperer", -+ "pampers", -+ "pamphlet", -+ "panama", -+ "pancake", -+ "pancreas", -+ "panda", -+ "pandemic", -+ "pang", -+ "panhandle", -+ "panic", -+ "panning", -+ "panorama", -+ "panoramic", -+ "panther", -+ "pantomime", -+ "pantry", -+ "pants", -+ "pantyhose", -+ "paparazzi", -+ "papaya", -+ "paper", -+ "paprika", -+ "papyrus", -+ "parabola", -+ "parachute", -+ "parade", -+ "paradox", -+ "paragraph", -+ "parakeet", -+ "paralegal", -+ "paralyses", -+ "paralysis", -+ "paralyze", -+ "paramedic", -+ "parameter", -+ "paramount", -+ "parasail", -+ "parasite", -+ "parasitic", -+ "parcel", -+ "parched", -+ "parchment", -+ "pardon", -+ "parish", -+ "parka", -+ "parking", -+ "parkway", -+ "parlor", -+ "parmesan", -+ "parole", -+ "parrot", -+ "parsley", -+ "parsnip", -+ "partake", -+ "parted", -+ "parting", -+ "partition", -+ "partly", -+ "partner", -+ "partridge", -+ "party", -+ "passable", -+ "passably", -+ "passage", -+ "passcode", -+ "passenger", -+ "passerby", -+ "passing", -+ "passion", -+ "passive", -+ "passivism", -+ "passover", -+ "passport", -+ "password", -+ "pasta", -+ "pasted", -+ "pastel", -+ "pastime", -+ "pastor", -+ "pastrami", -+ "pasture", -+ "pasty", -+ "patchwork", -+ "patchy", -+ "paternal", -+ "paternity", -+ "path", -+ "patience", -+ "patient", -+ "patio", -+ "patriarch", -+ "patriot", -+ "patrol", -+ "patronage", -+ "patronize", -+ "pauper", -+ "pavement", -+ "paver", -+ "pavestone", -+ "pavilion", -+ "paving", -+ "pawing", -+ "payable", -+ "payback", -+ "paycheck", -+ "payday", -+ "payee", -+ "payer", -+ "paying", -+ "payment", -+ "payphone", -+ "payroll", -+ "pebble", -+ "pebbly", -+ "pecan", -+ "pectin", -+ "peculiar", -+ "peddling", -+ "pediatric", -+ "pedicure", -+ "pedigree", -+ "pedometer", -+ "pegboard", -+ "pelican", -+ "pellet", -+ "pelt", -+ "pelvis", -+ "penalize", -+ "penalty", -+ "pencil", -+ "pendant", -+ "pending", -+ "penholder", -+ "penknife", -+ "pennant", -+ "penniless", -+ "penny", -+ "penpal", -+ "pension", -+ "pentagon", -+ "pentagram", -+ "pep", -+ "perceive", -+ "percent", -+ "perch", -+ "percolate", -+ "perennial", -+ "perfected", -+ "perfectly", -+ "perfume", -+ "periscope", -+ "perish", -+ "perjurer", -+ "perjury", -+ "perkiness", -+ "perky", -+ "perm", -+ "peroxide", -+ "perpetual", -+ "perplexed", -+ "persecute", -+ "persevere", -+ "persuaded", -+ "persuader", -+ "pesky", -+ "peso", -+ "pessimism", -+ "pessimist", -+ "pester", -+ "pesticide", -+ "petal", -+ "petite", -+ "petition", -+ "petri", -+ "petroleum", -+ "petted", -+ "petticoat", -+ "pettiness", -+ "petty", -+ "petunia", -+ "phantom", -+ "phobia", -+ "phoenix", -+ "phonebook", -+ "phoney", -+ "phonics", -+ "phoniness", -+ "phony", -+ "phosphate", -+ "photo", -+ "phrase", -+ "phrasing", -+ "placard", -+ "placate", -+ "placidly", -+ "plank", -+ "planner", -+ "plant", -+ "plasma", -+ "plaster", -+ "plastic", -+ "plated", -+ "platform", -+ "plating", -+ "platinum", -+ "platonic", -+ "platter", -+ "platypus", -+ "plausible", -+ "plausibly", -+ "playable", -+ "playback", -+ "player", -+ "playful", -+ "playgroup", -+ "playhouse", -+ "playing", -+ "playlist", -+ "playmaker", -+ "playmate", -+ "playoff", -+ "playpen", -+ "playroom", -+ "playset", -+ "plaything", -+ "playtime", -+ "plaza", -+ "pleading", -+ "pleat", -+ "pledge", -+ "plentiful", -+ "plenty", -+ "plethora", -+ "plexiglas", -+ "pliable", -+ "plod", -+ "plop", -+ "plot", -+ "plow", -+ "ploy", -+ "pluck", -+ "plug", -+ "plunder", -+ "plunging", -+ "plural", -+ "plus", -+ "plutonium", -+ "plywood", -+ "poach", -+ "pod", -+ "poem", -+ "poet", -+ "pogo", -+ "pointed", -+ "pointer", -+ "pointing", -+ "pointless", -+ "pointy", -+ "poise", -+ "poison", -+ "poker", -+ "poking", -+ "polar", -+ "police", -+ "policy", -+ "polio", -+ "polish", -+ "politely", -+ "polka", -+ "polo", -+ "polyester", -+ "polygon", -+ "polygraph", -+ "polymer", -+ "poncho", -+ "pond", -+ "pony", -+ "popcorn", -+ "pope", -+ "poplar", -+ "popper", -+ "poppy", -+ "popsicle", -+ "populace", -+ "popular", -+ "populate", -+ "porcupine", -+ "pork", -+ "porous", -+ "porridge", -+ "portable", -+ "portal", -+ "portfolio", -+ "porthole", -+ "portion", -+ "portly", -+ "portside", -+ "poser", -+ "posh", -+ "posing", -+ "possible", -+ "possibly", -+ "possum", -+ "postage", -+ "postal", -+ "postbox", -+ "postcard", -+ "posted", -+ "poster", -+ "posting", -+ "postnasal", -+ "posture", -+ "postwar", -+ "pouch", -+ "pounce", -+ "pouncing", -+ "pound", -+ "pouring", -+ "pout", -+ "powdered", -+ "powdering", -+ "powdery", -+ "power", -+ "powwow", -+ "pox", -+ "praising", -+ "prance", -+ "prancing", -+ "pranker", -+ "prankish", -+ "prankster", -+ "prayer", -+ "praying", -+ "preacher", -+ "preaching", -+ "preachy", -+ "preamble", -+ "precinct", -+ "precise", -+ "precision", -+ "precook", -+ "precut", -+ "predator", -+ "predefine", -+ "predict", -+ "preface", -+ "prefix", -+ "preflight", -+ "preformed", -+ "pregame", -+ "pregnancy", -+ "pregnant", -+ "preheated", -+ "prelaunch", -+ "prelaw", -+ "prelude", -+ "premiere", -+ "premises", -+ "premium", -+ "prenatal", -+ "preoccupy", -+ "preorder", -+ "prepaid", -+ "prepay", -+ "preplan", -+ "preppy", -+ "preschool", -+ "prescribe", -+ "preseason", -+ "preset", -+ "preshow", -+ "president", -+ "presoak", -+ "press", -+ "presume", -+ "presuming", -+ "preteen", -+ "pretended", -+ "pretender", -+ "pretense", -+ "pretext", -+ "pretty", -+ "pretzel", -+ "prevail", -+ "prevalent", -+ "prevent", -+ "preview", -+ "previous", -+ "prewar", -+ "prewashed", -+ "prideful", -+ "pried", -+ "primal", -+ "primarily", -+ "primary", -+ "primate", -+ "primer", -+ "primp", -+ "princess", -+ "print", -+ "prior", -+ "prism", -+ "prison", -+ "prissy", -+ "pristine", -+ "privacy", -+ "private", -+ "privatize", -+ "prize", -+ "proactive", -+ "probable", -+ "probably", -+ "probation", -+ "probe", -+ "probing", -+ "probiotic", -+ "problem", -+ "procedure", -+ "process", -+ "proclaim", -+ "procreate", -+ "procurer", -+ "prodigal", -+ "prodigy", -+ "produce", -+ "product", -+ "profane", -+ "profanity", -+ "professed", -+ "professor", -+ "profile", -+ "profound", -+ "profusely", -+ "progeny", -+ "prognosis", -+ "program", -+ "progress", -+ "projector", -+ "prologue", -+ "prolonged", -+ "promenade", -+ "prominent", -+ "promoter", -+ "promotion", -+ "prompter", -+ "promptly", -+ "prone", -+ "prong", -+ "pronounce", -+ "pronto", -+ "proofing", -+ "proofread", -+ "proofs", -+ "propeller", -+ "properly", -+ "property", -+ "proponent", -+ "proposal", -+ "propose", -+ "props", -+ "prorate", -+ "protector", -+ "protegee", -+ "proton", -+ "prototype", -+ "protozoan", -+ "protract", -+ "protrude", -+ "proud", -+ "provable", -+ "proved", -+ "proven", -+ "provided", -+ "provider", -+ "providing", -+ "province", -+ "proving", -+ "provoke", -+ "provoking", -+ "provolone", -+ "prowess", -+ "prowler", -+ "prowling", -+ "proximity", -+ "proxy", -+ "prozac", -+ "prude", -+ "prudishly", -+ "prune", -+ "pruning", -+ "pry", -+ "psychic", -+ "public", -+ "publisher", -+ "pucker", -+ "pueblo", -+ "pug", -+ "pull", -+ "pulmonary", -+ "pulp", -+ "pulsate", -+ "pulse", -+ "pulverize", -+ "puma", -+ "pumice", -+ "pummel", -+ "punch", -+ "punctual", -+ "punctuate", -+ "punctured", -+ "pungent", -+ "punisher", -+ "punk", -+ "pupil", -+ "puppet", -+ "puppy", -+ "purchase", -+ "pureblood", -+ "purebred", -+ "purely", -+ "pureness", -+ "purgatory", -+ "purge", -+ "purging", -+ "purifier", -+ "purify", -+ "purist", -+ "puritan", -+ "purity", -+ "purple", -+ "purplish", -+ "purposely", -+ "purr", -+ "purse", -+ "pursuable", -+ "pursuant", -+ "pursuit", -+ "purveyor", -+ "pushcart", -+ "pushchair", -+ "pusher", -+ "pushiness", -+ "pushing", -+ "pushover", -+ "pushpin", -+ "pushup", -+ "pushy", -+ "putdown", -+ "putt", -+ "puzzle", -+ "puzzling", -+ "pyramid", -+ "pyromania", -+ "python", -+ "quack", -+ "quadrant", -+ "quail", -+ "quaintly", -+ "quake", -+ "quaking", -+ "qualified", -+ "qualifier", -+ "qualify", -+ "quality", -+ "qualm", -+ "quantum", -+ "quarrel", -+ "quarry", -+ "quartered", -+ "quarterly", -+ "quarters", -+ "quartet", -+ "quench", -+ "query", -+ "quicken", -+ "quickly", -+ "quickness", -+ "quicksand", -+ "quickstep", -+ "quiet", -+ "quill", -+ "quilt", -+ "quintet", -+ "quintuple", -+ "quirk", -+ "quit", -+ "quiver", -+ "quizzical", -+ "quotable", -+ "quotation", -+ "quote", -+ "rabid", -+ "race", -+ "racing", -+ "racism", -+ "rack", -+ "racoon", -+ "radar", -+ "radial", -+ "radiance", -+ "radiantly", -+ "radiated", -+ "radiation", -+ "radiator", -+ "radio", -+ "radish", -+ "raffle", -+ "raft", -+ "rage", -+ "ragged", -+ "raging", -+ "ragweed", -+ "raider", -+ "railcar", -+ "railing", -+ "railroad", -+ "railway", -+ "raisin", -+ "rake", -+ "raking", -+ "rally", -+ "ramble", -+ "rambling", -+ "ramp", -+ "ramrod", -+ "ranch", -+ "rancidity", -+ "random", -+ "ranged", -+ "ranger", -+ "ranging", -+ "ranked", -+ "ranking", -+ "ransack", -+ "ranting", -+ "rants", -+ "rare", -+ "rarity", -+ "rascal", -+ "rash", -+ "rasping", -+ "ravage", -+ "raven", -+ "ravine", -+ "raving", -+ "ravioli", -+ "ravishing", -+ "reabsorb", -+ "reach", -+ "reacquire", -+ "reaction", -+ "reactive", -+ "reactor", -+ "reaffirm", -+ "ream", -+ "reanalyze", -+ "reappear", -+ "reapply", -+ "reappoint", -+ "reapprove", -+ "rearrange", -+ "rearview", -+ "reason", -+ "reassign", -+ "reassure", -+ "reattach", -+ "reawake", -+ "rebalance", -+ "rebate", -+ "rebel", -+ "rebirth", -+ "reboot", -+ "reborn", -+ "rebound", -+ "rebuff", -+ "rebuild", -+ "rebuilt", -+ "reburial", -+ "rebuttal", -+ "recall", -+ "recant", -+ "recapture", -+ "recast", -+ "recede", -+ "recent", -+ "recess", -+ "recharger", -+ "recipient", -+ "recital", -+ "recite", -+ "reckless", -+ "reclaim", -+ "recliner", -+ "reclining", -+ "recluse", -+ "reclusive", -+ "recognize", -+ "recoil", -+ "recollect", -+ "recolor", -+ "reconcile", -+ "reconfirm", -+ "reconvene", -+ "recopy", -+ "record", -+ "recount", -+ "recoup", -+ "recovery", -+ "recreate", -+ "rectal", -+ "rectangle", -+ "rectified", -+ "rectify", -+ "recycled", -+ "recycler", -+ "recycling", -+ "reemerge", -+ "reenact", -+ "reenter", -+ "reentry", -+ "reexamine", -+ "referable", -+ "referee", -+ "reference", -+ "refill", -+ "refinance", -+ "refined", -+ "refinery", -+ "refining", -+ "refinish", -+ "reflected", -+ "reflector", -+ "reflex", -+ "reflux", -+ "refocus", -+ "refold", -+ "reforest", -+ "reformat", -+ "reformed", -+ "reformer", -+ "reformist", -+ "refract", -+ "refrain", -+ "refreeze", -+ "refresh", -+ "refried", -+ "refueling", -+ "refund", -+ "refurbish", -+ "refurnish", -+ "refusal", -+ "refuse", -+ "refusing", -+ "refutable", -+ "refute", -+ "regain", -+ "regalia", -+ "regally", -+ "reggae", -+ "regime", -+ "region", -+ "register", -+ "registrar", -+ "registry", -+ "regress", -+ "regretful", -+ "regroup", -+ "regular", -+ "regulate", -+ "regulator", -+ "rehab", -+ "reheat", -+ "rehire", -+ "rehydrate", -+ "reimburse", -+ "reissue", -+ "reiterate", -+ "rejoice", -+ "rejoicing", -+ "rejoin", -+ "rekindle", -+ "relapse", -+ "relapsing", -+ "relatable", -+ "related", -+ "relation", -+ "relative", -+ "relax", -+ "relay", -+ "relearn", -+ "release", -+ "relenting", -+ "reliable", -+ "reliably", -+ "reliance", -+ "reliant", -+ "relic", -+ "relieve", -+ "relieving", -+ "relight", -+ "relish", -+ "relive", -+ "reload", -+ "relocate", -+ "relock", -+ "reluctant", -+ "rely", -+ "remake", -+ "remark", -+ "remarry", -+ "rematch", -+ "remedial", -+ "remedy", -+ "remember", -+ "reminder", -+ "remindful", -+ "remission", -+ "remix", -+ "remnant", -+ "remodeler", -+ "remold", -+ "remorse", -+ "remote", -+ "removable", -+ "removal", -+ "removed", -+ "remover", -+ "removing", -+ "rename", -+ "renderer", -+ "rendering", -+ "rendition", -+ "renegade", -+ "renewable", -+ "renewably", -+ "renewal", -+ "renewed", -+ "renounce", -+ "renovate", -+ "renovator", -+ "rentable", -+ "rental", -+ "rented", -+ "renter", -+ "reoccupy", -+ "reoccur", -+ "reopen", -+ "reorder", -+ "repackage", -+ "repacking", -+ "repaint", -+ "repair", -+ "repave", -+ "repaying", -+ "repayment", -+ "repeal", -+ "repeated", -+ "repeater", -+ "repent", -+ "rephrase", -+ "replace", -+ "replay", -+ "replica", -+ "reply", -+ "reporter", -+ "repose", -+ "repossess", -+ "repost", -+ "repressed", -+ "reprimand", -+ "reprint", -+ "reprise", -+ "reproach", -+ "reprocess", -+ "reproduce", -+ "reprogram", -+ "reps", -+ "reptile", -+ "reptilian", -+ "repugnant", -+ "repulsion", -+ "repulsive", -+ "repurpose", -+ "reputable", -+ "reputably", -+ "request", -+ "require", -+ "requisite", -+ "reroute", -+ "rerun", -+ "resale", -+ "resample", -+ "rescuer", -+ "reseal", -+ "research", -+ "reselect", -+ "reseller", -+ "resemble", -+ "resend", -+ "resent", -+ "reset", -+ "reshape", -+ "reshoot", -+ "reshuffle", -+ "residence", -+ "residency", -+ "resident", -+ "residual", -+ "residue", -+ "resigned", -+ "resilient", -+ "resistant", -+ "resisting", -+ "resize", -+ "resolute", -+ "resolved", -+ "resonant", -+ "resonate", -+ "resort", -+ "resource", -+ "respect", -+ "resubmit", -+ "result", -+ "resume", -+ "resupply", -+ "resurface", -+ "resurrect", -+ "retail", -+ "retainer", -+ "retaining", -+ "retake", -+ "retaliate", -+ "retention", -+ "rethink", -+ "retinal", -+ "retired", -+ "retiree", -+ "retiring", -+ "retold", -+ "retool", -+ "retorted", -+ "retouch", -+ "retrace", -+ "retract", -+ "retrain", -+ "retread", -+ "retreat", -+ "retrial", -+ "retrieval", -+ "retriever", -+ "retry", -+ "return", -+ "retying", -+ "retype", -+ "reunion", -+ "reunite", -+ "reusable", -+ "reuse", -+ "reveal", -+ "reveler", -+ "revenge", -+ "revenue", -+ "reverb", -+ "revered", -+ "reverence", -+ "reverend", -+ "reversal", -+ "reverse", -+ "reversing", -+ "reversion", -+ "revert", -+ "revisable", -+ "revise", -+ "revision", -+ "revisit", -+ "revivable", -+ "revival", -+ "reviver", -+ "reviving", -+ "revocable", -+ "revoke", -+ "revolt", -+ "revolver", -+ "revolving", -+ "reward", -+ "rewash", -+ "rewind", -+ "rewire", -+ "reword", -+ "rework", -+ "rewrap", -+ "rewrite", -+ "rhyme", -+ "ribbon", -+ "ribcage", -+ "rice", -+ "riches", -+ "richly", -+ "richness", -+ "rickety", -+ "ricotta", -+ "riddance", -+ "ridden", -+ "ride", -+ "riding", -+ "rifling", -+ "rift", -+ "rigging", -+ "rigid", -+ "rigor", -+ "rimless", -+ "rimmed", -+ "rind", -+ "rink", -+ "rinse", -+ "rinsing", -+ "riot", -+ "ripcord", -+ "ripeness", -+ "ripening", -+ "ripping", -+ "ripple", -+ "rippling", -+ "riptide", -+ "rise", -+ "rising", -+ "risk", -+ "risotto", -+ "ritalin", -+ "ritzy", -+ "rival", -+ "riverbank", -+ "riverbed", -+ "riverboat", -+ "riverside", -+ "riveter", -+ "riveting", -+ "roamer", -+ "roaming", -+ "roast", -+ "robbing", -+ "robe", -+ "robin", -+ "robotics", -+ "robust", -+ "rockband", -+ "rocker", -+ "rocket", -+ "rockfish", -+ "rockiness", -+ "rocking", -+ "rocklike", -+ "rockslide", -+ "rockstar", -+ "rocky", -+ "rogue", -+ "roman", -+ "romp", -+ "rope", -+ "roping", -+ "roster", -+ "rosy", -+ "rotten", -+ "rotting", -+ "rotunda", -+ "roulette", -+ "rounding", -+ "roundish", -+ "roundness", -+ "roundup", -+ "roundworm", -+ "routine", -+ "routing", -+ "rover", -+ "roving", -+ "royal", -+ "rubbed", -+ "rubber", -+ "rubbing", -+ "rubble", -+ "rubdown", -+ "ruby", -+ "ruckus", -+ "rudder", -+ "rug", -+ "ruined", -+ "rule", -+ "rumble", -+ "rumbling", -+ "rummage", -+ "rumor", -+ "runaround", -+ "rundown", -+ "runner", -+ "running", -+ "runny", -+ "runt", -+ "runway", -+ "rupture", -+ "rural", -+ "ruse", -+ "rush", -+ "rust", -+ "rut", -+ "sabbath", -+ "sabotage", -+ "sacrament", -+ "sacred", -+ "sacrifice", -+ "sadden", -+ "saddlebag", -+ "saddled", -+ "saddling", -+ "sadly", -+ "sadness", -+ "safari", -+ "safeguard", -+ "safehouse", -+ "safely", -+ "safeness", -+ "saffron", -+ "saga", -+ "sage", -+ "sagging", -+ "saggy", -+ "said", -+ "saint", -+ "sake", -+ "salad", -+ "salami", -+ "salaried", -+ "salary", -+ "saline", -+ "salon", -+ "saloon", -+ "salsa", -+ "salt", -+ "salutary", -+ "salute", -+ "salvage", -+ "salvaging", -+ "salvation", -+ "same", -+ "sample", -+ "sampling", -+ "sanction", -+ "sanctity", -+ "sanctuary", -+ "sandal", -+ "sandbag", -+ "sandbank", -+ "sandbar", -+ "sandblast", -+ "sandbox", -+ "sanded", -+ "sandfish", -+ "sanding", -+ "sandlot", -+ "sandpaper", -+ "sandpit", -+ "sandstone", -+ "sandstorm", -+ "sandworm", -+ "sandy", -+ "sanitary", -+ "sanitizer", -+ "sank", -+ "santa", -+ "sapling", -+ "sappiness", -+ "sappy", -+ "sarcasm", -+ "sarcastic", -+ "sardine", -+ "sash", -+ "sasquatch", -+ "sassy", -+ "satchel", -+ "satiable", -+ "satin", -+ "satirical", -+ "satisfied", -+ "satisfy", -+ "saturate", -+ "saturday", -+ "sauciness", -+ "saucy", -+ "sauna", -+ "savage", -+ "savanna", -+ "saved", -+ "savings", -+ "savior", -+ "savor", -+ "saxophone", -+ "say", -+ "scabbed", -+ "scabby", -+ "scalded", -+ "scalding", -+ "scale", -+ "scaling", -+ "scallion", -+ "scallop", -+ "scalping", -+ "scam", -+ "scandal", -+ "scanner", -+ "scanning", -+ "scant", -+ "scapegoat", -+ "scarce", -+ "scarcity", -+ "scarecrow", -+ "scared", -+ "scarf", -+ "scarily", -+ "scariness", -+ "scarring", -+ "scary", -+ "scavenger", -+ "scenic", -+ "schedule", -+ "schematic", -+ "scheme", -+ "scheming", -+ "schilling", -+ "schnapps", -+ "scholar", -+ "science", -+ "scientist", -+ "scion", -+ "scoff", -+ "scolding", -+ "scone", -+ "scoop", -+ "scooter", -+ "scope", -+ "scorch", -+ "scorebook", -+ "scorecard", -+ "scored", -+ "scoreless", -+ "scorer", -+ "scoring", -+ "scorn", -+ "scorpion", -+ "scotch", -+ "scoundrel", -+ "scoured", -+ "scouring", -+ "scouting", -+ "scouts", -+ "scowling", -+ "scrabble", -+ "scraggly", -+ "scrambled", -+ "scrambler", -+ "scrap", -+ "scratch", -+ "scrawny", -+ "screen", -+ "scribble", -+ "scribe", -+ "scribing", -+ "scrimmage", -+ "script", -+ "scroll", -+ "scrooge", -+ "scrounger", -+ "scrubbed", -+ "scrubber", -+ "scruffy", -+ "scrunch", -+ "scrutiny", -+ "scuba", -+ "scuff", -+ "sculptor", -+ "sculpture", -+ "scurvy", -+ "scuttle", -+ "secluded", -+ "secluding", -+ "seclusion", -+ "second", -+ "secrecy", -+ "secret", -+ "sectional", -+ "sector", -+ "secular", -+ "securely", -+ "security", -+ "sedan", -+ "sedate", -+ "sedation", -+ "sedative", -+ "sediment", -+ "seduce", -+ "seducing", -+ "segment", -+ "seismic", -+ "seizing", -+ "seldom", -+ "selected", -+ "selection", -+ "selective", -+ "selector", -+ "self", -+ "seltzer", -+ "semantic", -+ "semester", -+ "semicolon", -+ "semifinal", -+ "seminar", -+ "semisoft", -+ "semisweet", -+ "senate", -+ "senator", -+ "send", -+ "senior", -+ "senorita", -+ "sensation", -+ "sensitive", -+ "sensitize", -+ "sensually", -+ "sensuous", -+ "sepia", -+ "september", -+ "septic", -+ "septum", -+ "sequel", -+ "sequence", -+ "sequester", -+ "series", -+ "sermon", -+ "serotonin", -+ "serpent", -+ "serrated", -+ "serve", -+ "service", -+ "serving", -+ "sesame", -+ "sessions", -+ "setback", -+ "setting", -+ "settle", -+ "settling", -+ "setup", -+ "sevenfold", -+ "seventeen", -+ "seventh", -+ "seventy", -+ "severity", -+ "shabby", -+ "shack", -+ "shaded", -+ "shadily", -+ "shadiness", -+ "shading", -+ "shadow", -+ "shady", -+ "shaft", -+ "shakable", -+ "shakily", -+ "shakiness", -+ "shaking", -+ "shaky", -+ "shale", -+ "shallot", -+ "shallow", -+ "shame", -+ "shampoo", -+ "shamrock", -+ "shank", -+ "shanty", -+ "shape", -+ "shaping", -+ "share", -+ "sharpener", -+ "sharper", -+ "sharpie", -+ "sharply", -+ "sharpness", -+ "shawl", -+ "sheath", -+ "shed", -+ "sheep", -+ "sheet", -+ "shelf", -+ "shell", -+ "shelter", -+ "shelve", -+ "shelving", -+ "sherry", -+ "shield", -+ "shifter", -+ "shifting", -+ "shiftless", -+ "shifty", -+ "shimmer", -+ "shimmy", -+ "shindig", -+ "shine", -+ "shingle", -+ "shininess", -+ "shining", -+ "shiny", -+ "ship", -+ "shirt", -+ "shivering", -+ "shock", -+ "shone", -+ "shoplift", -+ "shopper", -+ "shopping", -+ "shoptalk", -+ "shore", -+ "shortage", -+ "shortcake", -+ "shortcut", -+ "shorten", -+ "shorter", -+ "shorthand", -+ "shortlist", -+ "shortly", -+ "shortness", -+ "shorts", -+ "shortwave", -+ "shorty", -+ "shout", -+ "shove", -+ "showbiz", -+ "showcase", -+ "showdown", -+ "shower", -+ "showgirl", -+ "showing", -+ "showman", -+ "shown", -+ "showoff", -+ "showpiece", -+ "showplace", -+ "showroom", -+ "showy", -+ "shrank", -+ "shrapnel", -+ "shredder", -+ "shredding", -+ "shrewdly", -+ "shriek", -+ "shrill", -+ "shrimp", -+ "shrine", -+ "shrink", -+ "shrivel", -+ "shrouded", -+ "shrubbery", -+ "shrubs", -+ "shrug", -+ "shrunk", -+ "shucking", -+ "shudder", -+ "shuffle", -+ "shuffling", -+ "shun", -+ "shush", -+ "shut", -+ "shy", -+ "siamese", -+ "siberian", -+ "sibling", -+ "siding", -+ "sierra", -+ "siesta", -+ "sift", -+ "sighing", -+ "silenced", -+ "silencer", -+ "silent", -+ "silica", -+ "silicon", -+ "silk", -+ "silliness", -+ "silly", -+ "silo", -+ "silt", -+ "silver", -+ "similarly", -+ "simile", -+ "simmering", -+ "simple", -+ "simplify", -+ "simply", -+ "sincere", -+ "sincerity", -+ "singer", -+ "singing", -+ "single", -+ "singular", -+ "sinister", -+ "sinless", -+ "sinner", -+ "sinuous", -+ "sip", -+ "siren", -+ "sister", -+ "sitcom", -+ "sitter", -+ "sitting", -+ "situated", -+ "situation", -+ "sixfold", -+ "sixteen", -+ "sixth", -+ "sixties", -+ "sixtieth", -+ "sixtyfold", -+ "sizable", -+ "sizably", -+ "size", -+ "sizing", -+ "sizzle", -+ "sizzling", -+ "skater", -+ "skating", -+ "skedaddle", -+ "skeletal", -+ "skeleton", -+ "skeptic", -+ "sketch", -+ "skewed", -+ "skewer", -+ "skid", -+ "skied", -+ "skier", -+ "skies", -+ "skiing", -+ "skilled", -+ "skillet", -+ "skillful", -+ "skimmed", -+ "skimmer", -+ "skimming", -+ "skimpily", -+ "skincare", -+ "skinhead", -+ "skinless", -+ "skinning", -+ "skinny", -+ "skintight", -+ "skipper", -+ "skipping", -+ "skirmish", -+ "skirt", -+ "skittle", -+ "skydiver", -+ "skylight", -+ "skyline", -+ "skype", -+ "skyrocket", -+ "skyward", -+ "slab", -+ "slacked", -+ "slacker", -+ "slacking", -+ "slackness", -+ "slacks", -+ "slain", -+ "slam", -+ "slander", -+ "slang", -+ "slapping", -+ "slapstick", -+ "slashed", -+ "slashing", -+ "slate", -+ "slather", -+ "slaw", -+ "sled", -+ "sleek", -+ "sleep", -+ "sleet", -+ "sleeve", -+ "slept", -+ "sliceable", -+ "sliced", -+ "slicer", -+ "slicing", -+ "slick", -+ "slider", -+ "slideshow", -+ "sliding", -+ "slighted", -+ "slighting", -+ "slightly", -+ "slimness", -+ "slimy", -+ "slinging", -+ "slingshot", -+ "slinky", -+ "slip", -+ "slit", -+ "sliver", -+ "slobbery", -+ "slogan", -+ "sloped", -+ "sloping", -+ "sloppily", -+ "sloppy", -+ "slot", -+ "slouching", -+ "slouchy", -+ "sludge", -+ "slug", -+ "slum", -+ "slurp", -+ "slush", -+ "sly", -+ "small", -+ "smartly", -+ "smartness", -+ "smasher", -+ "smashing", -+ "smashup", -+ "smell", -+ "smelting", -+ "smile", -+ "smilingly", -+ "smirk", -+ "smite", -+ "smith", -+ "smitten", -+ "smock", -+ "smog", -+ "smoked", -+ "smokeless", -+ "smokiness", -+ "smoking", -+ "smoky", -+ "smolder", -+ "smooth", -+ "smother", -+ "smudge", -+ "smudgy", -+ "smuggler", -+ "smuggling", -+ "smugly", -+ "smugness", -+ "snack", -+ "snagged", -+ "snaking", -+ "snap", -+ "snare", -+ "snarl", -+ "snazzy", -+ "sneak", -+ "sneer", -+ "sneeze", -+ "sneezing", -+ "snide", -+ "sniff", -+ "snippet", -+ "snipping", -+ "snitch", -+ "snooper", -+ "snooze", -+ "snore", -+ "snoring", -+ "snorkel", -+ "snort", -+ "snout", -+ "snowbird", -+ "snowboard", -+ "snowbound", -+ "snowcap", -+ "snowdrift", -+ "snowdrop", -+ "snowfall", -+ "snowfield", -+ "snowflake", -+ "snowiness", -+ "snowless", -+ "snowman", -+ "snowplow", -+ "snowshoe", -+ "snowstorm", -+ "snowsuit", -+ "snowy", -+ "snub", -+ "snuff", -+ "snuggle", -+ "snugly", -+ "snugness", -+ "speak", -+ "spearfish", -+ "spearhead", -+ "spearman", -+ "spearmint", -+ "species", -+ "specimen", -+ "specked", -+ "speckled", -+ "specks", -+ "spectacle", -+ "spectator", -+ "spectrum", -+ "speculate", -+ "speech", -+ "speed", -+ "spellbind", -+ "speller", -+ "spelling", -+ "spendable", -+ "spender", -+ "spending", -+ "spent", -+ "spew", -+ "sphere", -+ "spherical", -+ "sphinx", -+ "spider", -+ "spied", -+ "spiffy", -+ "spill", -+ "spilt", -+ "spinach", -+ "spinal", -+ "spindle", -+ "spinner", -+ "spinning", -+ "spinout", -+ "spinster", -+ "spiny", -+ "spiral", -+ "spirited", -+ "spiritism", -+ "spirits", -+ "spiritual", -+ "splashed", -+ "splashing", -+ "splashy", -+ "splatter", -+ "spleen", -+ "splendid", -+ "splendor", -+ "splice", -+ "splicing", -+ "splinter", -+ "splotchy", -+ "splurge", -+ "spoilage", -+ "spoiled", -+ "spoiler", -+ "spoiling", -+ "spoils", -+ "spoken", -+ "spokesman", -+ "sponge", -+ "spongy", -+ "sponsor", -+ "spoof", -+ "spookily", -+ "spooky", -+ "spool", -+ "spoon", -+ "spore", -+ "sporting", -+ "sports", -+ "sporty", -+ "spotless", -+ "spotlight", -+ "spotted", -+ "spotter", -+ "spotting", -+ "spotty", -+ "spousal", -+ "spouse", -+ "spout", -+ "sprain", -+ "sprang", -+ "sprawl", -+ "spray", -+ "spree", -+ "sprig", -+ "spring", -+ "sprinkled", -+ "sprinkler", -+ "sprint", -+ "sprite", -+ "sprout", -+ "spruce", -+ "sprung", -+ "spry", -+ "spud", -+ "spur", -+ "sputter", -+ "spyglass", -+ "squabble", -+ "squad", -+ "squall", -+ "squander", -+ "squash", -+ "squatted", -+ "squatter", -+ "squatting", -+ "squeak", -+ "squealer", -+ "squealing", -+ "squeamish", -+ "squeegee", -+ "squeeze", -+ "squeezing", -+ "squid", -+ "squiggle", -+ "squiggly", -+ "squint", -+ "squire", -+ "squirt", -+ "squishier", -+ "squishy", -+ "stability", -+ "stabilize", -+ "stable", -+ "stack", -+ "stadium", -+ "staff", -+ "stage", -+ "staging", -+ "stagnant", -+ "stagnate", -+ "stainable", -+ "stained", -+ "staining", -+ "stainless", -+ "stalemate", -+ "staleness", -+ "stalling", -+ "stallion", -+ "stamina", -+ "stammer", -+ "stamp", -+ "stand", -+ "stank", -+ "staple", -+ "stapling", -+ "starboard", -+ "starch", -+ "stardom", -+ "stardust", -+ "starfish", -+ "stargazer", -+ "staring", -+ "stark", -+ "starless", -+ "starlet", -+ "starlight", -+ "starlit", -+ "starring", -+ "starry", -+ "starship", -+ "starter", -+ "starting", -+ "startle", -+ "startling", -+ "startup", -+ "starved", -+ "starving", -+ "stash", -+ "state", -+ "static", -+ "statistic", -+ "statue", -+ "stature", -+ "status", -+ "statute", -+ "statutory", -+ "staunch", -+ "stays", -+ "steadfast", -+ "steadier", -+ "steadily", -+ "steadying", -+ "steam", -+ "steed", -+ "steep", -+ "steerable", -+ "steering", -+ "steersman", -+ "stegosaur", -+ "stellar", -+ "stem", -+ "stench", -+ "stencil", -+ "step", -+ "stereo", -+ "sterile", -+ "sterility", -+ "sterilize", -+ "sterling", -+ "sternness", -+ "sternum", -+ "stew", -+ "stick", -+ "stiffen", -+ "stiffly", -+ "stiffness", -+ "stifle", -+ "stifling", -+ "stillness", -+ "stilt", -+ "stimulant", -+ "stimulate", -+ "stimuli", -+ "stimulus", -+ "stinger", -+ "stingily", -+ "stinging", -+ "stingray", -+ "stingy", -+ "stinking", -+ "stinky", -+ "stipend", -+ "stipulate", -+ "stir", -+ "stitch", -+ "stock", -+ "stoic", -+ "stoke", -+ "stole", -+ "stomp", -+ "stonewall", -+ "stoneware", -+ "stonework", -+ "stoning", -+ "stony", -+ "stood", -+ "stooge", -+ "stool", -+ "stoop", -+ "stoplight", -+ "stoppable", -+ "stoppage", -+ "stopped", -+ "stopper", -+ "stopping", -+ "stopwatch", -+ "storable", -+ "storage", -+ "storeroom", -+ "storewide", -+ "storm", -+ "stout", -+ "stove", -+ "stowaway", -+ "stowing", -+ "straddle", -+ "straggler", -+ "strained", -+ "strainer", -+ "straining", -+ "strangely", -+ "stranger", -+ "strangle", -+ "strategic", -+ "strategy", -+ "stratus", -+ "straw", -+ "stray", -+ "streak", -+ "stream", -+ "street", -+ "strength", -+ "strenuous", -+ "strep", -+ "stress", -+ "stretch", -+ "strewn", -+ "stricken", -+ "strict", -+ "stride", -+ "strife", -+ "strike", -+ "striking", -+ "strive", -+ "striving", -+ "strobe", -+ "strode", -+ "stroller", -+ "strongbox", -+ "strongly", -+ "strongman", -+ "struck", -+ "structure", -+ "strudel", -+ "struggle", -+ "strum", -+ "strung", -+ "strut", -+ "stubbed", -+ "stubble", -+ "stubbly", -+ "stubborn", -+ "stucco", -+ "stuck", -+ "student", -+ "studied", -+ "studio", -+ "study", -+ "stuffed", -+ "stuffing", -+ "stuffy", -+ "stumble", -+ "stumbling", -+ "stump", -+ "stung", -+ "stunned", -+ "stunner", -+ "stunning", -+ "stunt", -+ "stupor", -+ "sturdily", -+ "sturdy", -+ "styling", -+ "stylishly", -+ "stylist", -+ "stylized", -+ "stylus", -+ "suave", -+ "subarctic", -+ "subatomic", -+ "subdivide", -+ "subdued", -+ "subduing", -+ "subfloor", -+ "subgroup", -+ "subheader", -+ "subject", -+ "sublease", -+ "sublet", -+ "sublevel", -+ "sublime", -+ "submarine", -+ "submerge", -+ "submersed", -+ "submitter", -+ "subpanel", -+ "subpar", -+ "subplot", -+ "subprime", -+ "subscribe", -+ "subscript", -+ "subsector", -+ "subside", -+ "subsiding", -+ "subsidize", -+ "subsidy", -+ "subsoil", -+ "subsonic", -+ "substance", -+ "subsystem", -+ "subtext", -+ "subtitle", -+ "subtly", -+ "subtotal", -+ "subtract", -+ "subtype", -+ "suburb", -+ "subway", -+ "subwoofer", -+ "subzero", -+ "succulent", -+ "such", -+ "suction", -+ "sudden", -+ "sudoku", -+ "suds", -+ "sufferer", -+ "suffering", -+ "suffice", -+ "suffix", -+ "suffocate", -+ "suffrage", -+ "sugar", -+ "suggest", -+ "suing", -+ "suitable", -+ "suitably", -+ "suitcase", -+ "suitor", -+ "sulfate", -+ "sulfide", -+ "sulfite", -+ "sulfur", -+ "sulk", -+ "sullen", -+ "sulphate", -+ "sulphuric", -+ "sultry", -+ "superbowl", -+ "superglue", -+ "superhero", -+ "superior", -+ "superjet", -+ "superman", -+ "supermom", -+ "supernova", -+ "supervise", -+ "supper", -+ "supplier", -+ "supply", -+ "support", -+ "supremacy", -+ "supreme", -+ "surcharge", -+ "surely", -+ "sureness", -+ "surface", -+ "surfacing", -+ "surfboard", -+ "surfer", -+ "surgery", -+ "surgical", -+ "surging", -+ "surname", -+ "surpass", -+ "surplus", -+ "surprise", -+ "surreal", -+ "surrender", -+ "surrogate", -+ "surround", -+ "survey", -+ "survival", -+ "survive", -+ "surviving", -+ "survivor", -+ "sushi", -+ "suspect", -+ "suspend", -+ "suspense", -+ "sustained", -+ "sustainer", -+ "swab", -+ "swaddling", -+ "swagger", -+ "swampland", -+ "swan", -+ "swapping", -+ "swarm", -+ "sway", -+ "swear", -+ "sweat", -+ "sweep", -+ "swell", -+ "swept", -+ "swerve", -+ "swifter", -+ "swiftly", -+ "swiftness", -+ "swimmable", -+ "swimmer", -+ "swimming", -+ "swimsuit", -+ "swimwear", -+ "swinger", -+ "swinging", -+ "swipe", -+ "swirl", -+ "switch", -+ "swivel", -+ "swizzle", -+ "swooned", -+ "swoop", -+ "swoosh", -+ "swore", -+ "sworn", -+ "swung", -+ "sycamore", -+ "sympathy", -+ "symphonic", -+ "symphony", -+ "symptom", -+ "synapse", -+ "syndrome", -+ "synergy", -+ "synopses", -+ "synopsis", -+ "synthesis", -+ "synthetic", -+ "syrup", -+ "system", -+ "t-shirt", -+ "tabasco", -+ "tabby", -+ "tableful", -+ "tables", -+ "tablet", -+ "tableware", -+ "tabloid", -+ "tackiness", -+ "tacking", -+ "tackle", -+ "tackling", -+ "tacky", -+ "taco", -+ "tactful", -+ "tactical", -+ "tactics", -+ "tactile", -+ "tactless", -+ "tadpole", -+ "taekwondo", -+ "tag", -+ "tainted", -+ "take", -+ "taking", -+ "talcum", -+ "talisman", -+ "tall", -+ "talon", -+ "tamale", -+ "tameness", -+ "tamer", -+ "tamper", -+ "tank", -+ "tanned", -+ "tannery", -+ "tanning", -+ "tantrum", -+ "tapeless", -+ "tapered", -+ "tapering", -+ "tapestry", -+ "tapioca", -+ "tapping", -+ "taps", -+ "tarantula", -+ "target", -+ "tarmac", -+ "tarnish", -+ "tarot", -+ "tartar", -+ "tartly", -+ "tartness", -+ "task", -+ "tassel", -+ "taste", -+ "tastiness", -+ "tasting", -+ "tasty", -+ "tattered", -+ "tattle", -+ "tattling", -+ "tattoo", -+ "taunt", -+ "tavern", -+ "thank", -+ "that", -+ "thaw", -+ "theater", -+ "theatrics", -+ "thee", -+ "theft", -+ "theme", -+ "theology", -+ "theorize", -+ "thermal", -+ "thermos", -+ "thesaurus", -+ "these", -+ "thesis", -+ "thespian", -+ "thicken", -+ "thicket", -+ "thickness", -+ "thieving", -+ "thievish", -+ "thigh", -+ "thimble", -+ "thing", -+ "think", -+ "thinly", -+ "thinner", -+ "thinness", -+ "thinning", -+ "thirstily", -+ "thirsting", -+ "thirsty", -+ "thirteen", -+ "thirty", -+ "thong", -+ "thorn", -+ "those", -+ "thousand", -+ "thrash", -+ "thread", -+ "threaten", -+ "threefold", -+ "thrift", -+ "thrill", -+ "thrive", -+ "thriving", -+ "throat", -+ "throbbing", -+ "throng", -+ "throttle", -+ "throwaway", -+ "throwback", -+ "thrower", -+ "throwing", -+ "thud", -+ "thumb", -+ "thumping", -+ "thursday", -+ "thus", -+ "thwarting", -+ "thyself", -+ "tiara", -+ "tibia", -+ "tidal", -+ "tidbit", -+ "tidiness", -+ "tidings", -+ "tidy", -+ "tiger", -+ "tighten", -+ "tightly", -+ "tightness", -+ "tightrope", -+ "tightwad", -+ "tigress", -+ "tile", -+ "tiling", -+ "till", -+ "tilt", -+ "timid", -+ "timing", -+ "timothy", -+ "tinderbox", -+ "tinfoil", -+ "tingle", -+ "tingling", -+ "tingly", -+ "tinker", -+ "tinkling", -+ "tinsel", -+ "tinsmith", -+ "tint", -+ "tinwork", -+ "tiny", -+ "tipoff", -+ "tipped", -+ "tipper", -+ "tipping", -+ "tiptoeing", -+ "tiptop", -+ "tiring", -+ "tissue", -+ "trace", -+ "tracing", -+ "track", -+ "traction", -+ "tractor", -+ "trade", -+ "trading", -+ "tradition", -+ "traffic", -+ "tragedy", -+ "trailing", -+ "trailside", -+ "train", -+ "traitor", -+ "trance", -+ "tranquil", -+ "transfer", -+ "transform", -+ "translate", -+ "transpire", -+ "transport", -+ "transpose", -+ "trapdoor", -+ "trapeze", -+ "trapezoid", -+ "trapped", -+ "trapper", -+ "trapping", -+ "traps", -+ "trash", -+ "travel", -+ "traverse", -+ "travesty", -+ "tray", -+ "treachery", -+ "treading", -+ "treadmill", -+ "treason", -+ "treat", -+ "treble", -+ "tree", -+ "trekker", -+ "tremble", -+ "trembling", -+ "tremor", -+ "trench", -+ "trend", -+ "trespass", -+ "triage", -+ "trial", -+ "triangle", -+ "tribesman", -+ "tribunal", -+ "tribune", -+ "tributary", -+ "tribute", -+ "triceps", -+ "trickery", -+ "trickily", -+ "tricking", -+ "trickle", -+ "trickster", -+ "tricky", -+ "tricolor", -+ "tricycle", -+ "trident", -+ "tried", -+ "trifle", -+ "trifocals", -+ "trillion", -+ "trilogy", -+ "trimester", -+ "trimmer", -+ "trimming", -+ "trimness", -+ "trinity", -+ "trio", -+ "tripod", -+ "tripping", -+ "triumph", -+ "trivial", -+ "trodden", -+ "trolling", -+ "trombone", -+ "trophy", -+ "tropical", -+ "tropics", -+ "trouble", -+ "troubling", -+ "trough", -+ "trousers", -+ "trout", -+ "trowel", -+ "truce", -+ "truck", -+ "truffle", -+ "trump", -+ "trunks", -+ "trustable", -+ "trustee", -+ "trustful", -+ "trusting", -+ "trustless", -+ "truth", -+ "try", -+ "tubby", -+ "tubeless", -+ "tubular", -+ "tucking", -+ "tuesday", -+ "tug", -+ "tuition", -+ "tulip", -+ "tumble", -+ "tumbling", -+ "tummy", -+ "turban", -+ "turbine", -+ "turbofan", -+ "turbojet", -+ "turbulent", -+ "turf", -+ "turkey", -+ "turmoil", -+ "turret", -+ "turtle", -+ "tusk", -+ "tutor", -+ "tutu", -+ "tux", -+ "tweak", -+ "tweed", -+ "tweet", -+ "tweezers", -+ "twelve", -+ "twentieth", -+ "twenty", -+ "twerp", -+ "twice", -+ "twiddle", -+ "twiddling", -+ "twig", -+ "twilight", -+ "twine", -+ "twins", -+ "twirl", -+ "twistable", -+ "twisted", -+ "twister", -+ "twisting", -+ "twisty", -+ "twitch", -+ "twitter", -+ "tycoon", -+ "tying", -+ "tyke", -+ "udder", -+ "ultimate", -+ "ultimatum", -+ "ultra", -+ "umbilical", -+ "umbrella", -+ "umpire", -+ "unabashed", -+ "unable", -+ "unadorned", -+ "unadvised", -+ "unafraid", -+ "unaired", -+ "unaligned", -+ "unaltered", -+ "unarmored", -+ "unashamed", -+ "unaudited", -+ "unawake", -+ "unaware", -+ "unbaked", -+ "unbalance", -+ "unbeaten", -+ "unbend", -+ "unbent", -+ "unbiased", -+ "unbitten", -+ "unblended", -+ "unblessed", -+ "unblock", -+ "unbolted", -+ "unbounded", -+ "unboxed", -+ "unbraided", -+ "unbridle", -+ "unbroken", -+ "unbuckled", -+ "unbundle", -+ "unburned", -+ "unbutton", -+ "uncanny", -+ "uncapped", -+ "uncaring", -+ "uncertain", -+ "unchain", -+ "unchanged", -+ "uncharted", -+ "uncheck", -+ "uncivil", -+ "unclad", -+ "unclaimed", -+ "unclamped", -+ "unclasp", -+ "uncle", -+ "unclip", -+ "uncloak", -+ "unclog", -+ "unclothed", -+ "uncoated", -+ "uncoiled", -+ "uncolored", -+ "uncombed", -+ "uncommon", -+ "uncooked", -+ "uncork", -+ "uncorrupt", -+ "uncounted", -+ "uncouple", -+ "uncouth", -+ "uncover", -+ "uncross", -+ "uncrown", -+ "uncrushed", -+ "uncured", -+ "uncurious", -+ "uncurled", -+ "uncut", -+ "undamaged", -+ "undated", -+ "undaunted", -+ "undead", -+ "undecided", -+ "undefined", -+ "underage", -+ "underarm", -+ "undercoat", -+ "undercook", -+ "undercut", -+ "underdog", -+ "underdone", -+ "underfed", -+ "underfeed", -+ "underfoot", -+ "undergo", -+ "undergrad", -+ "underhand", -+ "underline", -+ "underling", -+ "undermine", -+ "undermost", -+ "underpaid", -+ "underpass", -+ "underpay", -+ "underrate", -+ "undertake", -+ "undertone", -+ "undertook", -+ "undertow", -+ "underuse", -+ "underwear", -+ "underwent", -+ "underwire", -+ "undesired", -+ "undiluted", -+ "undivided", -+ "undocked", -+ "undoing", -+ "undone", -+ "undrafted", -+ "undress", -+ "undrilled", -+ "undusted", -+ "undying", -+ "unearned", -+ "unearth", -+ "unease", -+ "uneasily", -+ "uneasy", -+ "uneatable", -+ "uneaten", -+ "unedited", -+ "unelected", -+ "unending", -+ "unengaged", -+ "unenvied", -+ "unequal", -+ "unethical", -+ "uneven", -+ "unexpired", -+ "unexposed", -+ "unfailing", -+ "unfair", -+ "unfasten", -+ "unfazed", -+ "unfeeling", -+ "unfiled", -+ "unfilled", -+ "unfitted", -+ "unfitting", -+ "unfixable", -+ "unfixed", -+ "unflawed", -+ "unfocused", -+ "unfold", -+ "unfounded", -+ "unframed", -+ "unfreeze", -+ "unfrosted", -+ "unfrozen", -+ "unfunded", -+ "unglazed", -+ "ungloved", -+ "unglue", -+ "ungodly", -+ "ungraded", -+ "ungreased", -+ "unguarded", -+ "unguided", -+ "unhappily", -+ "unhappy", -+ "unharmed", -+ "unhealthy", -+ "unheard", -+ "unhearing", -+ "unheated", -+ "unhelpful", -+ "unhidden", -+ "unhinge", -+ "unhitched", -+ "unholy", -+ "unhook", -+ "unicorn", -+ "unicycle", -+ "unified", -+ "unifier", -+ "uniformed", -+ "uniformly", -+ "unify", -+ "unimpeded", -+ "uninjured", -+ "uninstall", -+ "uninsured", -+ "uninvited", -+ "union", -+ "uniquely", -+ "unisexual", -+ "unison", -+ "unissued", -+ "unit", -+ "universal", -+ "universe", -+ "unjustly", -+ "unkempt", -+ "unkind", -+ "unknotted", -+ "unknowing", -+ "unknown", -+ "unlaced", -+ "unlatch", -+ "unlawful", -+ "unleaded", -+ "unlearned", -+ "unleash", -+ "unless", -+ "unleveled", -+ "unlighted", -+ "unlikable", -+ "unlimited", -+ "unlined", -+ "unlinked", -+ "unlisted", -+ "unlit", -+ "unlivable", -+ "unloaded", -+ "unloader", -+ "unlocked", -+ "unlocking", -+ "unlovable", -+ "unloved", -+ "unlovely", -+ "unloving", -+ "unluckily", -+ "unlucky", -+ "unmade", -+ "unmanaged", -+ "unmanned", -+ "unmapped", -+ "unmarked", -+ "unmasked", -+ "unmasking", -+ "unmatched", -+ "unmindful", -+ "unmixable", -+ "unmixed", -+ "unmolded", -+ "unmoral", -+ "unmovable", -+ "unmoved", -+ "unmoving", -+ "unnamable", -+ "unnamed", -+ "unnatural", -+ "unneeded", -+ "unnerve", -+ "unnerving", -+ "unnoticed", -+ "unopened", -+ "unopposed", -+ "unpack", -+ "unpadded", -+ "unpaid", -+ "unpainted", -+ "unpaired", -+ "unpaved", -+ "unpeeled", -+ "unpicked", -+ "unpiloted", -+ "unpinned", -+ "unplanned", -+ "unplanted", -+ "unpleased", -+ "unpledged", -+ "unplowed", -+ "unplug", -+ "unpopular", -+ "unproven", -+ "unquote", -+ "unranked", -+ "unrated", -+ "unraveled", -+ "unreached", -+ "unread", -+ "unreal", -+ "unreeling", -+ "unrefined", -+ "unrelated", -+ "unrented", -+ "unrest", -+ "unretired", -+ "unrevised", -+ "unrigged", -+ "unripe", -+ "unrivaled", -+ "unroasted", -+ "unrobed", -+ "unroll", -+ "unruffled", -+ "unruly", -+ "unrushed", -+ "unsaddle", -+ "unsafe", -+ "unsaid", -+ "unsalted", -+ "unsaved", -+ "unsavory", -+ "unscathed", -+ "unscented", -+ "unscrew", -+ "unsealed", -+ "unseated", -+ "unsecured", -+ "unseeing", -+ "unseemly", -+ "unseen", -+ "unselect", -+ "unselfish", -+ "unsent", -+ "unsettled", -+ "unshackle", -+ "unshaken", -+ "unshaved", -+ "unshaven", -+ "unsheathe", -+ "unshipped", -+ "unsightly", -+ "unsigned", -+ "unskilled", -+ "unsliced", -+ "unsmooth", -+ "unsnap", -+ "unsocial", -+ "unsoiled", -+ "unsold", -+ "unsolved", -+ "unsorted", -+ "unspoiled", -+ "unspoken", -+ "unstable", -+ "unstaffed", -+ "unstamped", -+ "unsteady", -+ "unsterile", -+ "unstirred", -+ "unstitch", -+ "unstopped", -+ "unstuck", -+ "unstuffed", -+ "unstylish", -+ "unsubtle", -+ "unsubtly", -+ "unsuited", -+ "unsure", -+ "unsworn", -+ "untagged", -+ "untainted", -+ "untaken", -+ "untamed", -+ "untangled", -+ "untapped", -+ "untaxed", -+ "unthawed", -+ "unthread", -+ "untidy", -+ "untie", -+ "until", -+ "untimed", -+ "untimely", -+ "untitled", -+ "untoasted", -+ "untold", -+ "untouched", -+ "untracked", -+ "untrained", -+ "untreated", -+ "untried", -+ "untrimmed", -+ "untrue", -+ "untruth", -+ "unturned", -+ "untwist", -+ "untying", -+ "unusable", -+ "unused", -+ "unusual", -+ "unvalued", -+ "unvaried", -+ "unvarying", -+ "unveiled", -+ "unveiling", -+ "unvented", -+ "unviable", -+ "unvisited", -+ "unvocal", -+ "unwanted", -+ "unwarlike", -+ "unwary", -+ "unwashed", -+ "unwatched", -+ "unweave", -+ "unwed", -+ "unwelcome", -+ "unwell", -+ "unwieldy", -+ "unwilling", -+ "unwind", -+ "unwired", -+ "unwitting", -+ "unwomanly", -+ "unworldly", -+ "unworn", -+ "unworried", -+ "unworthy", -+ "unwound", -+ "unwoven", -+ "unwrapped", -+ "unwritten", -+ "unzip", -+ "upbeat", -+ "upchuck", -+ "upcoming", -+ "upcountry", -+ "update", -+ "upfront", -+ "upgrade", -+ "upheaval", -+ "upheld", -+ "uphill", -+ "uphold", -+ "uplifted", -+ "uplifting", -+ "upload", -+ "upon", -+ "upper", -+ "upright", -+ "uprising", -+ "upriver", -+ "uproar", -+ "uproot", -+ "upscale", -+ "upside", -+ "upstage", -+ "upstairs", -+ "upstart", -+ "upstate", -+ "upstream", -+ "upstroke", -+ "upswing", -+ "uptake", -+ "uptight", -+ "uptown", -+ "upturned", -+ "upward", -+ "upwind", -+ "uranium", -+ "urban", -+ "urchin", -+ "urethane", -+ "urgency", -+ "urgent", -+ "urging", -+ "urologist", -+ "urology", -+ "usable", -+ "usage", -+ "useable", -+ "used", -+ "uselessly", -+ "user", -+ "usher", -+ "usual", -+ "utensil", -+ "utility", -+ "utilize", -+ "utmost", -+ "utopia", -+ "utter", -+ "vacancy", -+ "vacant", -+ "vacate", -+ "vacation", -+ "vagabond", -+ "vagrancy", -+ "vagrantly", -+ "vaguely", -+ "vagueness", -+ "valiant", -+ "valid", -+ "valium", -+ "valley", -+ "valuables", -+ "value", -+ "vanilla", -+ "vanish", -+ "vanity", -+ "vanquish", -+ "vantage", -+ "vaporizer", -+ "variable", -+ "variably", -+ "varied", -+ "variety", -+ "various", -+ "varmint", -+ "varnish", -+ "varsity", -+ "varying", -+ "vascular", -+ "vaseline", -+ "vastly", -+ "vastness", -+ "veal", -+ "vegan", -+ "veggie", -+ "vehicular", -+ "velcro", -+ "velocity", -+ "velvet", -+ "vendetta", -+ "vending", -+ "vendor", -+ "veneering", -+ "vengeful", -+ "venomous", -+ "ventricle", -+ "venture", -+ "venue", -+ "venus", -+ "verbalize", -+ "verbally", -+ "verbose", -+ "verdict", -+ "verify", -+ "verse", -+ "version", -+ "versus", -+ "vertebrae", -+ "vertical", -+ "vertigo", -+ "very", -+ "vessel", -+ "vest", -+ "veteran", -+ "veto", -+ "vexingly", -+ "viability", -+ "viable", -+ "vibes", -+ "vice", -+ "vicinity", -+ "victory", -+ "video", -+ "viewable", -+ "viewer", -+ "viewing", -+ "viewless", -+ "viewpoint", -+ "vigorous", -+ "village", -+ "villain", -+ "vindicate", -+ "vineyard", -+ "vintage", -+ "violate", -+ "violation", -+ "violator", -+ "violet", -+ "violin", -+ "viper", -+ "viral", -+ "virtual", -+ "virtuous", -+ "virus", -+ "visa", -+ "viscosity", -+ "viscous", -+ "viselike", -+ "visible", -+ "visibly", -+ "vision", -+ "visiting", -+ "visitor", -+ "visor", -+ "vista", -+ "vitality", -+ "vitalize", -+ "vitally", -+ "vitamins", -+ "vivacious", -+ "vividly", -+ "vividness", -+ "vixen", -+ "vocalist", -+ "vocalize", -+ "vocally", -+ "vocation", -+ "voice", -+ "voicing", -+ "void", -+ "volatile", -+ "volley", -+ "voltage", -+ "volumes", -+ "voter", -+ "voting", -+ "voucher", -+ "vowed", -+ "vowel", -+ "voyage", -+ "wackiness", -+ "wad", -+ "wafer", -+ "waffle", -+ "waged", -+ "wager", -+ "wages", -+ "waggle", -+ "wagon", -+ "wake", -+ "waking", -+ "walk", -+ "walmart", -+ "walnut", -+ "walrus", -+ "waltz", -+ "wand", -+ "wannabe", -+ "wanted", -+ "wanting", -+ "wasabi", -+ "washable", -+ "washbasin", -+ "washboard", -+ "washbowl", -+ "washcloth", -+ "washday", -+ "washed", -+ "washer", -+ "washhouse", -+ "washing", -+ "washout", -+ "washroom", -+ "washstand", -+ "washtub", -+ "wasp", -+ "wasting", -+ "watch", -+ "water", -+ "waviness", -+ "waving", -+ "wavy", -+ "whacking", -+ "whacky", -+ "wham", -+ "wharf", -+ "wheat", -+ "whenever", -+ "whiff", -+ "whimsical", -+ "whinny", -+ "whiny", -+ "whisking", -+ "whoever", -+ "whole", -+ "whomever", -+ "whoopee", -+ "whooping", -+ "whoops", -+ "why", -+ "wick", -+ "widely", -+ "widen", -+ "widget", -+ "widow", -+ "width", -+ "wieldable", -+ "wielder", -+ "wife", -+ "wifi", -+ "wikipedia", -+ "wildcard", -+ "wildcat", -+ "wilder", -+ "wildfire", -+ "wildfowl", -+ "wildland", -+ "wildlife", -+ "wildly", -+ "wildness", -+ "willed", -+ "willfully", -+ "willing", -+ "willow", -+ "willpower", -+ "wilt", -+ "wimp", -+ "wince", -+ "wincing", -+ "wind", -+ "wing", -+ "winking", -+ "winner", -+ "winnings", -+ "winter", -+ "wipe", -+ "wired", -+ "wireless", -+ "wiring", -+ "wiry", -+ "wisdom", -+ "wise", -+ "wish", -+ "wisplike", -+ "wispy", -+ "wistful", -+ "wizard", -+ "wobble", -+ "wobbling", -+ "wobbly", -+ "wok", -+ "wolf", -+ "wolverine", -+ "womanhood", -+ "womankind", -+ "womanless", -+ "womanlike", -+ "womanly", -+ "womb", -+ "woof", -+ "wooing", -+ "wool", -+ "woozy", -+ "word", -+ "work", -+ "worried", -+ "worrier", -+ "worrisome", -+ "worry", -+ "worsening", -+ "worshiper", -+ "worst", -+ "wound", -+ "woven", -+ "wow", -+ "wrangle", -+ "wrath", -+ "wreath", -+ "wreckage", -+ "wrecker", -+ "wrecking", -+ "wrench", -+ "wriggle", -+ "wriggly", -+ "wrinkle", -+ "wrinkly", -+ "wrist", -+ "writing", -+ "written", -+ "wrongdoer", -+ "wronged", -+ "wrongful", -+ "wrongly", -+ "wrongness", -+ "wrought", -+ "xbox", -+ "xerox", -+ "yahoo", -+ "yam", -+ "yanking", -+ "yapping", -+ "yard", -+ "yarn", -+ "yeah", -+ "yearbook", -+ "yearling", -+ "yearly", -+ "yearning", -+ "yeast", -+ "yelling", -+ "yelp", -+ "yen", -+ "yesterday", -+ "yiddish", -+ "yield", -+ "yin", -+ "yippee", -+ "yo-yo", -+ "yodel", -+ "yoga", -+ "yogurt", -+ "yonder", -+ "yoyo", -+ "yummy", -+ "zap", -+ "zealous", -+ "zebra", -+ "zen", -+ "zeppelin", -+ "zero", -+ "zestfully", -+ "zesty", -+ "zigzagged", -+ "zipfile", -+ "zipping", -+ "zippy", -+ "zips", -+ "zit", -+ "zodiac", -+ "zombie", -+ "zone", -+ "zoning", -+ "zookeeper", -+ "zoologist", -+ "zoology", -+ "zoom", - ]; -diff --git a/jslib/common/src/models/api/cardApi.ts b/jslib/common/src/models/api/cardApi.ts -index b837bb90..8d972624 100644 ---- a/jslib/common/src/models/api/cardApi.ts -+++ b/jslib/common/src/models/api/cardApi.ts -@@ -1,23 +1,23 @@ --import { BaseResponse } from '../response/baseResponse'; -+import { BaseResponse } from "../response/baseResponse"; - - export class CardApi extends BaseResponse { -- cardholderName: string; -- brand: string; -- number: string; -- expMonth: string; -- expYear: string; -- code: string; -+ cardholderName: string; -+ brand: string; -+ number: string; -+ expMonth: string; -+ expYear: string; -+ code: string; - -- constructor(data: any = null) { -- super(data); -- if (data == null) { -- return; -- } -- this.cardholderName = this.getResponseProperty('CardholderName'); -- this.brand = this.getResponseProperty('Brand'); -- this.number = this.getResponseProperty('Number'); -- this.expMonth = this.getResponseProperty('ExpMonth'); -- this.expYear = this.getResponseProperty('ExpYear'); -- this.code = this.getResponseProperty('Code'); -+ constructor(data: any = null) { -+ super(data); -+ if (data == null) { -+ return; - } -+ this.cardholderName = this.getResponseProperty("CardholderName"); -+ this.brand = this.getResponseProperty("Brand"); -+ this.number = this.getResponseProperty("Number"); -+ this.expMonth = this.getResponseProperty("ExpMonth"); -+ this.expYear = this.getResponseProperty("ExpYear"); -+ this.code = this.getResponseProperty("Code"); -+ } - } -diff --git a/jslib/common/src/models/api/fieldApi.ts b/jslib/common/src/models/api/fieldApi.ts -index b9e8e0e9..d7e603ea 100644 ---- a/jslib/common/src/models/api/fieldApi.ts -+++ b/jslib/common/src/models/api/fieldApi.ts -@@ -1,22 +1,22 @@ --import { BaseResponse } from '../response/baseResponse'; -+import { BaseResponse } from "../response/baseResponse"; - --import { FieldType } from '../../enums/fieldType'; --import { LinkedIdType } from '../../enums/linkedIdType'; -+import { FieldType } from "../../enums/fieldType"; -+import { LinkedIdType } from "../../enums/linkedIdType"; - - export class FieldApi extends BaseResponse { -- name: string; -- value: string; -- type: FieldType; -- linkedId: LinkedIdType; -+ name: string; -+ value: string; -+ type: FieldType; -+ linkedId: LinkedIdType; - -- constructor(data: any = null) { -- super(data); -- if (data == null) { -- return; -- } -- this.type = this.getResponseProperty('Type'); -- this.name = this.getResponseProperty('Name'); -- this.value = this.getResponseProperty('Value'); -- this.linkedId = this.getResponseProperty('linkedId'); -+ constructor(data: any = null) { -+ super(data); -+ if (data == null) { -+ return; - } -+ this.type = this.getResponseProperty("Type"); -+ this.name = this.getResponseProperty("Name"); -+ this.value = this.getResponseProperty("Value"); -+ this.linkedId = this.getResponseProperty("linkedId"); -+ } - } -diff --git a/jslib/common/src/models/api/identityApi.ts b/jslib/common/src/models/api/identityApi.ts -index e271e5e3..9312438f 100644 ---- a/jslib/common/src/models/api/identityApi.ts -+++ b/jslib/common/src/models/api/identityApi.ts -@@ -1,47 +1,47 @@ --import { BaseResponse } from '../response/baseResponse'; -+import { BaseResponse } from "../response/baseResponse"; - - export class IdentityApi extends BaseResponse { -- title: string; -- firstName: string; -- middleName: string; -- lastName: string; -- address1: string; -- address2: string; -- address3: string; -- city: string; -- state: string; -- postalCode: string; -- country: string; -- company: string; -- email: string; -- phone: string; -- ssn: string; -- username: string; -- passportNumber: string; -- licenseNumber: string; -+ title: string; -+ firstName: string; -+ middleName: string; -+ lastName: string; -+ address1: string; -+ address2: string; -+ address3: string; -+ city: string; -+ state: string; -+ postalCode: string; -+ country: string; -+ company: string; -+ email: string; -+ phone: string; -+ ssn: string; -+ username: string; -+ passportNumber: string; -+ licenseNumber: string; - -- constructor(data: any = null) { -- super(data); -- if (data == null) { -- return; -- } -- this.title = this.getResponseProperty('Title'); -- this.firstName = this.getResponseProperty('FirstName'); -- this.middleName = this.getResponseProperty('MiddleName'); -- this.lastName = this.getResponseProperty('LastName'); -- this.address1 = this.getResponseProperty('Address1'); -- this.address2 = this.getResponseProperty('Address2'); -- this.address3 = this.getResponseProperty('Address3'); -- this.city = this.getResponseProperty('City'); -- this.state = this.getResponseProperty('State'); -- this.postalCode = this.getResponseProperty('PostalCode'); -- this.country = this.getResponseProperty('Country'); -- this.company = this.getResponseProperty('Company'); -- this.email = this.getResponseProperty('Email'); -- this.phone = this.getResponseProperty('Phone'); -- this.ssn = this.getResponseProperty('SSN'); -- this.username = this.getResponseProperty('Username'); -- this.passportNumber = this.getResponseProperty('PassportNumber'); -- this.licenseNumber = this.getResponseProperty('LicenseNumber'); -+ constructor(data: any = null) { -+ super(data); -+ if (data == null) { -+ return; - } -+ this.title = this.getResponseProperty("Title"); -+ this.firstName = this.getResponseProperty("FirstName"); -+ this.middleName = this.getResponseProperty("MiddleName"); -+ this.lastName = this.getResponseProperty("LastName"); -+ this.address1 = this.getResponseProperty("Address1"); -+ this.address2 = this.getResponseProperty("Address2"); -+ this.address3 = this.getResponseProperty("Address3"); -+ this.city = this.getResponseProperty("City"); -+ this.state = this.getResponseProperty("State"); -+ this.postalCode = this.getResponseProperty("PostalCode"); -+ this.country = this.getResponseProperty("Country"); -+ this.company = this.getResponseProperty("Company"); -+ this.email = this.getResponseProperty("Email"); -+ this.phone = this.getResponseProperty("Phone"); -+ this.ssn = this.getResponseProperty("SSN"); -+ this.username = this.getResponseProperty("Username"); -+ this.passportNumber = this.getResponseProperty("PassportNumber"); -+ this.licenseNumber = this.getResponseProperty("LicenseNumber"); -+ } - } -diff --git a/jslib/common/src/models/api/loginApi.ts b/jslib/common/src/models/api/loginApi.ts -index a02cde38..ddbe37ec 100644 ---- a/jslib/common/src/models/api/loginApi.ts -+++ b/jslib/common/src/models/api/loginApi.ts -@@ -1,29 +1,29 @@ --import { BaseResponse } from '../response/baseResponse'; -+import { BaseResponse } from "../response/baseResponse"; - --import { LoginUriApi } from './loginUriApi'; -+import { LoginUriApi } from "./loginUriApi"; - - export class LoginApi extends BaseResponse { -- uris: LoginUriApi[]; -- username: string; -- password: string; -- passwordRevisionDate: string; -- totp: string; -- autofillOnPageLoad: boolean; -+ uris: LoginUriApi[]; -+ username: string; -+ password: string; -+ passwordRevisionDate: string; -+ totp: string; -+ autofillOnPageLoad: boolean; - -- constructor(data: any = null) { -- super(data); -- if (data == null) { -- return; -- } -- this.username = this.getResponseProperty('Username'); -- this.password = this.getResponseProperty('Password'); -- this.passwordRevisionDate = this.getResponseProperty('PasswordRevisionDate'); -- this.totp = this.getResponseProperty('Totp'); -- this.autofillOnPageLoad = this.getResponseProperty('AutofillOnPageLoad'); -+ constructor(data: any = null) { -+ super(data); -+ if (data == null) { -+ return; -+ } -+ this.username = this.getResponseProperty("Username"); -+ this.password = this.getResponseProperty("Password"); -+ this.passwordRevisionDate = this.getResponseProperty("PasswordRevisionDate"); -+ this.totp = this.getResponseProperty("Totp"); -+ this.autofillOnPageLoad = this.getResponseProperty("AutofillOnPageLoad"); - -- const uris = this.getResponseProperty('Uris'); -- if (uris != null) { -- this.uris = uris.map((u: any) => new LoginUriApi(u)); -- } -+ const uris = this.getResponseProperty("Uris"); -+ if (uris != null) { -+ this.uris = uris.map((u: any) => new LoginUriApi(u)); - } -+ } - } -diff --git a/jslib/common/src/models/api/loginUriApi.ts b/jslib/common/src/models/api/loginUriApi.ts -index 6ea8d4e2..9da6e0a7 100644 ---- a/jslib/common/src/models/api/loginUriApi.ts -+++ b/jslib/common/src/models/api/loginUriApi.ts -@@ -1,18 +1,18 @@ --import { BaseResponse } from '../response/baseResponse'; -+import { BaseResponse } from "../response/baseResponse"; - --import { UriMatchType } from '../../enums/uriMatchType'; -+import { UriMatchType } from "../../enums/uriMatchType"; - - export class LoginUriApi extends BaseResponse { -- uri: string; -- match: UriMatchType = null; -+ uri: string; -+ match: UriMatchType = null; - -- constructor(data: any = null) { -- super(data); -- if (data == null) { -- return; -- } -- this.uri = this.getResponseProperty('Uri'); -- const match = this.getResponseProperty('Match'); -- this.match = match != null ? match : null; -+ constructor(data: any = null) { -+ super(data); -+ if (data == null) { -+ return; - } -+ this.uri = this.getResponseProperty("Uri"); -+ const match = this.getResponseProperty("Match"); -+ this.match = match != null ? match : null; -+ } - } -diff --git a/jslib/common/src/models/api/permissionsApi.ts b/jslib/common/src/models/api/permissionsApi.ts -index 0755074e..bac79bd3 100644 ---- a/jslib/common/src/models/api/permissionsApi.ts -+++ b/jslib/common/src/models/api/permissionsApi.ts -@@ -1,55 +1,55 @@ --import { BaseResponse } from '../response/baseResponse'; -+import { BaseResponse } from "../response/baseResponse"; - - export class PermissionsApi extends BaseResponse { -- accessEventLogs: boolean; -- accessImportExport: boolean; -- accessReports: boolean; -- /** -- * @deprecated Sep 29 2021: This permission has been split out to `createNewCollections`, `editAnyCollection`, and -- * `deleteAnyCollection`. It exists here for backwards compatibility with Server versions <= 1.43.0 -- */ -- manageAllCollections: boolean; -- createNewCollections: boolean; -- editAnyCollection: boolean; -- deleteAnyCollection: boolean; -- /** -- * @deprecated Sep 29 2021: This permission has been split out to `editAssignedCollections` and -- * `deleteAssignedCollections`. It exists here for backwards compatibility with Server versions <= 1.43.0 -- */ -- manageAssignedCollections: boolean; -- editAssignedCollections: boolean; -- deleteAssignedCollections: boolean; -- manageCiphers: boolean; -- manageGroups: boolean; -- manageSso: boolean; -- managePolicies: boolean; -- manageUsers: boolean; -- manageResetPassword: boolean; -+ accessEventLogs: boolean; -+ accessImportExport: boolean; -+ accessReports: boolean; -+ /** -+ * @deprecated Sep 29 2021: This permission has been split out to `createNewCollections`, `editAnyCollection`, and -+ * `deleteAnyCollection`. It exists here for backwards compatibility with Server versions <= 1.43.0 -+ */ -+ manageAllCollections: boolean; -+ createNewCollections: boolean; -+ editAnyCollection: boolean; -+ deleteAnyCollection: boolean; -+ /** -+ * @deprecated Sep 29 2021: This permission has been split out to `editAssignedCollections` and -+ * `deleteAssignedCollections`. It exists here for backwards compatibility with Server versions <= 1.43.0 -+ */ -+ manageAssignedCollections: boolean; -+ editAssignedCollections: boolean; -+ deleteAssignedCollections: boolean; -+ manageCiphers: boolean; -+ manageGroups: boolean; -+ manageSso: boolean; -+ managePolicies: boolean; -+ manageUsers: boolean; -+ manageResetPassword: boolean; - -- constructor(data: any = null) { -- super(data); -- if (data == null) { -- return this; -- } -- this.accessEventLogs = this.getResponseProperty('AccessEventLogs'); -- this.accessImportExport = this.getResponseProperty('AccessImportExport'); -- this.accessReports = this.getResponseProperty('AccessReports'); -+ constructor(data: any = null) { -+ super(data); -+ if (data == null) { -+ return this; -+ } -+ this.accessEventLogs = this.getResponseProperty("AccessEventLogs"); -+ this.accessImportExport = this.getResponseProperty("AccessImportExport"); -+ this.accessReports = this.getResponseProperty("AccessReports"); - -- // For backwards compatibility with Server <= 1.43.0 -- this.manageAllCollections = this.getResponseProperty('ManageAllCollections'); -- this.manageAssignedCollections = this.getResponseProperty('ManageAssignedCollections'); -+ // For backwards compatibility with Server <= 1.43.0 -+ this.manageAllCollections = this.getResponseProperty("ManageAllCollections"); -+ this.manageAssignedCollections = this.getResponseProperty("ManageAssignedCollections"); - -- this.createNewCollections = this.getResponseProperty('CreateNewCollections'); -- this.editAnyCollection = this.getResponseProperty('EditAnyCollection'); -- this.deleteAnyCollection = this.getResponseProperty('DeleteAnyCollection'); -- this.editAssignedCollections = this.getResponseProperty('EditAssignedCollections'); -- this.deleteAssignedCollections = this.getResponseProperty('DeleteAssignedCollections'); -+ this.createNewCollections = this.getResponseProperty("CreateNewCollections"); -+ this.editAnyCollection = this.getResponseProperty("EditAnyCollection"); -+ this.deleteAnyCollection = this.getResponseProperty("DeleteAnyCollection"); -+ this.editAssignedCollections = this.getResponseProperty("EditAssignedCollections"); -+ this.deleteAssignedCollections = this.getResponseProperty("DeleteAssignedCollections"); - -- this.manageCiphers = this.getResponseProperty('ManageCiphers'); -- this.manageGroups = this.getResponseProperty('ManageGroups'); -- this.manageSso = this.getResponseProperty('ManageSso'); -- this.managePolicies = this.getResponseProperty('ManagePolicies'); -- this.manageUsers = this.getResponseProperty('ManageUsers'); -- this.manageResetPassword = this.getResponseProperty('ManageResetPassword'); -- } -+ this.manageCiphers = this.getResponseProperty("ManageCiphers"); -+ this.manageGroups = this.getResponseProperty("ManageGroups"); -+ this.manageSso = this.getResponseProperty("ManageSso"); -+ this.managePolicies = this.getResponseProperty("ManagePolicies"); -+ this.manageUsers = this.getResponseProperty("ManageUsers"); -+ this.manageResetPassword = this.getResponseProperty("ManageResetPassword"); -+ } - } -diff --git a/jslib/common/src/models/api/secureNoteApi.ts b/jslib/common/src/models/api/secureNoteApi.ts -index 2fbdd94b..dfb25134 100644 ---- a/jslib/common/src/models/api/secureNoteApi.ts -+++ b/jslib/common/src/models/api/secureNoteApi.ts -@@ -1,15 +1,15 @@ --import { BaseResponse } from '../response/baseResponse'; -+import { BaseResponse } from "../response/baseResponse"; - --import { SecureNoteType } from '../../enums/secureNoteType'; -+import { SecureNoteType } from "../../enums/secureNoteType"; - - export class SecureNoteApi extends BaseResponse { -- type: SecureNoteType; -+ type: SecureNoteType; - -- constructor(data: any = null) { -- super(data); -- if (data == null) { -- return; -- } -- this.type = this.getResponseProperty('Type'); -+ constructor(data: any = null) { -+ super(data); -+ if (data == null) { -+ return; - } -+ this.type = this.getResponseProperty("Type"); -+ } - } -diff --git a/jslib/common/src/models/api/sendFileApi.ts b/jslib/common/src/models/api/sendFileApi.ts -index fc4feeee..f4d0926c 100644 ---- a/jslib/common/src/models/api/sendFileApi.ts -+++ b/jslib/common/src/models/api/sendFileApi.ts -@@ -1,21 +1,21 @@ --import { BaseResponse } from '../response/baseResponse'; -+import { BaseResponse } from "../response/baseResponse"; - - export class SendFileApi extends BaseResponse { -- id: string; -- fileName: string; -- key: string; -- size: string; -- sizeName: string; -+ id: string; -+ fileName: string; -+ key: string; -+ size: string; -+ sizeName: string; - -- constructor(data: any = null) { -- super(data); -- if (data == null) { -- return; -- } -- this.id = this.getResponseProperty('Id'); -- this.fileName = this.getResponseProperty('FileName'); -- this.key = this.getResponseProperty('Key'); -- this.size = this.getResponseProperty('Size'); -- this.sizeName = this.getResponseProperty('SizeName'); -+ constructor(data: any = null) { -+ super(data); -+ if (data == null) { -+ return; - } -+ this.id = this.getResponseProperty("Id"); -+ this.fileName = this.getResponseProperty("FileName"); -+ this.key = this.getResponseProperty("Key"); -+ this.size = this.getResponseProperty("Size"); -+ this.sizeName = this.getResponseProperty("SizeName"); -+ } - } -diff --git a/jslib/common/src/models/api/sendTextApi.ts b/jslib/common/src/models/api/sendTextApi.ts -index 083077c3..56d21450 100644 ---- a/jslib/common/src/models/api/sendTextApi.ts -+++ b/jslib/common/src/models/api/sendTextApi.ts -@@ -1,15 +1,15 @@ --import { BaseResponse } from '../response/baseResponse'; -+import { BaseResponse } from "../response/baseResponse"; - - export class SendTextApi extends BaseResponse { -- text: string; -- hidden: boolean; -+ text: string; -+ hidden: boolean; - -- constructor(data: any = null) { -- super(data); -- if (data == null) { -- return; -- } -- this.text = this.getResponseProperty('Text'); -- this.hidden = this.getResponseProperty('Hidden') || false; -+ constructor(data: any = null) { -+ super(data); -+ if (data == null) { -+ return; - } -+ this.text = this.getResponseProperty("Text"); -+ this.hidden = this.getResponseProperty("Hidden") || false; -+ } - } -diff --git a/jslib/common/src/models/api/ssoConfigApi.ts b/jslib/common/src/models/api/ssoConfigApi.ts -index c846524a..338ab793 100644 ---- a/jslib/common/src/models/api/ssoConfigApi.ts -+++ b/jslib/common/src/models/api/ssoConfigApi.ts -@@ -1,118 +1,124 @@ --import { BaseResponse } from '../response/baseResponse'; -+import { BaseResponse } from "../response/baseResponse"; - - enum SsoType { -- OpenIdConnect = 1, -- Saml2 = 2, -+ OpenIdConnect = 1, -+ Saml2 = 2, - } - - enum OpenIdConnectRedirectBehavior { -- RedirectGet = 0, -- FormPost = 1, -+ RedirectGet = 0, -+ FormPost = 1, - } - - enum Saml2BindingType { -- HttpRedirect = 1, -- HttpPost = 2, -- Artifact = 4, -+ HttpRedirect = 1, -+ HttpPost = 2, -+ Artifact = 4, - } - - enum Saml2NameIdFormat { -- NotConfigured = 0, -- Unspecified = 1, -- EmailAddress = 2, -- X509SubjectName = 3, -- WindowsDomainQualifiedName = 4, -- KerberosPrincipalName = 5, -- EntityIdentifier = 6, -- Persistent = 7, -- Transient = 8, -+ NotConfigured = 0, -+ Unspecified = 1, -+ EmailAddress = 2, -+ X509SubjectName = 3, -+ WindowsDomainQualifiedName = 4, -+ KerberosPrincipalName = 5, -+ EntityIdentifier = 6, -+ Persistent = 7, -+ Transient = 8, - } - - enum Saml2SigningBehavior { -- IfIdpWantAuthnRequestsSigned = 0, -- Always = 1, -- Never = 3, -+ IfIdpWantAuthnRequestsSigned = 0, -+ Always = 1, -+ Never = 3, - } - - export class SsoConfigApi extends BaseResponse { -- configType: SsoType; -- -- keyConnectorEnabled: boolean; -- keyConnectorUrl: string; -- -- // OpenId -- authority: string; -- clientId: string; -- clientSecret: string; -- metadataAddress: string; -- redirectBehavior: OpenIdConnectRedirectBehavior; -- getClaimsFromUserInfoEndpoint: boolean; -- additionalScopes: string; -- additionalUserIdClaimTypes: string; -- additionalEmailClaimTypes: string; -- additionalNameClaimTypes: string; -- acrValues: string; -- expectedReturnAcrValue: string; -- -- // SAML -- spNameIdFormat: Saml2NameIdFormat; -- spOutboundSigningAlgorithm: string; -- spSigningBehavior: Saml2SigningBehavior; -- spMinIncomingSigningAlgorithm: boolean; -- spWantAssertionsSigned: boolean; -- spValidateCertificates: boolean; -- -- idpEntityId: string; -- idpBindingType: Saml2BindingType; -- idpSingleSignOnServiceUrl: string; -- idpSingleLogoutServiceUrl: string; -- idpArtifactResolutionServiceUrl: string; -- idpX509PublicCert: string; -- idpOutboundSigningAlgorithm: string; -- idpAllowUnsolicitedAuthnResponse: boolean; -- idpDisableOutboundLogoutRequests: boolean; -- idpWantAuthnRequestsSigned: boolean; -- -- constructor(data: any = null) { -- super(data); -- if (data == null) { -- return; -- } -- -- this.configType = this.getResponseProperty('ConfigType'); -- -- this.keyConnectorEnabled = this.getResponseProperty('KeyConnectorEnabled'); -- this.keyConnectorUrl = this.getResponseProperty('KeyConnectorUrl'); -- -- this.authority = this.getResponseProperty('Authority'); -- this.clientId = this.getResponseProperty('ClientId'); -- this.clientSecret = this.getResponseProperty('ClientSecret'); -- this.metadataAddress = this.getResponseProperty('MetadataAddress'); -- this.redirectBehavior = this.getResponseProperty('RedirectBehavior'); -- this.getClaimsFromUserInfoEndpoint = this.getResponseProperty('GetClaimsFromUserInfoEndpoint'); -- this.additionalScopes = this.getResponseProperty('AdditionalScopes'); -- this.additionalUserIdClaimTypes = this.getResponseProperty('AdditionalUserIdClaimTypes'); -- this.additionalEmailClaimTypes = this.getResponseProperty('AdditionalEmailClaimTypes'); -- this.additionalNameClaimTypes = this.getResponseProperty('AdditionalNameClaimTypes'); -- this.acrValues = this.getResponseProperty('AcrValues'); -- this.expectedReturnAcrValue = this.getResponseProperty('ExpectedReturnAcrValue'); -- -- this.spNameIdFormat = this.getResponseProperty('SpNameIdFormat'); -- this.spOutboundSigningAlgorithm = this.getResponseProperty('SpOutboundSigningAlgorithm'); -- this.spSigningBehavior = this.getResponseProperty('SpSigningBehavior'); -- this.spMinIncomingSigningAlgorithm = this.getResponseProperty('SpMinIncomingSigningAlgorithm'); -- this.spWantAssertionsSigned = this.getResponseProperty('SpWantAssertionsSigned'); -- this.spValidateCertificates = this.getResponseProperty('SpValidateCertificates'); -- -- this.idpEntityId = this.getResponseProperty('IdpEntityId'); -- this.idpBindingType = this.getResponseProperty('IdpBindingType'); -- this.idpSingleSignOnServiceUrl = this.getResponseProperty('IdpSingleSignOnServiceUrl'); -- this.idpSingleLogoutServiceUrl = this.getResponseProperty('IdpSingleLogoutServiceUrl'); -- this.idpArtifactResolutionServiceUrl = this.getResponseProperty('IdpArtifactResolutionServiceUrl'); -- this.idpX509PublicCert = this.getResponseProperty('IdpX509PublicCert'); -- this.idpOutboundSigningAlgorithm = this.getResponseProperty('IdpOutboundSigningAlgorithm'); -- this.idpAllowUnsolicitedAuthnResponse = this.getResponseProperty('IdpAllowUnsolicitedAuthnResponse'); -- this.idpDisableOutboundLogoutRequests = this.getResponseProperty('IdpDisableOutboundLogoutRequests'); -- this.idpWantAuthnRequestsSigned = this.getResponseProperty('IdpWantAuthnRequestsSigned'); -+ configType: SsoType; -+ -+ keyConnectorEnabled: boolean; -+ keyConnectorUrl: string; -+ -+ // OpenId -+ authority: string; -+ clientId: string; -+ clientSecret: string; -+ metadataAddress: string; -+ redirectBehavior: OpenIdConnectRedirectBehavior; -+ getClaimsFromUserInfoEndpoint: boolean; -+ additionalScopes: string; -+ additionalUserIdClaimTypes: string; -+ additionalEmailClaimTypes: string; -+ additionalNameClaimTypes: string; -+ acrValues: string; -+ expectedReturnAcrValue: string; -+ -+ // SAML -+ spNameIdFormat: Saml2NameIdFormat; -+ spOutboundSigningAlgorithm: string; -+ spSigningBehavior: Saml2SigningBehavior; -+ spMinIncomingSigningAlgorithm: boolean; -+ spWantAssertionsSigned: boolean; -+ spValidateCertificates: boolean; -+ -+ idpEntityId: string; -+ idpBindingType: Saml2BindingType; -+ idpSingleSignOnServiceUrl: string; -+ idpSingleLogoutServiceUrl: string; -+ idpArtifactResolutionServiceUrl: string; -+ idpX509PublicCert: string; -+ idpOutboundSigningAlgorithm: string; -+ idpAllowUnsolicitedAuthnResponse: boolean; -+ idpDisableOutboundLogoutRequests: boolean; -+ idpWantAuthnRequestsSigned: boolean; -+ -+ constructor(data: any = null) { -+ super(data); -+ if (data == null) { -+ return; - } -+ -+ this.configType = this.getResponseProperty("ConfigType"); -+ -+ this.keyConnectorEnabled = this.getResponseProperty("KeyConnectorEnabled"); -+ this.keyConnectorUrl = this.getResponseProperty("KeyConnectorUrl"); -+ -+ this.authority = this.getResponseProperty("Authority"); -+ this.clientId = this.getResponseProperty("ClientId"); -+ this.clientSecret = this.getResponseProperty("ClientSecret"); -+ this.metadataAddress = this.getResponseProperty("MetadataAddress"); -+ this.redirectBehavior = this.getResponseProperty("RedirectBehavior"); -+ this.getClaimsFromUserInfoEndpoint = this.getResponseProperty("GetClaimsFromUserInfoEndpoint"); -+ this.additionalScopes = this.getResponseProperty("AdditionalScopes"); -+ this.additionalUserIdClaimTypes = this.getResponseProperty("AdditionalUserIdClaimTypes"); -+ this.additionalEmailClaimTypes = this.getResponseProperty("AdditionalEmailClaimTypes"); -+ this.additionalNameClaimTypes = this.getResponseProperty("AdditionalNameClaimTypes"); -+ this.acrValues = this.getResponseProperty("AcrValues"); -+ this.expectedReturnAcrValue = this.getResponseProperty("ExpectedReturnAcrValue"); -+ -+ this.spNameIdFormat = this.getResponseProperty("SpNameIdFormat"); -+ this.spOutboundSigningAlgorithm = this.getResponseProperty("SpOutboundSigningAlgorithm"); -+ this.spSigningBehavior = this.getResponseProperty("SpSigningBehavior"); -+ this.spMinIncomingSigningAlgorithm = this.getResponseProperty("SpMinIncomingSigningAlgorithm"); -+ this.spWantAssertionsSigned = this.getResponseProperty("SpWantAssertionsSigned"); -+ this.spValidateCertificates = this.getResponseProperty("SpValidateCertificates"); -+ -+ this.idpEntityId = this.getResponseProperty("IdpEntityId"); -+ this.idpBindingType = this.getResponseProperty("IdpBindingType"); -+ this.idpSingleSignOnServiceUrl = this.getResponseProperty("IdpSingleSignOnServiceUrl"); -+ this.idpSingleLogoutServiceUrl = this.getResponseProperty("IdpSingleLogoutServiceUrl"); -+ this.idpArtifactResolutionServiceUrl = this.getResponseProperty( -+ "IdpArtifactResolutionServiceUrl" -+ ); -+ this.idpX509PublicCert = this.getResponseProperty("IdpX509PublicCert"); -+ this.idpOutboundSigningAlgorithm = this.getResponseProperty("IdpOutboundSigningAlgorithm"); -+ this.idpAllowUnsolicitedAuthnResponse = this.getResponseProperty( -+ "IdpAllowUnsolicitedAuthnResponse" -+ ); -+ this.idpDisableOutboundLogoutRequests = this.getResponseProperty( -+ "IdpDisableOutboundLogoutRequests" -+ ); -+ this.idpWantAuthnRequestsSigned = this.getResponseProperty("IdpWantAuthnRequestsSigned"); -+ } - } -diff --git a/jslib/common/src/models/data/attachmentData.ts b/jslib/common/src/models/data/attachmentData.ts -index dc0f2220..50af03ac 100644 ---- a/jslib/common/src/models/data/attachmentData.ts -+++ b/jslib/common/src/models/data/attachmentData.ts -@@ -1,22 +1,22 @@ --import { AttachmentResponse } from '../response/attachmentResponse'; -+import { AttachmentResponse } from "../response/attachmentResponse"; - - export class AttachmentData { -- id: string; -- url: string; -- fileName: string; -- key: string; -- size: string; -- sizeName: string; -+ id: string; -+ url: string; -+ fileName: string; -+ key: string; -+ size: string; -+ sizeName: string; - -- constructor(response?: AttachmentResponse) { -- if (response == null) { -- return; -- } -- this.id = response.id; -- this.url = response.url; -- this.fileName = response.fileName; -- this.key = response.key; -- this.size = response.size; -- this.sizeName = response.sizeName; -+ constructor(response?: AttachmentResponse) { -+ if (response == null) { -+ return; - } -+ this.id = response.id; -+ this.url = response.url; -+ this.fileName = response.fileName; -+ this.key = response.key; -+ this.size = response.size; -+ this.sizeName = response.sizeName; -+ } - } -diff --git a/jslib/common/src/models/data/cardData.ts b/jslib/common/src/models/data/cardData.ts -index d87c7231..9d90e4b2 100644 ---- a/jslib/common/src/models/data/cardData.ts -+++ b/jslib/common/src/models/data/cardData.ts -@@ -1,23 +1,23 @@ --import { CardApi } from '../api/cardApi'; -+import { CardApi } from "../api/cardApi"; - - export class CardData { -- cardholderName: string; -- brand: string; -- number: string; -- expMonth: string; -- expYear: string; -- code: string; -+ cardholderName: string; -+ brand: string; -+ number: string; -+ expMonth: string; -+ expYear: string; -+ code: string; - -- constructor(data?: CardApi) { -- if (data == null) { -- return; -- } -- -- this.cardholderName = data.cardholderName; -- this.brand = data.brand; -- this.number = data.number; -- this.expMonth = data.expMonth; -- this.expYear = data.expYear; -- this.code = data.code; -+ constructor(data?: CardApi) { -+ if (data == null) { -+ return; - } -+ -+ this.cardholderName = data.cardholderName; -+ this.brand = data.brand; -+ this.number = data.number; -+ this.expMonth = data.expMonth; -+ this.expYear = data.expYear; -+ this.code = data.code; -+ } - } -diff --git a/jslib/common/src/models/data/cipherData.ts b/jslib/common/src/models/data/cipherData.ts -index 679db751..cdda0e46 100644 ---- a/jslib/common/src/models/data/cipherData.ts -+++ b/jslib/common/src/models/data/cipherData.ts -@@ -1,87 +1,87 @@ --import { CipherRepromptType } from '../../enums/cipherRepromptType'; --import { CipherType } from '../../enums/cipherType'; -+import { CipherRepromptType } from "../../enums/cipherRepromptType"; -+import { CipherType } from "../../enums/cipherType"; - --import { AttachmentData } from './attachmentData'; --import { CardData } from './cardData'; --import { FieldData } from './fieldData'; --import { IdentityData } from './identityData'; --import { LoginData } from './loginData'; --import { PasswordHistoryData } from './passwordHistoryData'; --import { SecureNoteData } from './secureNoteData'; -+import { AttachmentData } from "./attachmentData"; -+import { CardData } from "./cardData"; -+import { FieldData } from "./fieldData"; -+import { IdentityData } from "./identityData"; -+import { LoginData } from "./loginData"; -+import { PasswordHistoryData } from "./passwordHistoryData"; -+import { SecureNoteData } from "./secureNoteData"; - --import { CipherResponse } from '../response/cipherResponse'; -+import { CipherResponse } from "../response/cipherResponse"; - - export class CipherData { -- id: string; -- organizationId: string; -- folderId: string; -- userId: string; -- edit: boolean; -- viewPassword: boolean; -- organizationUseTotp: boolean; -- favorite: boolean; -- revisionDate: string; -- type: CipherType; -- sizeName: string; -- name: string; -- notes: string; -- login?: LoginData; -- secureNote?: SecureNoteData; -- card?: CardData; -- identity?: IdentityData; -- fields?: FieldData[]; -- attachments?: AttachmentData[]; -- passwordHistory?: PasswordHistoryData[]; -- collectionIds?: string[]; -- deletedDate: string; -- reprompt: CipherRepromptType; -+ id: string; -+ organizationId: string; -+ folderId: string; -+ userId: string; -+ edit: boolean; -+ viewPassword: boolean; -+ organizationUseTotp: boolean; -+ favorite: boolean; -+ revisionDate: string; -+ type: CipherType; -+ sizeName: string; -+ name: string; -+ notes: string; -+ login?: LoginData; -+ secureNote?: SecureNoteData; -+ card?: CardData; -+ identity?: IdentityData; -+ fields?: FieldData[]; -+ attachments?: AttachmentData[]; -+ passwordHistory?: PasswordHistoryData[]; -+ collectionIds?: string[]; -+ deletedDate: string; -+ reprompt: CipherRepromptType; - -- constructor(response?: CipherResponse, userId?: string, collectionIds?: string[]) { -- if (response == null) { -- return; -- } -+ constructor(response?: CipherResponse, userId?: string, collectionIds?: string[]) { -+ if (response == null) { -+ return; -+ } - -- this.id = response.id; -- this.organizationId = response.organizationId; -- this.folderId = response.folderId; -- this.userId = userId; -- this.edit = response.edit; -- this.viewPassword = response.viewPassword; -- this.organizationUseTotp = response.organizationUseTotp; -- this.favorite = response.favorite; -- this.revisionDate = response.revisionDate; -- this.type = response.type; -- this.name = response.name; -- this.notes = response.notes; -- this.collectionIds = collectionIds != null ? collectionIds : response.collectionIds; -- this.deletedDate = response.deletedDate; -- this.reprompt = response.reprompt; -+ this.id = response.id; -+ this.organizationId = response.organizationId; -+ this.folderId = response.folderId; -+ this.userId = userId; -+ this.edit = response.edit; -+ this.viewPassword = response.viewPassword; -+ this.organizationUseTotp = response.organizationUseTotp; -+ this.favorite = response.favorite; -+ this.revisionDate = response.revisionDate; -+ this.type = response.type; -+ this.name = response.name; -+ this.notes = response.notes; -+ this.collectionIds = collectionIds != null ? collectionIds : response.collectionIds; -+ this.deletedDate = response.deletedDate; -+ this.reprompt = response.reprompt; - -- switch (this.type) { -- case CipherType.Login: -- this.login = new LoginData(response.login); -- break; -- case CipherType.SecureNote: -- this.secureNote = new SecureNoteData(response.secureNote); -- break; -- case CipherType.Card: -- this.card = new CardData(response.card); -- break; -- case CipherType.Identity: -- this.identity = new IdentityData(response.identity); -- break; -- default: -- break; -- } -+ switch (this.type) { -+ case CipherType.Login: -+ this.login = new LoginData(response.login); -+ break; -+ case CipherType.SecureNote: -+ this.secureNote = new SecureNoteData(response.secureNote); -+ break; -+ case CipherType.Card: -+ this.card = new CardData(response.card); -+ break; -+ case CipherType.Identity: -+ this.identity = new IdentityData(response.identity); -+ break; -+ default: -+ break; -+ } - -- if (response.fields != null) { -- this.fields = response.fields.map(f => new FieldData(f)); -- } -- if (response.attachments != null) { -- this.attachments = response.attachments.map(a => new AttachmentData(a)); -- } -- if (response.passwordHistory != null) { -- this.passwordHistory = response.passwordHistory.map(ph => new PasswordHistoryData(ph)); -- } -+ if (response.fields != null) { -+ this.fields = response.fields.map((f) => new FieldData(f)); -+ } -+ if (response.attachments != null) { -+ this.attachments = response.attachments.map((a) => new AttachmentData(a)); -+ } -+ if (response.passwordHistory != null) { -+ this.passwordHistory = response.passwordHistory.map((ph) => new PasswordHistoryData(ph)); - } -+ } - } -diff --git a/jslib/common/src/models/data/collectionData.ts b/jslib/common/src/models/data/collectionData.ts -index cc3ef64b..9e2607ed 100644 ---- a/jslib/common/src/models/data/collectionData.ts -+++ b/jslib/common/src/models/data/collectionData.ts -@@ -1,17 +1,17 @@ --import { CollectionDetailsResponse } from '../response/collectionResponse'; -+import { CollectionDetailsResponse } from "../response/collectionResponse"; - - export class CollectionData { -- id: string; -- organizationId: string; -- name: string; -- externalId: string; -- readOnly: boolean; -+ id: string; -+ organizationId: string; -+ name: string; -+ externalId: string; -+ readOnly: boolean; - -- constructor(response: CollectionDetailsResponse) { -- this.id = response.id; -- this.organizationId = response.organizationId; -- this.name = response.name; -- this.externalId = response.externalId; -- this.readOnly = response.readOnly; -- } -+ constructor(response: CollectionDetailsResponse) { -+ this.id = response.id; -+ this.organizationId = response.organizationId; -+ this.name = response.name; -+ this.externalId = response.externalId; -+ this.readOnly = response.readOnly; -+ } - } -diff --git a/jslib/common/src/models/data/eventData.ts b/jslib/common/src/models/data/eventData.ts -index f8639ad8..c0e38ddc 100644 ---- a/jslib/common/src/models/data/eventData.ts -+++ b/jslib/common/src/models/data/eventData.ts -@@ -1,7 +1,7 @@ --import { EventType } from '../../enums/eventType'; -+import { EventType } from "../../enums/eventType"; - - export class EventData { -- type: EventType; -- cipherId: string; -- date: string; -+ type: EventType; -+ cipherId: string; -+ date: string; - } -diff --git a/jslib/common/src/models/data/fieldData.ts b/jslib/common/src/models/data/fieldData.ts -index e83445f2..7b9fe197 100644 ---- a/jslib/common/src/models/data/fieldData.ts -+++ b/jslib/common/src/models/data/fieldData.ts -@@ -1,21 +1,21 @@ --import { FieldType } from '../../enums/fieldType'; --import { LinkedIdType } from '../../enums/linkedIdType'; -+import { FieldType } from "../../enums/fieldType"; -+import { LinkedIdType } from "../../enums/linkedIdType"; - --import { FieldApi } from '../api/fieldApi'; -+import { FieldApi } from "../api/fieldApi"; - - export class FieldData { -- type: FieldType; -- name: string; -- value: string; -- linkedId: LinkedIdType; -+ type: FieldType; -+ name: string; -+ value: string; -+ linkedId: LinkedIdType; - -- constructor(response?: FieldApi) { -- if (response == null) { -- return; -- } -- this.type = response.type; -- this.name = response.name; -- this.value = response.value; -- this.linkedId = response.linkedId; -+ constructor(response?: FieldApi) { -+ if (response == null) { -+ return; - } -+ this.type = response.type; -+ this.name = response.name; -+ this.value = response.value; -+ this.linkedId = response.linkedId; -+ } - } -diff --git a/jslib/common/src/models/data/folderData.ts b/jslib/common/src/models/data/folderData.ts -index 509267c9..459ba4de 100644 ---- a/jslib/common/src/models/data/folderData.ts -+++ b/jslib/common/src/models/data/folderData.ts -@@ -1,15 +1,15 @@ --import { FolderResponse } from '../response/folderResponse'; -+import { FolderResponse } from "../response/folderResponse"; - - export class FolderData { -- id: string; -- userId: string; -- name: string; -- revisionDate: string; -+ id: string; -+ userId: string; -+ name: string; -+ revisionDate: string; - -- constructor(response: FolderResponse, userId: string) { -- this.userId = userId; -- this.name = response.name; -- this.id = response.id; -- this.revisionDate = response.revisionDate; -- } -+ constructor(response: FolderResponse, userId: string) { -+ this.userId = userId; -+ this.name = response.name; -+ this.id = response.id; -+ this.revisionDate = response.revisionDate; -+ } - } -diff --git a/jslib/common/src/models/data/identityData.ts b/jslib/common/src/models/data/identityData.ts -index 50fff957..02aa7eb6 100644 ---- a/jslib/common/src/models/data/identityData.ts -+++ b/jslib/common/src/models/data/identityData.ts -@@ -1,47 +1,47 @@ --import { IdentityApi } from '../api/identityApi'; -+import { IdentityApi } from "../api/identityApi"; - - export class IdentityData { -- title: string; -- firstName: string; -- middleName: string; -- lastName: string; -- address1: string; -- address2: string; -- address3: string; -- city: string; -- state: string; -- postalCode: string; -- country: string; -- company: string; -- email: string; -- phone: string; -- ssn: string; -- username: string; -- passportNumber: string; -- licenseNumber: string; -+ title: string; -+ firstName: string; -+ middleName: string; -+ lastName: string; -+ address1: string; -+ address2: string; -+ address3: string; -+ city: string; -+ state: string; -+ postalCode: string; -+ country: string; -+ company: string; -+ email: string; -+ phone: string; -+ ssn: string; -+ username: string; -+ passportNumber: string; -+ licenseNumber: string; - -- constructor(data?: IdentityApi) { -- if (data == null) { -- return; -- } -- -- this.title = data.title; -- this.firstName = data.firstName; -- this.middleName = data.middleName; -- this.lastName = data.lastName; -- this.address1 = data.address1; -- this.address2 = data.address2; -- this.address3 = data.address3; -- this.city = data.city; -- this.state = data.state; -- this.postalCode = data.postalCode; -- this.country = data.country; -- this.company = data.company; -- this.email = data.email; -- this.phone = data.phone; -- this.ssn = data.ssn; -- this.username = data.username; -- this.passportNumber = data.passportNumber; -- this.licenseNumber = data.licenseNumber; -+ constructor(data?: IdentityApi) { -+ if (data == null) { -+ return; - } -+ -+ this.title = data.title; -+ this.firstName = data.firstName; -+ this.middleName = data.middleName; -+ this.lastName = data.lastName; -+ this.address1 = data.address1; -+ this.address2 = data.address2; -+ this.address3 = data.address3; -+ this.city = data.city; -+ this.state = data.state; -+ this.postalCode = data.postalCode; -+ this.country = data.country; -+ this.company = data.company; -+ this.email = data.email; -+ this.phone = data.phone; -+ this.ssn = data.ssn; -+ this.username = data.username; -+ this.passportNumber = data.passportNumber; -+ this.licenseNumber = data.licenseNumber; -+ } - } -diff --git a/jslib/common/src/models/data/loginData.ts b/jslib/common/src/models/data/loginData.ts -index 7b5d7027..e51180c8 100644 ---- a/jslib/common/src/models/data/loginData.ts -+++ b/jslib/common/src/models/data/loginData.ts -@@ -1,28 +1,28 @@ --import { LoginApi } from '../api/loginApi'; -+import { LoginApi } from "../api/loginApi"; - --import { LoginUriData } from './loginUriData'; -+import { LoginUriData } from "./loginUriData"; - - export class LoginData { -- uris: LoginUriData[]; -- username: string; -- password: string; -- passwordRevisionDate: string; -- totp: string; -- autofillOnPageLoad: boolean; -+ uris: LoginUriData[]; -+ username: string; -+ password: string; -+ passwordRevisionDate: string; -+ totp: string; -+ autofillOnPageLoad: boolean; - -- constructor(data?: LoginApi) { -- if (data == null) { -- return; -- } -+ constructor(data?: LoginApi) { -+ if (data == null) { -+ return; -+ } - -- this.username = data.username; -- this.password = data.password; -- this.passwordRevisionDate = data.passwordRevisionDate; -- this.totp = data.totp; -- this.autofillOnPageLoad = data.autofillOnPageLoad; -+ this.username = data.username; -+ this.password = data.password; -+ this.passwordRevisionDate = data.passwordRevisionDate; -+ this.totp = data.totp; -+ this.autofillOnPageLoad = data.autofillOnPageLoad; - -- if (data.uris) { -- this.uris = data.uris.map(u => new LoginUriData(u)); -- } -+ if (data.uris) { -+ this.uris = data.uris.map((u) => new LoginUriData(u)); - } -+ } - } -diff --git a/jslib/common/src/models/data/loginUriData.ts b/jslib/common/src/models/data/loginUriData.ts -index e696355c..01cad0bf 100644 ---- a/jslib/common/src/models/data/loginUriData.ts -+++ b/jslib/common/src/models/data/loginUriData.ts -@@ -1,16 +1,16 @@ --import { UriMatchType } from '../../enums/uriMatchType'; -+import { UriMatchType } from "../../enums/uriMatchType"; - --import { LoginUriApi } from '../api/loginUriApi'; -+import { LoginUriApi } from "../api/loginUriApi"; - - export class LoginUriData { -- uri: string; -- match: UriMatchType = null; -+ uri: string; -+ match: UriMatchType = null; - -- constructor(data?: LoginUriApi) { -- if (data == null) { -- return; -- } -- this.uri = data.uri; -- this.match = data.match; -+ constructor(data?: LoginUriApi) { -+ if (data == null) { -+ return; - } -+ this.uri = data.uri; -+ this.match = data.match; -+ } - } -diff --git a/jslib/common/src/models/data/organizationData.ts b/jslib/common/src/models/data/organizationData.ts -index c4061057..3903b0c2 100644 ---- a/jslib/common/src/models/data/organizationData.ts -+++ b/jslib/common/src/models/data/organizationData.ts -@@ -1,80 +1,80 @@ --import { ProfileOrganizationResponse } from '../response/profileOrganizationResponse'; -+import { ProfileOrganizationResponse } from "../response/profileOrganizationResponse"; - --import { OrganizationUserStatusType } from '../../enums/organizationUserStatusType'; --import { OrganizationUserType } from '../../enums/organizationUserType'; --import { ProductType } from '../../enums/productType'; -+import { OrganizationUserStatusType } from "../../enums/organizationUserStatusType"; -+import { OrganizationUserType } from "../../enums/organizationUserType"; -+import { ProductType } from "../../enums/productType"; - --import { PermissionsApi } from '../api/permissionsApi'; -+import { PermissionsApi } from "../api/permissionsApi"; - - export class OrganizationData { -- id: string; -- name: string; -- status: OrganizationUserStatusType; -- type: OrganizationUserType; -- enabled: boolean; -- usePolicies: boolean; -- useGroups: boolean; -- useDirectory: boolean; -- useEvents: boolean; -- useTotp: boolean; -- use2fa: boolean; -- useApi: boolean; -- useSso: boolean; -- useKeyConnector: boolean; -- useResetPassword: boolean; -- selfHost: boolean; -- usersGetPremium: boolean; -- seats: number; -- maxCollections: number; -- maxStorageGb?: number; -- ssoBound: boolean; -- identifier: string; -- permissions: PermissionsApi; -- resetPasswordEnrolled: boolean; -- userId: string; -- hasPublicAndPrivateKeys: boolean; -- providerId: string; -- providerName: string; -- isProviderUser: boolean; -- familySponsorshipFriendlyName: string; -- familySponsorshipAvailable: boolean; -- planProductType: ProductType; -- keyConnectorEnabled: boolean; -- keyConnectorUrl: string; -+ id: string; -+ name: string; -+ status: OrganizationUserStatusType; -+ type: OrganizationUserType; -+ enabled: boolean; -+ usePolicies: boolean; -+ useGroups: boolean; -+ useDirectory: boolean; -+ useEvents: boolean; -+ useTotp: boolean; -+ use2fa: boolean; -+ useApi: boolean; -+ useSso: boolean; -+ useKeyConnector: boolean; -+ useResetPassword: boolean; -+ selfHost: boolean; -+ usersGetPremium: boolean; -+ seats: number; -+ maxCollections: number; -+ maxStorageGb?: number; -+ ssoBound: boolean; -+ identifier: string; -+ permissions: PermissionsApi; -+ resetPasswordEnrolled: boolean; -+ userId: string; -+ hasPublicAndPrivateKeys: boolean; -+ providerId: string; -+ providerName: string; -+ isProviderUser: boolean; -+ familySponsorshipFriendlyName: string; -+ familySponsorshipAvailable: boolean; -+ planProductType: ProductType; -+ keyConnectorEnabled: boolean; -+ keyConnectorUrl: string; - -- constructor(response: ProfileOrganizationResponse) { -- this.id = response.id; -- this.name = response.name; -- this.status = response.status; -- this.type = response.type; -- this.enabled = response.enabled; -- this.usePolicies = response.usePolicies; -- this.useGroups = response.useGroups; -- this.useDirectory = response.useDirectory; -- this.useEvents = response.useEvents; -- this.useTotp = response.useTotp; -- this.use2fa = response.use2fa; -- this.useApi = response.useApi; -- this.useSso = response.useSso; -- this.useKeyConnector = response.useKeyConnector; -- this.useResetPassword = response.useResetPassword; -- this.selfHost = response.selfHost; -- this.usersGetPremium = response.usersGetPremium; -- this.seats = response.seats; -- this.maxCollections = response.maxCollections; -- this.maxStorageGb = response.maxStorageGb; -- this.ssoBound = response.ssoBound; -- this.identifier = response.identifier; -- this.permissions = response.permissions; -- this.resetPasswordEnrolled = response.resetPasswordEnrolled; -- this.userId = response.userId; -- this.hasPublicAndPrivateKeys = response.hasPublicAndPrivateKeys; -- this.providerId = response.providerId; -- this.providerName = response.providerName; -- this.familySponsorshipFriendlyName = response.familySponsorshipFriendlyName; -- this.familySponsorshipAvailable = response.familySponsorshipAvailable; -- this.planProductType = response.planProductType; -- this.keyConnectorEnabled = response.keyConnectorEnabled; -- this.keyConnectorUrl = response.keyConnectorUrl; -- } -+ constructor(response: ProfileOrganizationResponse) { -+ this.id = response.id; -+ this.name = response.name; -+ this.status = response.status; -+ this.type = response.type; -+ this.enabled = response.enabled; -+ this.usePolicies = response.usePolicies; -+ this.useGroups = response.useGroups; -+ this.useDirectory = response.useDirectory; -+ this.useEvents = response.useEvents; -+ this.useTotp = response.useTotp; -+ this.use2fa = response.use2fa; -+ this.useApi = response.useApi; -+ this.useSso = response.useSso; -+ this.useKeyConnector = response.useKeyConnector; -+ this.useResetPassword = response.useResetPassword; -+ this.selfHost = response.selfHost; -+ this.usersGetPremium = response.usersGetPremium; -+ this.seats = response.seats; -+ this.maxCollections = response.maxCollections; -+ this.maxStorageGb = response.maxStorageGb; -+ this.ssoBound = response.ssoBound; -+ this.identifier = response.identifier; -+ this.permissions = response.permissions; -+ this.resetPasswordEnrolled = response.resetPasswordEnrolled; -+ this.userId = response.userId; -+ this.hasPublicAndPrivateKeys = response.hasPublicAndPrivateKeys; -+ this.providerId = response.providerId; -+ this.providerName = response.providerName; -+ this.familySponsorshipFriendlyName = response.familySponsorshipFriendlyName; -+ this.familySponsorshipAvailable = response.familySponsorshipAvailable; -+ this.planProductType = response.planProductType; -+ this.keyConnectorEnabled = response.keyConnectorEnabled; -+ this.keyConnectorUrl = response.keyConnectorUrl; -+ } - } -diff --git a/jslib/common/src/models/data/passwordHistoryData.ts b/jslib/common/src/models/data/passwordHistoryData.ts -index 067c46fd..72436f3d 100644 ---- a/jslib/common/src/models/data/passwordHistoryData.ts -+++ b/jslib/common/src/models/data/passwordHistoryData.ts -@@ -1,15 +1,15 @@ --import { PasswordHistoryResponse } from '../response/passwordHistoryResponse'; -+import { PasswordHistoryResponse } from "../response/passwordHistoryResponse"; - - export class PasswordHistoryData { -- password: string; -- lastUsedDate: string; -+ password: string; -+ lastUsedDate: string; - -- constructor(response?: PasswordHistoryResponse) { -- if (response == null) { -- return; -- } -- -- this.password = response.password; -- this.lastUsedDate = response.lastUsedDate; -+ constructor(response?: PasswordHistoryResponse) { -+ if (response == null) { -+ return; - } -+ -+ this.password = response.password; -+ this.lastUsedDate = response.lastUsedDate; -+ } - } -diff --git a/jslib/common/src/models/data/policyData.ts b/jslib/common/src/models/data/policyData.ts -index 5f012424..46cbbeb9 100644 ---- a/jslib/common/src/models/data/policyData.ts -+++ b/jslib/common/src/models/data/policyData.ts -@@ -1,19 +1,19 @@ --import { PolicyResponse } from '../response/policyResponse'; -+import { PolicyResponse } from "../response/policyResponse"; - --import { PolicyType } from '../../enums/policyType'; -+import { PolicyType } from "../../enums/policyType"; - - export class PolicyData { -- id: string; -- organizationId: string; -- type: PolicyType; -- data: any; -- enabled: boolean; -+ id: string; -+ organizationId: string; -+ type: PolicyType; -+ data: any; -+ enabled: boolean; - -- constructor(response: PolicyResponse) { -- this.id = response.id; -- this.organizationId = response.organizationId; -- this.type = response.type; -- this.data = response.data; -- this.enabled = response.enabled; -- } -+ constructor(response: PolicyResponse) { -+ this.id = response.id; -+ this.organizationId = response.organizationId; -+ this.type = response.type; -+ this.data = response.data; -+ this.enabled = response.enabled; -+ } - } -diff --git a/jslib/common/src/models/data/providerData.ts b/jslib/common/src/models/data/providerData.ts -index 990a0190..7efdab05 100644 ---- a/jslib/common/src/models/data/providerData.ts -+++ b/jslib/common/src/models/data/providerData.ts -@@ -1,24 +1,24 @@ --import { ProfileProviderResponse } from '../response/profileProviderResponse'; -+import { ProfileProviderResponse } from "../response/profileProviderResponse"; - --import { ProviderUserStatusType } from '../../enums/providerUserStatusType'; --import { ProviderUserType } from '../../enums/providerUserType'; -+import { ProviderUserStatusType } from "../../enums/providerUserStatusType"; -+import { ProviderUserType } from "../../enums/providerUserType"; - - export class ProviderData { -- id: string; -- name: string; -- status: ProviderUserStatusType; -- type: ProviderUserType; -- enabled: boolean; -- userId: string; -- useEvents: boolean; -+ id: string; -+ name: string; -+ status: ProviderUserStatusType; -+ type: ProviderUserType; -+ enabled: boolean; -+ userId: string; -+ useEvents: boolean; - -- constructor(response: ProfileProviderResponse) { -- this.id = response.id; -- this.name = response.name; -- this.status = response.status; -- this.type = response.type; -- this.enabled = response.enabled; -- this.userId = response.userId; -- this.useEvents = response.useEvents; -- } -+ constructor(response: ProfileProviderResponse) { -+ this.id = response.id; -+ this.name = response.name; -+ this.status = response.status; -+ this.type = response.type; -+ this.enabled = response.enabled; -+ this.userId = response.userId; -+ this.useEvents = response.useEvents; -+ } - } -diff --git a/jslib/common/src/models/data/secureNoteData.ts b/jslib/common/src/models/data/secureNoteData.ts -index 4a24f2cf..119c77b5 100644 ---- a/jslib/common/src/models/data/secureNoteData.ts -+++ b/jslib/common/src/models/data/secureNoteData.ts -@@ -1,15 +1,15 @@ --import { SecureNoteType } from '../../enums/secureNoteType'; -+import { SecureNoteType } from "../../enums/secureNoteType"; - --import { SecureNoteApi } from '../api/secureNoteApi'; -+import { SecureNoteApi } from "../api/secureNoteApi"; - - export class SecureNoteData { -- type: SecureNoteType; -+ type: SecureNoteType; - -- constructor(data?: SecureNoteApi) { -- if (data == null) { -- return; -- } -- -- this.type = data.type; -+ constructor(data?: SecureNoteApi) { -+ if (data == null) { -+ return; - } -+ -+ this.type = data.type; -+ } - } -diff --git a/jslib/common/src/models/data/sendData.ts b/jslib/common/src/models/data/sendData.ts -index 363429cc..d07dc362 100644 ---- a/jslib/common/src/models/data/sendData.ts -+++ b/jslib/common/src/models/data/sendData.ts -@@ -1,59 +1,59 @@ --import { SendType } from '../../enums/sendType'; -+import { SendType } from "../../enums/sendType"; - --import { SendFileData } from './sendFileData'; --import { SendTextData } from './sendTextData'; -+import { SendFileData } from "./sendFileData"; -+import { SendTextData } from "./sendTextData"; - --import { SendResponse } from '../response/sendResponse'; -+import { SendResponse } from "../response/sendResponse"; - - export class SendData { -- id: string; -- accessId: string; -- userId: string; -- type: SendType; -- name: string; -- notes: string; -- file: SendFileData; -- text: SendTextData; -- key: string; -- maxAccessCount?: number; -- accessCount: number; -- revisionDate: string; -- expirationDate: string; -- deletionDate: string; -- password: string; -- disabled: boolean; -- hideEmail: boolean; -+ id: string; -+ accessId: string; -+ userId: string; -+ type: SendType; -+ name: string; -+ notes: string; -+ file: SendFileData; -+ text: SendTextData; -+ key: string; -+ maxAccessCount?: number; -+ accessCount: number; -+ revisionDate: string; -+ expirationDate: string; -+ deletionDate: string; -+ password: string; -+ disabled: boolean; -+ hideEmail: boolean; - -- constructor(response?: SendResponse, userId?: string) { -- if (response == null) { -- return; -- } -+ constructor(response?: SendResponse, userId?: string) { -+ if (response == null) { -+ return; -+ } - -- this.id = response.id; -- this.accessId = response.accessId; -- this.userId = userId; -- this.type = response.type; -- this.name = response.name; -- this.notes = response.notes; -- this.key = response.key; -- this.maxAccessCount = response.maxAccessCount; -- this.accessCount = response.accessCount; -- this.revisionDate = response.revisionDate; -- this.expirationDate = response.expirationDate; -- this.deletionDate = response.deletionDate; -- this.password = response.password; -- this.disabled = response.disable; -- this.hideEmail = response.hideEmail; -+ this.id = response.id; -+ this.accessId = response.accessId; -+ this.userId = userId; -+ this.type = response.type; -+ this.name = response.name; -+ this.notes = response.notes; -+ this.key = response.key; -+ this.maxAccessCount = response.maxAccessCount; -+ this.accessCount = response.accessCount; -+ this.revisionDate = response.revisionDate; -+ this.expirationDate = response.expirationDate; -+ this.deletionDate = response.deletionDate; -+ this.password = response.password; -+ this.disabled = response.disable; -+ this.hideEmail = response.hideEmail; - -- switch (this.type) { -- case SendType.Text: -- this.text = new SendTextData(response.text); -- break; -- case SendType.File: -- this.file = new SendFileData(response.file); -- break; -- default: -- break; -- } -+ switch (this.type) { -+ case SendType.Text: -+ this.text = new SendTextData(response.text); -+ break; -+ case SendType.File: -+ this.file = new SendFileData(response.file); -+ break; -+ default: -+ break; - } -+ } - } -diff --git a/jslib/common/src/models/data/sendFileData.ts b/jslib/common/src/models/data/sendFileData.ts -index cb7b0850..d7bf8ea4 100644 ---- a/jslib/common/src/models/data/sendFileData.ts -+++ b/jslib/common/src/models/data/sendFileData.ts -@@ -1,21 +1,21 @@ --import { SendFileApi } from '../api/sendFileApi'; -+import { SendFileApi } from "../api/sendFileApi"; - - export class SendFileData { -- id: string; -- fileName: string; -- key: string; -- size: string; -- sizeName: string; -+ id: string; -+ fileName: string; -+ key: string; -+ size: string; -+ sizeName: string; - -- constructor(data?: SendFileApi) { -- if (data == null) { -- return; -- } -- -- this.id = data.id; -- this.fileName = data.fileName; -- this.key = data.key; -- this.size = data.size; -- this.sizeName = data.sizeName; -+ constructor(data?: SendFileApi) { -+ if (data == null) { -+ return; - } -+ -+ this.id = data.id; -+ this.fileName = data.fileName; -+ this.key = data.key; -+ this.size = data.size; -+ this.sizeName = data.sizeName; -+ } - } -diff --git a/jslib/common/src/models/data/sendTextData.ts b/jslib/common/src/models/data/sendTextData.ts -index 1d34adde..fedf2ed6 100644 ---- a/jslib/common/src/models/data/sendTextData.ts -+++ b/jslib/common/src/models/data/sendTextData.ts -@@ -1,15 +1,15 @@ --import { SendTextApi } from '../api/sendTextApi'; -+import { SendTextApi } from "../api/sendTextApi"; - - export class SendTextData { -- text: string; -- hidden: boolean; -+ text: string; -+ hidden: boolean; - -- constructor(data?: SendTextApi) { -- if (data == null) { -- return; -- } -- -- this.text = data.text; -- this.hidden = data.hidden; -+ constructor(data?: SendTextApi) { -+ if (data == null) { -+ return; - } -+ -+ this.text = data.text; -+ this.hidden = data.hidden; -+ } - } -diff --git a/jslib/common/src/models/domain/account.ts b/jslib/common/src/models/domain/account.ts -new file mode 100644 -index 00000000..51be98be ---- /dev/null -+++ b/jslib/common/src/models/domain/account.ts -@@ -0,0 +1,177 @@ -+import { OrganizationData } from "../data/organizationData"; -+ -+import { AuthenticationStatus } from "../../enums/authenticationStatus"; -+import { KdfType } from "../../enums/kdfType"; -+import { UriMatchType } from "../../enums/uriMatchType"; -+ -+import { CipherView } from "../view/cipherView"; -+import { CollectionView } from "../view/collectionView"; -+import { FolderView } from "../view/folderView"; -+import { SendView } from "../view/sendView"; -+ -+import { EncString } from "./encString"; -+import { GeneratedPasswordHistory } from "./generatedPasswordHistory"; -+import { Policy } from "./policy"; -+import { SymmetricCryptoKey } from "./symmetricCryptoKey"; -+ -+import { CipherData } from "../data/cipherData"; -+import { CollectionData } from "../data/collectionData"; -+import { EventData } from "../data/eventData"; -+import { FolderData } from "../data/folderData"; -+import { PolicyData } from "../data/policyData"; -+import { ProviderData } from "../data/providerData"; -+import { SendData } from "../data/sendData"; -+import { EnvironmentUrls } from "./environmentUrls"; -+ -+export class EncryptionPair { -+ encrypted?: TEncrypted; -+ decrypted?: TDecrypted; -+} -+ -+export class DataEncryptionPair { -+ encrypted?: { [id: string]: TEncrypted }; -+ decrypted?: TDecrypted[]; -+} -+ -+export class AccountData { -+ ciphers?: DataEncryptionPair = new DataEncryptionPair< -+ CipherData, -+ CipherView -+ >(); -+ folders?: DataEncryptionPair = new DataEncryptionPair< -+ FolderData, -+ FolderView -+ >(); -+ localData?: any; -+ sends?: DataEncryptionPair = new DataEncryptionPair(); -+ collections?: DataEncryptionPair = new DataEncryptionPair< -+ CollectionData, -+ CollectionView -+ >(); -+ policies?: DataEncryptionPair = new DataEncryptionPair(); -+ passwordGenerationHistory?: EncryptionPair< -+ GeneratedPasswordHistory[], -+ GeneratedPasswordHistory[] -+ > = new EncryptionPair(); -+ addEditCipherInfo?: any; -+ collapsedGroupings?: Set; -+ eventCollection?: EventData[]; -+ organizations?: { [id: string]: OrganizationData }; -+ providers?: { [id: string]: ProviderData }; -+} -+ -+export class AccountKeys { -+ cryptoMasterKey?: SymmetricCryptoKey; -+ cryptoMasterKeyAuto?: string; -+ cryptoMasterKeyB64?: string; -+ cryptoMasterKeyBiometric?: string; -+ cryptoSymmetricKey?: EncryptionPair = new EncryptionPair< -+ string, -+ SymmetricCryptoKey -+ >(); -+ organizationKeys?: EncryptionPair> = new EncryptionPair< -+ any, -+ Map -+ >(); -+ providerKeys?: EncryptionPair> = new EncryptionPair< -+ any, -+ Map -+ >(); -+ privateKey?: EncryptionPair = new EncryptionPair(); -+ legacyEtmKey?: SymmetricCryptoKey; -+ publicKey?: ArrayBuffer; -+ apiKeyClientSecret?: string; -+} -+ -+export class AccountProfile { -+ apiKeyClientId?: string; -+ authenticationStatus?: AuthenticationStatus; -+ convertAccountToKeyConnector?: boolean; -+ email?: string; -+ emailVerified?: boolean; -+ entityId?: string; -+ entityType?: string; -+ everBeenUnlocked?: boolean; -+ forcePasswordReset?: boolean; -+ hasPremiumPersonally?: boolean; -+ lastActive?: number; -+ lastSync?: string; -+ userId?: string; -+ usesKeyConnector?: boolean; -+ keyHash?: string; -+ kdfIterations?: number; -+ kdfType?: KdfType; -+} -+ -+export class AccountSettings { -+ autoConfirmFingerPrints?: boolean; -+ autoFillOnPageLoadDefault?: boolean; -+ biometricLocked?: boolean; -+ biometricUnlock?: boolean; -+ clearClipboard?: number; -+ defaultUriMatch?: UriMatchType; -+ disableAddLoginNotification?: boolean; -+ disableAutoBiometricsPrompt?: boolean; -+ disableAutoTotpCopy?: boolean; -+ disableBadgeCounter?: boolean; -+ disableChangedPasswordNotification?: boolean; -+ disableContextMenuItem?: boolean; -+ disableGa?: boolean; -+ dontShowCardsCurrentTab?: boolean; -+ dontShowIdentitiesCurrentTab?: boolean; -+ enableAlwaysOnTop?: boolean; -+ enableAutoFillOnPageLoad?: boolean; -+ enableBiometric?: boolean; -+ enableFullWidth?: boolean; -+ enableGravitars?: boolean; -+ environmentUrls: EnvironmentUrls = new EnvironmentUrls(); -+ equivalentDomains?: any; -+ minimizeOnCopyToClipboard?: boolean; -+ neverDomains?: { [id: string]: any }; -+ passwordGenerationOptions?: any; -+ pinProtected?: EncryptionPair = new EncryptionPair(); -+ protectedPin?: string; -+ settings?: any; // TODO: Merge whatever is going on here into the AccountSettings model properly -+ vaultTimeout?: number; -+ vaultTimeoutAction?: string = "lock"; -+} -+ -+export class AccountTokens { -+ accessToken?: string; -+ decodedToken?: any; -+ refreshToken?: string; -+ securityStamp?: string; -+} -+ -+export class Account { -+ data?: AccountData = new AccountData(); -+ keys?: AccountKeys = new AccountKeys(); -+ profile?: AccountProfile = new AccountProfile(); -+ settings?: AccountSettings = new AccountSettings(); -+ tokens?: AccountTokens = new AccountTokens(); -+ -+ constructor(init: Partial) { -+ Object.assign(this, { -+ data: { -+ ...new AccountData(), -+ ...init?.data, -+ }, -+ keys: { -+ ...new AccountKeys(), -+ ...init?.keys, -+ }, -+ profile: { -+ ...new AccountProfile(), -+ ...init?.profile, -+ }, -+ settings: { -+ ...new AccountSettings(), -+ ...init?.settings, -+ }, -+ tokens: { -+ ...new AccountTokens(), -+ ...init?.tokens, -+ }, -+ }); -+ } -+} -diff --git a/jslib/common/src/models/domain/attachment.ts b/jslib/common/src/models/domain/attachment.ts -index 9f08a9df..ceafce3e 100644 ---- a/jslib/common/src/models/domain/attachment.ts -+++ b/jslib/common/src/models/domain/attachment.ts -@@ -1,75 +1,91 @@ --import { AttachmentData } from '../data/attachmentData'; -+import { AttachmentData } from "../data/attachmentData"; - --import { AttachmentView } from '../view/attachmentView'; -+import { AttachmentView } from "../view/attachmentView"; - --import Domain from './domainBase'; --import { EncString } from './encString'; --import { SymmetricCryptoKey } from './symmetricCryptoKey'; -+import Domain from "./domainBase"; -+import { EncString } from "./encString"; -+import { SymmetricCryptoKey } from "./symmetricCryptoKey"; - --import { CryptoService } from '../../abstractions/crypto.service'; -+import { CryptoService } from "../../abstractions/crypto.service"; - --import { Utils } from '../../misc/utils'; -+import { Utils } from "../../misc/utils"; - - export class Attachment extends Domain { -- id: string; -- url: string; -- size: string; -- sizeName: string; -- key: EncString; -- fileName: EncString; -+ id: string; -+ url: string; -+ size: string; -+ sizeName: string; -+ key: EncString; -+ fileName: EncString; - -- constructor(obj?: AttachmentData, alreadyEncrypted: boolean = false) { -- super(); -- if (obj == null) { -- return; -- } -- -- this.size = obj.size; -- this.buildDomainModel(this, obj, { -- id: null, -- url: null, -- sizeName: null, -- fileName: null, -- key: null, -- }, alreadyEncrypted, ['id', 'url', 'sizeName']); -+ constructor(obj?: AttachmentData, alreadyEncrypted: boolean = false) { -+ super(); -+ if (obj == null) { -+ return; - } - -- async decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { -- const view = await this.decryptObj(new AttachmentView(this), { -- fileName: null, -- }, orgId, encKey); -+ this.size = obj.size; -+ this.buildDomainModel( -+ this, -+ obj, -+ { -+ id: null, -+ url: null, -+ sizeName: null, -+ fileName: null, -+ key: null, -+ }, -+ alreadyEncrypted, -+ ["id", "url", "sizeName"] -+ ); -+ } - -- if (this.key != null) { -- let cryptoService: CryptoService; -- const containerService = (Utils.global as any).bitwardenContainerService; -- if (containerService) { -- cryptoService = containerService.getCryptoService(); -- } else { -- throw new Error('global bitwardenContainerService not initialized.'); -- } -+ async decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { -+ const view = await this.decryptObj( -+ new AttachmentView(this), -+ { -+ fileName: null, -+ }, -+ orgId, -+ encKey -+ ); - -- try { -- const orgKey = await cryptoService.getOrgKey(orgId); -- const decValue = await cryptoService.decryptToBytes(this.key, orgKey ?? encKey); -- view.key = new SymmetricCryptoKey(decValue); -- } catch (e) { -- // TODO: error? -- } -- } -+ if (this.key != null) { -+ let cryptoService: CryptoService; -+ const containerService = (Utils.global as any).bitwardenContainerService; -+ if (containerService) { -+ cryptoService = containerService.getCryptoService(); -+ } else { -+ throw new Error("global bitwardenContainerService not initialized."); -+ } - -- return view; -+ try { -+ const orgKey = await cryptoService.getOrgKey(orgId); -+ const decValue = await cryptoService.decryptToBytes(this.key, orgKey ?? encKey); -+ view.key = new SymmetricCryptoKey(decValue); -+ } catch (e) { -+ // TODO: error? -+ } - } - -- toAttachmentData(): AttachmentData { -- const a = new AttachmentData(); -- a.size = this.size; -- this.buildDataModel(this, a, { -- id: null, -- url: null, -- sizeName: null, -- fileName: null, -- key: null, -- }, ['id', 'url', 'sizeName']); -- return a; -- } -+ return view; -+ } -+ -+ toAttachmentData(): AttachmentData { -+ const a = new AttachmentData(); -+ a.size = this.size; -+ this.buildDataModel( -+ this, -+ a, -+ { -+ id: null, -+ url: null, -+ sizeName: null, -+ fileName: null, -+ key: null, -+ }, -+ ["id", "url", "sizeName"] -+ ); -+ return a; -+ } - } -diff --git a/jslib/common/src/models/domain/authResult.ts b/jslib/common/src/models/domain/authResult.ts -index 7c5a39c1..eadad50f 100644 ---- a/jslib/common/src/models/domain/authResult.ts -+++ b/jslib/common/src/models/domain/authResult.ts -@@ -1,9 +1,9 @@ --import { TwoFactorProviderType } from '../../enums/twoFactorProviderType'; -+import { TwoFactorProviderType } from "../../enums/twoFactorProviderType"; - - export class AuthResult { -- twoFactor: boolean = false; -- captchaSiteKey: string = ''; -- resetMasterPassword: boolean = false; -- forcePasswordReset: boolean = false; -- twoFactorProviders: Map = null; -+ twoFactor: boolean = false; -+ captchaSiteKey: string = ""; -+ resetMasterPassword: boolean = false; -+ forcePasswordReset: boolean = false; -+ twoFactorProviders: Map = null; - } -diff --git a/jslib/common/src/models/domain/card.ts b/jslib/common/src/models/domain/card.ts -index da703608..62315d63 100644 ---- a/jslib/common/src/models/domain/card.ts -+++ b/jslib/common/src/models/domain/card.ts -@@ -1,56 +1,67 @@ --import { CardData } from '../data/cardData'; -+import { CardData } from "../data/cardData"; - --import Domain from './domainBase'; --import { EncString } from './encString'; -+import Domain from "./domainBase"; -+import { EncString } from "./encString"; - --import { CardView } from '../view/cardView'; --import { SymmetricCryptoKey } from './symmetricCryptoKey'; -+import { CardView } from "../view/cardView"; -+import { SymmetricCryptoKey } from "./symmetricCryptoKey"; - - export class Card extends Domain { -- cardholderName: EncString; -- brand: EncString; -- number: EncString; -- expMonth: EncString; -- expYear: EncString; -- code: EncString; -+ cardholderName: EncString; -+ brand: EncString; -+ number: EncString; -+ expMonth: EncString; -+ expYear: EncString; -+ code: EncString; - -- constructor(obj?: CardData, alreadyEncrypted: boolean = false) { -- super(); -- if (obj == null) { -- return; -- } -- -- this.buildDomainModel(this, obj, { -- cardholderName: null, -- brand: null, -- number: null, -- expMonth: null, -- expYear: null, -- code: null, -- }, alreadyEncrypted, []); -+ constructor(obj?: CardData, alreadyEncrypted: boolean = false) { -+ super(); -+ if (obj == null) { -+ return; - } - -- decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { -- return this.decryptObj(new CardView(this), { -- cardholderName: null, -- brand: null, -- number: null, -- expMonth: null, -- expYear: null, -- code: null, -- }, orgId, encKey); -- } -+ this.buildDomainModel( -+ this, -+ obj, -+ { -+ cardholderName: null, -+ brand: null, -+ number: null, -+ expMonth: null, -+ expYear: null, -+ code: null, -+ }, -+ alreadyEncrypted, -+ [] -+ ); -+ } - -- toCardData(): CardData { -- const c = new CardData(); -- this.buildDataModel(this, c, { -- cardholderName: null, -- brand: null, -- number: null, -- expMonth: null, -- expYear: null, -- code: null, -- }); -- return c; -- } -+ decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { -+ return this.decryptObj( -+ new CardView(this), -+ { -+ cardholderName: null, -+ brand: null, -+ number: null, -+ expMonth: null, -+ expYear: null, -+ code: null, -+ }, -+ orgId, -+ encKey -+ ); -+ } -+ -+ toCardData(): CardData { -+ const c = new CardData(); -+ this.buildDataModel(this, c, { -+ cardholderName: null, -+ brand: null, -+ number: null, -+ expMonth: null, -+ expYear: null, -+ code: null, -+ }); -+ return c; -+ } - } -diff --git a/jslib/common/src/models/domain/cipher.ts b/jslib/common/src/models/domain/cipher.ts -index 1eeb0387..14a7a14a 100644 ---- a/jslib/common/src/models/domain/cipher.ts -+++ b/jslib/common/src/models/domain/cipher.ts -@@ -1,224 +1,241 @@ --import { CipherRepromptType } from '../../enums/cipherRepromptType'; --import { CipherType } from '../../enums/cipherType'; -+import { CipherRepromptType } from "../../enums/cipherRepromptType"; -+import { CipherType } from "../../enums/cipherType"; - --import { CipherData } from '../data/cipherData'; -+import { CipherData } from "../data/cipherData"; - --import { CipherView } from '../view/cipherView'; -+import { CipherView } from "../view/cipherView"; - --import { Attachment } from './attachment'; --import { Card } from './card'; --import Domain from './domainBase'; --import { EncString } from './encString'; --import { Field } from './field'; --import { Identity } from './identity'; --import { Login } from './login'; --import { Password } from './password'; --import { SecureNote } from './secureNote'; --import { SymmetricCryptoKey } from './symmetricCryptoKey'; -+import { Attachment } from "./attachment"; -+import { Card } from "./card"; -+import Domain from "./domainBase"; -+import { EncString } from "./encString"; -+import { Field } from "./field"; -+import { Identity } from "./identity"; -+import { Login } from "./login"; -+import { Password } from "./password"; -+import { SecureNote } from "./secureNote"; -+import { SymmetricCryptoKey } from "./symmetricCryptoKey"; - - export class Cipher extends Domain { -- id: string; -- organizationId: string; -- folderId: string; -- name: EncString; -- notes: EncString; -- type: CipherType; -- favorite: boolean; -- organizationUseTotp: boolean; -- edit: boolean; -- viewPassword: boolean; -- revisionDate: Date; -- localData: any; -- login: Login; -- identity: Identity; -- card: Card; -- secureNote: SecureNote; -- attachments: Attachment[]; -- fields: Field[]; -- passwordHistory: Password[]; -- collectionIds: string[]; -- deletedDate: Date; -- reprompt: CipherRepromptType; -- -- constructor(obj?: CipherData, alreadyEncrypted: boolean = false, localData: any = null) { -- super(); -- if (obj == null) { -- return; -- } -- -- this.buildDomainModel(this, obj, { -- id: null, -- userId: null, -- organizationId: null, -- folderId: null, -- name: null, -- notes: null, -- }, alreadyEncrypted, ['id', 'userId', 'organizationId', 'folderId']); -- -- this.type = obj.type; -- this.favorite = obj.favorite; -- this.organizationUseTotp = obj.organizationUseTotp; -- this.edit = obj.edit; -- if (obj.viewPassword != null) { -- this.viewPassword = obj.viewPassword; -- } else { -- this.viewPassword = true; // Default for already synced Ciphers without viewPassword -- } -- this.revisionDate = obj.revisionDate != null ? new Date(obj.revisionDate) : null; -- this.collectionIds = obj.collectionIds; -- this.localData = localData; -- this.deletedDate = obj.deletedDate != null ? new Date(obj.deletedDate) : null; -- this.reprompt = obj.reprompt; -- -- switch (this.type) { -- case CipherType.Login: -- this.login = new Login(obj.login, alreadyEncrypted); -- break; -- case CipherType.SecureNote: -- this.secureNote = new SecureNote(obj.secureNote, alreadyEncrypted); -- break; -- case CipherType.Card: -- this.card = new Card(obj.card, alreadyEncrypted); -- break; -- case CipherType.Identity: -- this.identity = new Identity(obj.identity, alreadyEncrypted); -- break; -- default: -- break; -- } -- -- if (obj.attachments != null) { -- this.attachments = obj.attachments.map(a => new Attachment(a, alreadyEncrypted)); -- } else { -- this.attachments = null; -- } -- -- if (obj.fields != null) { -- this.fields = obj.fields.map(f => new Field(f, alreadyEncrypted)); -- } else { -- this.fields = null; -- } -- -- if (obj.passwordHistory != null) { -- this.passwordHistory = obj.passwordHistory.map(ph => new Password(ph, alreadyEncrypted)); -- } else { -- this.passwordHistory = null; -- } -+ id: string; -+ organizationId: string; -+ folderId: string; -+ name: EncString; -+ notes: EncString; -+ type: CipherType; -+ favorite: boolean; -+ organizationUseTotp: boolean; -+ edit: boolean; -+ viewPassword: boolean; -+ revisionDate: Date; -+ localData: any; -+ login: Login; -+ identity: Identity; -+ card: Card; -+ secureNote: SecureNote; -+ attachments: Attachment[]; -+ fields: Field[]; -+ passwordHistory: Password[]; -+ collectionIds: string[]; -+ deletedDate: Date; -+ reprompt: CipherRepromptType; -+ -+ constructor(obj?: CipherData, alreadyEncrypted: boolean = false, localData: any = null) { -+ super(); -+ if (obj == null) { -+ return; - } - -- async decrypt(encKey?: SymmetricCryptoKey): Promise { -- const model = new CipherView(this); -- -- await this.decryptObj(model, { -- name: null, -- notes: null, -- }, this.organizationId, encKey); -- -- switch (this.type) { -- case CipherType.Login: -- model.login = await this.login.decrypt(this.organizationId, encKey); -- break; -- case CipherType.SecureNote: -- model.secureNote = await this.secureNote.decrypt(this.organizationId, encKey); -- break; -- case CipherType.Card: -- model.card = await this.card.decrypt(this.organizationId, encKey); -- break; -- case CipherType.Identity: -- model.identity = await this.identity.decrypt(this.organizationId, encKey); -- break; -- default: -- break; -- } -- -- const orgId = this.organizationId; -- -- if (this.attachments != null && this.attachments.length > 0) { -- const attachments: any[] = []; -- await this.attachments.reduce((promise, attachment) => { -- return promise.then(() => { -- return attachment.decrypt(orgId, encKey); -- }).then(decAttachment => { -- attachments.push(decAttachment); -- }); -- }, Promise.resolve()); -- model.attachments = attachments; -- } -- -- if (this.fields != null && this.fields.length > 0) { -- const fields: any[] = []; -- await this.fields.reduce((promise, field) => { -- return promise.then(() => { -- return field.decrypt(orgId, encKey); -- }).then(decField => { -- fields.push(decField); -- }); -- }, Promise.resolve()); -- model.fields = fields; -- } -- -- if (this.passwordHistory != null && this.passwordHistory.length > 0) { -- const passwordHistory: any[] = []; -- await this.passwordHistory.reduce((promise, ph) => { -- return promise.then(() => { -- return ph.decrypt(orgId, encKey); -- }).then(decPh => { -- passwordHistory.push(decPh); -- }); -- }, Promise.resolve()); -- model.passwordHistory = passwordHistory; -- } -- -- return model; -+ this.buildDomainModel( -+ this, -+ obj, -+ { -+ id: null, -+ userId: null, -+ organizationId: null, -+ folderId: null, -+ name: null, -+ notes: null, -+ }, -+ alreadyEncrypted, -+ ["id", "userId", "organizationId", "folderId"] -+ ); -+ -+ this.type = obj.type; -+ this.favorite = obj.favorite; -+ this.organizationUseTotp = obj.organizationUseTotp; -+ this.edit = obj.edit; -+ if (obj.viewPassword != null) { -+ this.viewPassword = obj.viewPassword; -+ } else { -+ this.viewPassword = true; // Default for already synced Ciphers without viewPassword -+ } -+ this.revisionDate = obj.revisionDate != null ? new Date(obj.revisionDate) : null; -+ this.collectionIds = obj.collectionIds; -+ this.localData = localData; -+ this.deletedDate = obj.deletedDate != null ? new Date(obj.deletedDate) : null; -+ this.reprompt = obj.reprompt; -+ -+ switch (this.type) { -+ case CipherType.Login: -+ this.login = new Login(obj.login, alreadyEncrypted); -+ break; -+ case CipherType.SecureNote: -+ this.secureNote = new SecureNote(obj.secureNote, alreadyEncrypted); -+ break; -+ case CipherType.Card: -+ this.card = new Card(obj.card, alreadyEncrypted); -+ break; -+ case CipherType.Identity: -+ this.identity = new Identity(obj.identity, alreadyEncrypted); -+ break; -+ default: -+ break; -+ } -+ -+ if (obj.attachments != null) { -+ this.attachments = obj.attachments.map((a) => new Attachment(a, alreadyEncrypted)); -+ } else { -+ this.attachments = null; -+ } -+ -+ if (obj.fields != null) { -+ this.fields = obj.fields.map((f) => new Field(f, alreadyEncrypted)); -+ } else { -+ this.fields = null; -+ } -+ -+ if (obj.passwordHistory != null) { -+ this.passwordHistory = obj.passwordHistory.map((ph) => new Password(ph, alreadyEncrypted)); -+ } else { -+ this.passwordHistory = null; -+ } -+ } -+ -+ async decrypt(encKey?: SymmetricCryptoKey): Promise { -+ const model = new CipherView(this); -+ -+ await this.decryptObj( -+ model, -+ { -+ name: null, -+ notes: null, -+ }, -+ this.organizationId, -+ encKey -+ ); -+ -+ switch (this.type) { -+ case CipherType.Login: -+ model.login = await this.login.decrypt(this.organizationId, encKey); -+ break; -+ case CipherType.SecureNote: -+ model.secureNote = await this.secureNote.decrypt(this.organizationId, encKey); -+ break; -+ case CipherType.Card: -+ model.card = await this.card.decrypt(this.organizationId, encKey); -+ break; -+ case CipherType.Identity: -+ model.identity = await this.identity.decrypt(this.organizationId, encKey); -+ break; -+ default: -+ break; -+ } -+ -+ const orgId = this.organizationId; -+ -+ if (this.attachments != null && this.attachments.length > 0) { -+ const attachments: any[] = []; -+ await this.attachments.reduce((promise, attachment) => { -+ return promise -+ .then(() => { -+ return attachment.decrypt(orgId, encKey); -+ }) -+ .then((decAttachment) => { -+ attachments.push(decAttachment); -+ }); -+ }, Promise.resolve()); -+ model.attachments = attachments; -+ } -+ -+ if (this.fields != null && this.fields.length > 0) { -+ const fields: any[] = []; -+ await this.fields.reduce((promise, field) => { -+ return promise -+ .then(() => { -+ return field.decrypt(orgId, encKey); -+ }) -+ .then((decField) => { -+ fields.push(decField); -+ }); -+ }, Promise.resolve()); -+ model.fields = fields; -+ } -+ -+ if (this.passwordHistory != null && this.passwordHistory.length > 0) { -+ const passwordHistory: any[] = []; -+ await this.passwordHistory.reduce((promise, ph) => { -+ return promise -+ .then(() => { -+ return ph.decrypt(orgId, encKey); -+ }) -+ .then((decPh) => { -+ passwordHistory.push(decPh); -+ }); -+ }, Promise.resolve()); -+ model.passwordHistory = passwordHistory; - } - -- toCipherData(userId: string): CipherData { -- const c = new CipherData(); -- c.id = this.id; -- c.organizationId = this.organizationId; -- c.folderId = this.folderId; -- c.userId = this.organizationId != null ? userId : null; -- c.edit = this.edit; -- c.viewPassword = this.viewPassword; -- c.organizationUseTotp = this.organizationUseTotp; -- c.favorite = this.favorite; -- c.revisionDate = this.revisionDate != null ? this.revisionDate.toISOString() : null; -- c.type = this.type; -- c.collectionIds = this.collectionIds; -- c.deletedDate = this.deletedDate != null ? this.deletedDate.toISOString() : null; -- c.reprompt = this.reprompt; -- -- this.buildDataModel(this, c, { -- name: null, -- notes: null, -- }); -- -- switch (c.type) { -- case CipherType.Login: -- c.login = this.login.toLoginData(); -- break; -- case CipherType.SecureNote: -- c.secureNote = this.secureNote.toSecureNoteData(); -- break; -- case CipherType.Card: -- c.card = this.card.toCardData(); -- break; -- case CipherType.Identity: -- c.identity = this.identity.toIdentityData(); -- break; -- default: -- break; -- } -- -- if (this.fields != null) { -- c.fields = this.fields.map(f => f.toFieldData()); -- } -- if (this.attachments != null) { -- c.attachments = this.attachments.map(a => a.toAttachmentData()); -- } -- if (this.passwordHistory != null) { -- c.passwordHistory = this.passwordHistory.map(ph => ph.toPasswordHistoryData()); -- } -- return c; -+ return model; -+ } -+ -+ toCipherData(userId: string): CipherData { -+ const c = new CipherData(); -+ c.id = this.id; -+ c.organizationId = this.organizationId; -+ c.folderId = this.folderId; -+ c.userId = this.organizationId != null ? userId : null; -+ c.edit = this.edit; -+ c.viewPassword = this.viewPassword; -+ c.organizationUseTotp = this.organizationUseTotp; -+ c.favorite = this.favorite; -+ c.revisionDate = this.revisionDate != null ? this.revisionDate.toISOString() : null; -+ c.type = this.type; -+ c.collectionIds = this.collectionIds; -+ c.deletedDate = this.deletedDate != null ? this.deletedDate.toISOString() : null; -+ c.reprompt = this.reprompt; -+ -+ this.buildDataModel(this, c, { -+ name: null, -+ notes: null, -+ }); -+ -+ switch (c.type) { -+ case CipherType.Login: -+ c.login = this.login.toLoginData(); -+ break; -+ case CipherType.SecureNote: -+ c.secureNote = this.secureNote.toSecureNoteData(); -+ break; -+ case CipherType.Card: -+ c.card = this.card.toCardData(); -+ break; -+ case CipherType.Identity: -+ c.identity = this.identity.toIdentityData(); -+ break; -+ default: -+ break; -+ } -+ -+ if (this.fields != null) { -+ c.fields = this.fields.map((f) => f.toFieldData()); -+ } -+ if (this.attachments != null) { -+ c.attachments = this.attachments.map((a) => a.toAttachmentData()); -+ } -+ if (this.passwordHistory != null) { -+ c.passwordHistory = this.passwordHistory.map((ph) => ph.toPasswordHistoryData()); - } -+ return c; -+ } - } -diff --git a/jslib/common/src/models/domain/collection.ts b/jslib/common/src/models/domain/collection.ts -index 35a014ad..b08ecd0f 100644 ---- a/jslib/common/src/models/domain/collection.ts -+++ b/jslib/common/src/models/domain/collection.ts -@@ -1,37 +1,47 @@ --import { CollectionData } from '../data/collectionData'; -+import { CollectionData } from "../data/collectionData"; - --import { CollectionView } from '../view/collectionView'; -+import { CollectionView } from "../view/collectionView"; - --import Domain from './domainBase'; --import { EncString } from './encString'; -+import Domain from "./domainBase"; -+import { EncString } from "./encString"; - - export class Collection extends Domain { -- id: string; -- organizationId: string; -- name: EncString; -- externalId: string; -- readOnly: boolean; -- hidePasswords: boolean; -+ id: string; -+ organizationId: string; -+ name: EncString; -+ externalId: string; -+ readOnly: boolean; -+ hidePasswords: boolean; - -- constructor(obj?: CollectionData, alreadyEncrypted: boolean = false) { -- super(); -- if (obj == null) { -- return; -- } -- -- this.buildDomainModel(this, obj, { -- id: null, -- organizationId: null, -- name: null, -- externalId: null, -- readOnly: null, -- hidePasswords: null, -- }, alreadyEncrypted, ['id', 'organizationId', 'externalId', 'readOnly', 'hidePasswords']); -+ constructor(obj?: CollectionData, alreadyEncrypted: boolean = false) { -+ super(); -+ if (obj == null) { -+ return; - } - -- decrypt(): Promise { -- return this.decryptObj(new CollectionView(this), { -- name: null, -- }, this.organizationId); -- } -+ this.buildDomainModel( -+ this, -+ obj, -+ { -+ id: null, -+ organizationId: null, -+ name: null, -+ externalId: null, -+ readOnly: null, -+ hidePasswords: null, -+ }, -+ alreadyEncrypted, -+ ["id", "organizationId", "externalId", "readOnly", "hidePasswords"] -+ ); -+ } -+ -+ decrypt(): Promise { -+ return this.decryptObj( -+ new CollectionView(this), -+ { -+ name: null, -+ }, -+ this.organizationId -+ ); -+ } - } -diff --git a/jslib/common/src/models/domain/decryptParameters.ts b/jslib/common/src/models/domain/decryptParameters.ts -index 1274e88d..09a4c1d8 100644 ---- a/jslib/common/src/models/domain/decryptParameters.ts -+++ b/jslib/common/src/models/domain/decryptParameters.ts -@@ -1,8 +1,8 @@ - export class DecryptParameters { -- encKey: T; -- data: T; -- iv: T; -- macKey: T; -- mac: T; -- macData: T; -+ encKey: T; -+ data: T; -+ iv: T; -+ macKey: T; -+ mac: T; -+ macData: T; - } -diff --git a/jslib/common/src/models/domain/domainBase.ts b/jslib/common/src/models/domain/domainBase.ts -index dbf90243..a8c09abe 100644 ---- a/jslib/common/src/models/domain/domainBase.ts -+++ b/jslib/common/src/models/domain/domainBase.ts -@@ -1,66 +1,82 @@ --import { EncString } from './encString'; -+import { EncString } from "./encString"; - --import { View } from '../view/view'; -+import { View } from "../view/view"; - --import { SymmetricCryptoKey } from './symmetricCryptoKey'; -+import { SymmetricCryptoKey } from "./symmetricCryptoKey"; - - export default class Domain { -- protected buildDomainModel(domain: D, dataObj: any, map: any, -- alreadyEncrypted: boolean, notEncList: any[] = []) { -- for (const prop in map) { -- if (!map.hasOwnProperty(prop)) { -- continue; -- } -+ protected buildDomainModel( -+ domain: D, -+ dataObj: any, -+ map: any, -+ alreadyEncrypted: boolean, -+ notEncList: any[] = [] -+ ) { -+ for (const prop in map) { -+ if (!map.hasOwnProperty(prop)) { -+ continue; -+ } - -- const objProp = dataObj[(map[prop] || prop)]; -- if (alreadyEncrypted === true || notEncList.indexOf(prop) > -1) { -- (domain as any)[prop] = objProp ? objProp : null; -- } else { -- (domain as any)[prop] = objProp ? new EncString(objProp) : null; -- } -- } -+ const objProp = dataObj[map[prop] || prop]; -+ if (alreadyEncrypted === true || notEncList.indexOf(prop) > -1) { -+ (domain as any)[prop] = objProp ? objProp : null; -+ } else { -+ (domain as any)[prop] = objProp ? new EncString(objProp) : null; -+ } - } -- protected buildDataModel(domain: D, dataObj: any, map: any, notEncStringList: any[] = []) { -- for (const prop in map) { -- if (!map.hasOwnProperty(prop)) { -- continue; -- } -+ } -+ protected buildDataModel( -+ domain: D, -+ dataObj: any, -+ map: any, -+ notEncStringList: any[] = [] -+ ) { -+ for (const prop in map) { -+ if (!map.hasOwnProperty(prop)) { -+ continue; -+ } - -- const objProp = (domain as any)[(map[prop] || prop)]; -- if (notEncStringList.indexOf(prop) > -1) { -- (dataObj as any)[prop] = objProp != null ? objProp : null; -- } else { -- (dataObj as any)[prop] = objProp != null ? (objProp as EncString).encryptedString : null; -- } -- } -+ const objProp = (domain as any)[map[prop] || prop]; -+ if (notEncStringList.indexOf(prop) > -1) { -+ (dataObj as any)[prop] = objProp != null ? objProp : null; -+ } else { -+ (dataObj as any)[prop] = objProp != null ? (objProp as EncString).encryptedString : null; -+ } - } -+ } - -- protected async decryptObj(viewModel: T, map: any, orgId: string, -- key: SymmetricCryptoKey = null): Promise { -- const promises = []; -- const self: any = this; -+ protected async decryptObj( -+ viewModel: T, -+ map: any, -+ orgId: string, -+ key: SymmetricCryptoKey = null -+ ): Promise { -+ const promises = []; -+ const self: any = this; - -- for (const prop in map) { -- if (!map.hasOwnProperty(prop)) { -- continue; -- } -- -- // tslint:disable-next-line -- (function (theProp) { -- const p = Promise.resolve().then(() => { -- const mapProp = map[theProp] || theProp; -- if (self[mapProp]) { -- return self[mapProp].decrypt(orgId, key); -- } -- return null; -- }).then((val: any) => { -- (viewModel as any)[theProp] = val; -- }); -- promises.push(p); -- })(prop); -- } -+ for (const prop in map) { -+ if (!map.hasOwnProperty(prop)) { -+ continue; -+ } - -- await Promise.all(promises); -- return viewModel; -+ // tslint:disable-next-line -+ (function (theProp) { -+ const p = Promise.resolve() -+ .then(() => { -+ const mapProp = map[theProp] || theProp; -+ if (self[mapProp]) { -+ return self[mapProp].decrypt(orgId, key); -+ } -+ return null; -+ }) -+ .then((val: any) => { -+ (viewModel as any)[theProp] = val; -+ }); -+ promises.push(p); -+ })(prop); - } -+ -+ await Promise.all(promises); -+ return viewModel; -+ } - } -diff --git a/jslib/common/src/models/domain/encArrayBuffer.ts b/jslib/common/src/models/domain/encArrayBuffer.ts -index abc16917..97f47c39 100644 ---- a/jslib/common/src/models/domain/encArrayBuffer.ts -+++ b/jslib/common/src/models/domain/encArrayBuffer.ts -@@ -1,3 +1,3 @@ - export class EncArrayBuffer { -- constructor(public buffer: ArrayBuffer) { } -+ constructor(public buffer: ArrayBuffer) {} - } -diff --git a/jslib/common/src/models/domain/encString.ts b/jslib/common/src/models/domain/encString.ts -index 58811e23..9ce1c394 100644 ---- a/jslib/common/src/models/domain/encString.ts -+++ b/jslib/common/src/models/domain/encString.ts -@@ -1,117 +1,124 @@ --import { EncryptionType } from '../../enums/encryptionType'; -+import { EncryptionType } from "../../enums/encryptionType"; - --import { CryptoService } from '../../abstractions/crypto.service'; -+import { CryptoService } from "../../abstractions/crypto.service"; - --import { Utils } from '../../misc/utils'; -+import { Utils } from "../../misc/utils"; - --import { SymmetricCryptoKey } from './symmetricCryptoKey'; -+import { SymmetricCryptoKey } from "./symmetricCryptoKey"; - - export class EncString { -- encryptedString?: string; -- encryptionType?: EncryptionType; -- decryptedValue?: string; -- data?: string; -- iv?: string; -- mac?: string; -- -- constructor(encryptedStringOrType: string | EncryptionType, data?: string, iv?: string, mac?: string) { -- if (data != null) { -- // data and header -- const encType = encryptedStringOrType as EncryptionType; -- -- if (iv != null) { -- this.encryptedString = encType + '.' + iv + '|' + data; -- } else { -- this.encryptedString = encType + '.' + data; -- } -- -- // mac -- if (mac != null) { -- this.encryptedString += ('|' + mac); -- } -- -- this.encryptionType = encType; -- this.data = data; -- this.iv = iv; -- this.mac = mac; -- -- return; -- } -+ encryptedString?: string; -+ encryptionType?: EncryptionType; -+ decryptedValue?: string; -+ data?: string; -+ iv?: string; -+ mac?: string; -+ -+ constructor( -+ encryptedStringOrType: string | EncryptionType, -+ data?: string, -+ iv?: string, -+ mac?: string -+ ) { -+ if (data != null) { -+ // data and header -+ const encType = encryptedStringOrType as EncryptionType; -+ -+ if (iv != null) { -+ this.encryptedString = encType + "." + iv + "|" + data; -+ } else { -+ this.encryptedString = encType + "." + data; -+ } -+ -+ // mac -+ if (mac != null) { -+ this.encryptedString += "|" + mac; -+ } -+ -+ this.encryptionType = encType; -+ this.data = data; -+ this.iv = iv; -+ this.mac = mac; -+ -+ return; -+ } -+ -+ this.encryptedString = encryptedStringOrType as string; -+ if (!this.encryptedString) { -+ return; -+ } -+ -+ const headerPieces = this.encryptedString.split("."); -+ let encPieces: string[] = null; -+ -+ if (headerPieces.length === 2) { -+ try { -+ this.encryptionType = parseInt(headerPieces[0], null); -+ encPieces = headerPieces[1].split("|"); -+ } catch (e) { -+ return; -+ } -+ } else { -+ encPieces = this.encryptedString.split("|"); -+ this.encryptionType = -+ encPieces.length === 3 -+ ? EncryptionType.AesCbc128_HmacSha256_B64 -+ : EncryptionType.AesCbc256_B64; -+ } - -- this.encryptedString = encryptedStringOrType as string; -- if (!this.encryptedString) { -- return; -+ switch (this.encryptionType) { -+ case EncryptionType.AesCbc128_HmacSha256_B64: -+ case EncryptionType.AesCbc256_HmacSha256_B64: -+ if (encPieces.length !== 3) { -+ return; - } - -- const headerPieces = this.encryptedString.split('.'); -- let encPieces: string[] = null; -- -- if (headerPieces.length === 2) { -- try { -- this.encryptionType = parseInt(headerPieces[0], null); -- encPieces = headerPieces[1].split('|'); -- } catch (e) { -- return; -- } -- } else { -- encPieces = this.encryptedString.split('|'); -- this.encryptionType = encPieces.length === 3 ? EncryptionType.AesCbc128_HmacSha256_B64 : -- EncryptionType.AesCbc256_B64; -+ this.iv = encPieces[0]; -+ this.data = encPieces[1]; -+ this.mac = encPieces[2]; -+ break; -+ case EncryptionType.AesCbc256_B64: -+ if (encPieces.length !== 2) { -+ return; - } - -- switch (this.encryptionType) { -- case EncryptionType.AesCbc128_HmacSha256_B64: -- case EncryptionType.AesCbc256_HmacSha256_B64: -- if (encPieces.length !== 3) { -- return; -- } -- -- this.iv = encPieces[0]; -- this.data = encPieces[1]; -- this.mac = encPieces[2]; -- break; -- case EncryptionType.AesCbc256_B64: -- if (encPieces.length !== 2) { -- return; -- } -- -- this.iv = encPieces[0]; -- this.data = encPieces[1]; -- break; -- case EncryptionType.Rsa2048_OaepSha256_B64: -- case EncryptionType.Rsa2048_OaepSha1_B64: -- if (encPieces.length !== 1) { -- return; -- } -- -- this.data = encPieces[0]; -- break; -- default: -- return; -+ this.iv = encPieces[0]; -+ this.data = encPieces[1]; -+ break; -+ case EncryptionType.Rsa2048_OaepSha256_B64: -+ case EncryptionType.Rsa2048_OaepSha1_B64: -+ if (encPieces.length !== 1) { -+ return; - } -+ -+ this.data = encPieces[0]; -+ break; -+ default: -+ return; - } -+ } - -- async decrypt(orgId: string, key: SymmetricCryptoKey = null): Promise { -- if (this.decryptedValue != null) { -- return this.decryptedValue; -- } -+ async decrypt(orgId: string, key: SymmetricCryptoKey = null): Promise { -+ if (this.decryptedValue != null) { -+ return this.decryptedValue; -+ } - -- let cryptoService: CryptoService; -- const containerService = (Utils.global as any).bitwardenContainerService; -- if (containerService) { -- cryptoService = containerService.getCryptoService(); -- } else { -- throw new Error('global bitwardenContainerService not initialized.'); -- } -+ let cryptoService: CryptoService; -+ const containerService = (Utils.global as any).bitwardenContainerService; -+ if (containerService) { -+ cryptoService = containerService.getCryptoService(); -+ } else { -+ throw new Error("global bitwardenContainerService not initialized."); -+ } - -- try { -- if (key == null) { -- key = await cryptoService.getOrgKey(orgId); -- } -- this.decryptedValue = await cryptoService.decryptToUtf8(this, key); -- } catch (e) { -- this.decryptedValue = '[error: cannot decrypt]'; -- } -- return this.decryptedValue; -+ try { -+ if (key == null) { -+ key = await cryptoService.getOrgKey(orgId); -+ } -+ this.decryptedValue = await cryptoService.decryptToUtf8(this, key); -+ } catch (e) { -+ this.decryptedValue = "[error: cannot decrypt]"; - } -+ return this.decryptedValue; -+ } - } -diff --git a/jslib/common/src/models/domain/encryptedObject.ts b/jslib/common/src/models/domain/encryptedObject.ts -index f21fe300..5ce93dbe 100644 ---- a/jslib/common/src/models/domain/encryptedObject.ts -+++ b/jslib/common/src/models/domain/encryptedObject.ts -@@ -1,8 +1,8 @@ --import { SymmetricCryptoKey } from './symmetricCryptoKey'; -+import { SymmetricCryptoKey } from "./symmetricCryptoKey"; - - export class EncryptedObject { -- iv: ArrayBuffer; -- data: ArrayBuffer; -- mac: ArrayBuffer; -- key: SymmetricCryptoKey; -+ iv: ArrayBuffer; -+ data: ArrayBuffer; -+ mac: ArrayBuffer; -+ key: SymmetricCryptoKey; - } -diff --git a/jslib/common/src/models/domain/environmentUrls.ts b/jslib/common/src/models/domain/environmentUrls.ts -index 2241bd0a..d4fd173c 100644 ---- a/jslib/common/src/models/domain/environmentUrls.ts -+++ b/jslib/common/src/models/domain/environmentUrls.ts -@@ -1,6 +1,10 @@ - export class EnvironmentUrls { -- base: string; -- api: string; -- identity: string; -- events: string; -+ base: string = null; -+ api: string = null; -+ identity: string = null; -+ icons: string = null; -+ notifications: string = null; -+ events: string = null; -+ webVault: string = null; -+ keyConnector: string = null; - } -diff --git a/jslib/common/src/models/domain/field.ts b/jslib/common/src/models/domain/field.ts -index 7174797d..0aaf2a31 100644 ---- a/jslib/common/src/models/domain/field.ts -+++ b/jslib/common/src/models/domain/field.ts -@@ -1,49 +1,65 @@ --import { FieldType } from '../../enums/fieldType'; --import { LinkedIdType } from '../../enums/linkedIdType'; -+import { FieldType } from "../../enums/fieldType"; -+import { LinkedIdType } from "../../enums/linkedIdType"; - --import { FieldData } from '../data/fieldData'; -+import { FieldData } from "../data/fieldData"; - --import Domain from './domainBase'; --import { EncString } from './encString'; -+import Domain from "./domainBase"; -+import { EncString } from "./encString"; - --import { FieldView } from '../view/fieldView'; --import { SymmetricCryptoKey } from './symmetricCryptoKey'; -+import { FieldView } from "../view/fieldView"; -+import { SymmetricCryptoKey } from "./symmetricCryptoKey"; - - export class Field extends Domain { -- name: EncString; -- value: EncString; -- type: FieldType; -- linkedId: LinkedIdType; -- -- constructor(obj?: FieldData, alreadyEncrypted: boolean = false) { -- super(); -- if (obj == null) { -- return; -- } -- -- this.type = obj.type; -- this.linkedId = obj.linkedId; -- this.buildDomainModel(this, obj, { -- name: null, -- value: null, -- }, alreadyEncrypted, []); -- } -+ name: EncString; -+ value: EncString; -+ type: FieldType; -+ linkedId: LinkedIdType; - -- decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { -- return this.decryptObj(new FieldView(this), { -- name: null, -- value: null, -- }, orgId, encKey); -+ constructor(obj?: FieldData, alreadyEncrypted: boolean = false) { -+ super(); -+ if (obj == null) { -+ return; - } - -- toFieldData(): FieldData { -- const f = new FieldData(); -- this.buildDataModel(this, f, { -- name: null, -- value: null, -- type: null, -- linkedId: null, -- }, ['type', 'linkedId']); -- return f; -- } -+ this.type = obj.type; -+ this.linkedId = obj.linkedId; -+ this.buildDomainModel( -+ this, -+ obj, -+ { -+ name: null, -+ value: null, -+ }, -+ alreadyEncrypted, -+ [] -+ ); -+ } -+ -+ decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { -+ return this.decryptObj( -+ new FieldView(this), -+ { -+ name: null, -+ value: null, -+ }, -+ orgId, -+ encKey -+ ); -+ } -+ -+ toFieldData(): FieldData { -+ const f = new FieldData(); -+ this.buildDataModel( -+ this, -+ f, -+ { -+ name: null, -+ value: null, -+ type: null, -+ linkedId: null, -+ }, -+ ["type", "linkedId"] -+ ); -+ return f; -+ } - } -diff --git a/jslib/common/src/models/domain/folder.ts b/jslib/common/src/models/domain/folder.ts -index 7f267c07..4e4a08f2 100644 ---- a/jslib/common/src/models/domain/folder.ts -+++ b/jslib/common/src/models/domain/folder.ts -@@ -1,32 +1,42 @@ --import { FolderData } from '../data/folderData'; -+import { FolderData } from "../data/folderData"; - --import { FolderView } from '../view/folderView'; -+import { FolderView } from "../view/folderView"; - --import Domain from './domainBase'; --import { EncString } from './encString'; -+import Domain from "./domainBase"; -+import { EncString } from "./encString"; - - export class Folder extends Domain { -- id: string; -- name: EncString; -- revisionDate: Date; -+ id: string; -+ name: EncString; -+ revisionDate: Date; - -- constructor(obj?: FolderData, alreadyEncrypted: boolean = false) { -- super(); -- if (obj == null) { -- return; -- } -+ constructor(obj?: FolderData, alreadyEncrypted: boolean = false) { -+ super(); -+ if (obj == null) { -+ return; -+ } - -- this.buildDomainModel(this, obj, { -- id: null, -- name: null, -- }, alreadyEncrypted, ['id']); -+ this.buildDomainModel( -+ this, -+ obj, -+ { -+ id: null, -+ name: null, -+ }, -+ alreadyEncrypted, -+ ["id"] -+ ); - -- this.revisionDate = obj.revisionDate != null ? new Date(obj.revisionDate) : null; -- } -+ this.revisionDate = obj.revisionDate != null ? new Date(obj.revisionDate) : null; -+ } - -- decrypt(): Promise { -- return this.decryptObj(new FolderView(this), { -- name: null, -- }, null); -- } -+ decrypt(): Promise { -+ return this.decryptObj( -+ new FolderView(this), -+ { -+ name: null, -+ }, -+ null -+ ); -+ } - } -diff --git a/jslib/common/src/models/domain/generatedPasswordHistory.ts b/jslib/common/src/models/domain/generatedPasswordHistory.ts -index 1b082565..b4cc9b22 100644 ---- a/jslib/common/src/models/domain/generatedPasswordHistory.ts -+++ b/jslib/common/src/models/domain/generatedPasswordHistory.ts -@@ -1,9 +1,9 @@ - export class GeneratedPasswordHistory { -- password: string; -- date: number; -+ password: string; -+ date: number; - -- constructor(password: string, date: number) { -- this.password = password; -- this.date = date; -- } -+ constructor(password: string, date: number) { -+ this.password = password; -+ this.date = date; -+ } - } -diff --git a/jslib/common/src/models/domain/globalState.ts b/jslib/common/src/models/domain/globalState.ts -new file mode 100644 -index 00000000..326ba11b ---- /dev/null -+++ b/jslib/common/src/models/domain/globalState.ts -@@ -0,0 +1,40 @@ -+import { StateVersion } from "../../enums/stateVersion"; -+import { ThemeType } from "../../enums/themeType"; -+ -+import { EnvironmentUrls } from "./environmentUrls"; -+import { WindowState } from "./windowState"; -+ -+export class GlobalState { -+ enableAlwaysOnTop?: boolean; -+ installedVersion?: string; -+ locale?: string = "en"; -+ organizationInvitation?: any; -+ ssoCodeVerifier?: string; -+ ssoOrganizationIdentifier?: string; -+ ssoState?: string; -+ rememberedEmail?: string; -+ theme?: ThemeType = ThemeType.System; -+ window?: WindowState = new WindowState(); -+ twoFactorToken?: string; -+ disableFavicon?: boolean; -+ biometricAwaitingAcceptance?: boolean; -+ biometricFingerprintValidated?: boolean; -+ vaultTimeout?: number; -+ vaultTimeoutAction?: string; -+ loginRedirect?: any; -+ mainWindowSize?: number; -+ enableBiometrics?: boolean; -+ biometricText?: string; -+ noAutoPromptBiometrics?: boolean; -+ noAutoPromptBiometricsText?: string; -+ stateVersion: StateVersion = StateVersion.One; -+ environmentUrls: EnvironmentUrls = new EnvironmentUrls(); -+ enableTray?: boolean; -+ enableMinimizeToTray?: boolean; -+ enableCloseToTray?: boolean; -+ enableStartToTray?: boolean; -+ openAtLogin?: boolean; -+ alwaysShowDock?: boolean; -+ enableBrowserIntegration?: boolean; -+ enableBrowserIntegrationFingerprint?: boolean; -+} -diff --git a/jslib/common/src/models/domain/identity.ts b/jslib/common/src/models/domain/identity.ts -index df08293c..8584c946 100644 ---- a/jslib/common/src/models/domain/identity.ts -+++ b/jslib/common/src/models/domain/identity.ts -@@ -1,104 +1,115 @@ --import { IdentityData } from '../data/identityData'; -+import { IdentityData } from "../data/identityData"; - --import Domain from './domainBase'; --import { EncString } from './encString'; --import { SymmetricCryptoKey } from './symmetricCryptoKey'; -+import Domain from "./domainBase"; -+import { EncString } from "./encString"; -+import { SymmetricCryptoKey } from "./symmetricCryptoKey"; - --import { IdentityView } from '../view/identityView'; -+import { IdentityView } from "../view/identityView"; - - export class Identity extends Domain { -- title: EncString; -- firstName: EncString; -- middleName: EncString; -- lastName: EncString; -- address1: EncString; -- address2: EncString; -- address3: EncString; -- city: EncString; -- state: EncString; -- postalCode: EncString; -- country: EncString; -- company: EncString; -- email: EncString; -- phone: EncString; -- ssn: EncString; -- username: EncString; -- passportNumber: EncString; -- licenseNumber: EncString; -+ title: EncString; -+ firstName: EncString; -+ middleName: EncString; -+ lastName: EncString; -+ address1: EncString; -+ address2: EncString; -+ address3: EncString; -+ city: EncString; -+ state: EncString; -+ postalCode: EncString; -+ country: EncString; -+ company: EncString; -+ email: EncString; -+ phone: EncString; -+ ssn: EncString; -+ username: EncString; -+ passportNumber: EncString; -+ licenseNumber: EncString; - -- constructor(obj?: IdentityData, alreadyEncrypted: boolean = false) { -- super(); -- if (obj == null) { -- return; -- } -- -- this.buildDomainModel(this, obj, { -- title: null, -- firstName: null, -- middleName: null, -- lastName: null, -- address1: null, -- address2: null, -- address3: null, -- city: null, -- state: null, -- postalCode: null, -- country: null, -- company: null, -- email: null, -- phone: null, -- ssn: null, -- username: null, -- passportNumber: null, -- licenseNumber: null, -- }, alreadyEncrypted, []); -+ constructor(obj?: IdentityData, alreadyEncrypted: boolean = false) { -+ super(); -+ if (obj == null) { -+ return; - } - -- decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { -- return this.decryptObj(new IdentityView(this), { -- title: null, -- firstName: null, -- middleName: null, -- lastName: null, -- address1: null, -- address2: null, -- address3: null, -- city: null, -- state: null, -- postalCode: null, -- country: null, -- company: null, -- email: null, -- phone: null, -- ssn: null, -- username: null, -- passportNumber: null, -- licenseNumber: null, -- }, orgId, encKey); -- } -+ this.buildDomainModel( -+ this, -+ obj, -+ { -+ title: null, -+ firstName: null, -+ middleName: null, -+ lastName: null, -+ address1: null, -+ address2: null, -+ address3: null, -+ city: null, -+ state: null, -+ postalCode: null, -+ country: null, -+ company: null, -+ email: null, -+ phone: null, -+ ssn: null, -+ username: null, -+ passportNumber: null, -+ licenseNumber: null, -+ }, -+ alreadyEncrypted, -+ [] -+ ); -+ } - -- toIdentityData(): IdentityData { -- const i = new IdentityData(); -- this.buildDataModel(this, i, { -- title: null, -- firstName: null, -- middleName: null, -- lastName: null, -- address1: null, -- address2: null, -- address3: null, -- city: null, -- state: null, -- postalCode: null, -- country: null, -- company: null, -- email: null, -- phone: null, -- ssn: null, -- username: null, -- passportNumber: null, -- licenseNumber: null, -- }); -- return i; -- } -+ decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { -+ return this.decryptObj( -+ new IdentityView(this), -+ { -+ title: null, -+ firstName: null, -+ middleName: null, -+ lastName: null, -+ address1: null, -+ address2: null, -+ address3: null, -+ city: null, -+ state: null, -+ postalCode: null, -+ country: null, -+ company: null, -+ email: null, -+ phone: null, -+ ssn: null, -+ username: null, -+ passportNumber: null, -+ licenseNumber: null, -+ }, -+ orgId, -+ encKey -+ ); -+ } -+ -+ toIdentityData(): IdentityData { -+ const i = new IdentityData(); -+ this.buildDataModel(this, i, { -+ title: null, -+ firstName: null, -+ middleName: null, -+ lastName: null, -+ address1: null, -+ address2: null, -+ address3: null, -+ city: null, -+ state: null, -+ postalCode: null, -+ country: null, -+ company: null, -+ email: null, -+ phone: null, -+ ssn: null, -+ username: null, -+ passportNumber: null, -+ licenseNumber: null, -+ }); -+ return i; -+ } - } -diff --git a/jslib/common/src/models/domain/importResult.ts b/jslib/common/src/models/domain/importResult.ts -index 00534f4a..1ac28161 100644 ---- a/jslib/common/src/models/domain/importResult.ts -+++ b/jslib/common/src/models/domain/importResult.ts -@@ -1,13 +1,13 @@ --import { CipherView } from '../view/cipherView'; --import { CollectionView } from '../view/collectionView'; --import { FolderView } from '../view/folderView'; -+import { CipherView } from "../view/cipherView"; -+import { CollectionView } from "../view/collectionView"; -+import { FolderView } from "../view/folderView"; - - export class ImportResult { -- success = false; -- errorMessage: string; -- ciphers: CipherView[] = []; -- folders: FolderView[] = []; -- folderRelationships: [number, number][] = []; -- collections: CollectionView[] = []; -- collectionRelationships: [number, number][] = []; -+ success = false; -+ errorMessage: string; -+ ciphers: CipherView[] = []; -+ folders: FolderView[] = []; -+ folderRelationships: [number, number][] = []; -+ collections: CollectionView[] = []; -+ collectionRelationships: [number, number][] = []; - } -diff --git a/jslib/common/src/models/domain/login.ts b/jslib/common/src/models/domain/login.ts -index 33bd1216..8507e076 100644 ---- a/jslib/common/src/models/domain/login.ts -+++ b/jslib/common/src/models/domain/login.ts -@@ -1,78 +1,91 @@ --import { LoginUri } from './loginUri'; -+import { LoginUri } from "./loginUri"; - --import { LoginData } from '../data/loginData'; -+import { LoginData } from "../data/loginData"; - --import { LoginView } from '../view/loginView'; -+import { LoginView } from "../view/loginView"; - --import Domain from './domainBase'; --import { EncString } from './encString'; --import { SymmetricCryptoKey } from './symmetricCryptoKey'; -+import Domain from "./domainBase"; -+import { EncString } from "./encString"; -+import { SymmetricCryptoKey } from "./symmetricCryptoKey"; - - export class Login extends Domain { -- uris: LoginUri[]; -- username: EncString; -- password: EncString; -- passwordRevisionDate?: Date; -- totp: EncString; -- autofillOnPageLoad: boolean; -+ uris: LoginUri[]; -+ username: EncString; -+ password: EncString; -+ passwordRevisionDate?: Date; -+ totp: EncString; -+ autofillOnPageLoad: boolean; - -- constructor(obj?: LoginData, alreadyEncrypted: boolean = false) { -- super(); -- if (obj == null) { -- return; -- } -+ constructor(obj?: LoginData, alreadyEncrypted: boolean = false) { -+ super(); -+ if (obj == null) { -+ return; -+ } - -- this.passwordRevisionDate = obj.passwordRevisionDate != null ? new Date(obj.passwordRevisionDate) : null; -- this.autofillOnPageLoad = obj.autofillOnPageLoad; -- this.buildDomainModel(this, obj, { -- username: null, -- password: null, -- totp: null, -- }, alreadyEncrypted, []); -+ this.passwordRevisionDate = -+ obj.passwordRevisionDate != null ? new Date(obj.passwordRevisionDate) : null; -+ this.autofillOnPageLoad = obj.autofillOnPageLoad; -+ this.buildDomainModel( -+ this, -+ obj, -+ { -+ username: null, -+ password: null, -+ totp: null, -+ }, -+ alreadyEncrypted, -+ [] -+ ); - -- if (obj.uris) { -- this.uris = []; -- obj.uris.forEach(u => { -- this.uris.push(new LoginUri(u, alreadyEncrypted)); -- }); -- } -+ if (obj.uris) { -+ this.uris = []; -+ obj.uris.forEach((u) => { -+ this.uris.push(new LoginUri(u, alreadyEncrypted)); -+ }); - } -+ } - -- async decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { -- const view = await this.decryptObj(new LoginView(this), { -- username: null, -- password: null, -- totp: null, -- }, orgId, encKey); -- -- if (this.uris != null) { -- view.uris = []; -- for (let i = 0; i < this.uris.length; i++) { -- const uri = await this.uris[i].decrypt(orgId, encKey); -- view.uris.push(uri); -- } -- } -+ async decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { -+ const view = await this.decryptObj( -+ new LoginView(this), -+ { -+ username: null, -+ password: null, -+ totp: null, -+ }, -+ orgId, -+ encKey -+ ); - -- return view; -+ if (this.uris != null) { -+ view.uris = []; -+ for (let i = 0; i < this.uris.length; i++) { -+ const uri = await this.uris[i].decrypt(orgId, encKey); -+ view.uris.push(uri); -+ } - } - -- toLoginData(): LoginData { -- const l = new LoginData(); -- l.passwordRevisionDate = this.passwordRevisionDate != null ? this.passwordRevisionDate.toISOString() : null; -- l.autofillOnPageLoad = this.autofillOnPageLoad; -- this.buildDataModel(this, l, { -- username: null, -- password: null, -- totp: null, -- }); -+ return view; -+ } - -- if (this.uris != null && this.uris.length > 0) { -- l.uris = []; -- this.uris.forEach(u => { -- l.uris.push(u.toLoginUriData()); -- }); -- } -+ toLoginData(): LoginData { -+ const l = new LoginData(); -+ l.passwordRevisionDate = -+ this.passwordRevisionDate != null ? this.passwordRevisionDate.toISOString() : null; -+ l.autofillOnPageLoad = this.autofillOnPageLoad; -+ this.buildDataModel(this, l, { -+ username: null, -+ password: null, -+ totp: null, -+ }); - -- return l; -+ if (this.uris != null && this.uris.length > 0) { -+ l.uris = []; -+ this.uris.forEach((u) => { -+ l.uris.push(u.toLoginUriData()); -+ }); - } -+ -+ return l; -+ } - } -diff --git a/jslib/common/src/models/domain/loginUri.ts b/jslib/common/src/models/domain/loginUri.ts -index 6f7bac1d..a80f7d0e 100644 ---- a/jslib/common/src/models/domain/loginUri.ts -+++ b/jslib/common/src/models/domain/loginUri.ts -@@ -1,40 +1,56 @@ --import { UriMatchType } from '../../enums/uriMatchType'; -+import { UriMatchType } from "../../enums/uriMatchType"; - --import { LoginUriData } from '../data/loginUriData'; -+import { LoginUriData } from "../data/loginUriData"; - --import { LoginUriView } from '../view/loginUriView'; -+import { LoginUriView } from "../view/loginUriView"; - --import Domain from './domainBase'; --import { EncString } from './encString'; --import { SymmetricCryptoKey } from './symmetricCryptoKey'; -+import Domain from "./domainBase"; -+import { EncString } from "./encString"; -+import { SymmetricCryptoKey } from "./symmetricCryptoKey"; - - export class LoginUri extends Domain { -- uri: EncString; -- match: UriMatchType; -- -- constructor(obj?: LoginUriData, alreadyEncrypted: boolean = false) { -- super(); -- if (obj == null) { -- return; -- } -- -- this.match = obj.match; -- this.buildDomainModel(this, obj, { -- uri: null, -- }, alreadyEncrypted, []); -- } -+ uri: EncString; -+ match: UriMatchType; - -- decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { -- return this.decryptObj(new LoginUriView(this), { -- uri: null, -- }, orgId, encKey); -+ constructor(obj?: LoginUriData, alreadyEncrypted: boolean = false) { -+ super(); -+ if (obj == null) { -+ return; - } - -- toLoginUriData(): LoginUriData { -- const u = new LoginUriData(); -- this.buildDataModel(this, u, { -- uri: null, -- }, ['match']); -- return u; -- } -+ this.match = obj.match; -+ this.buildDomainModel( -+ this, -+ obj, -+ { -+ uri: null, -+ }, -+ alreadyEncrypted, -+ [] -+ ); -+ } -+ -+ decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { -+ return this.decryptObj( -+ new LoginUriView(this), -+ { -+ uri: null, -+ }, -+ orgId, -+ encKey -+ ); -+ } -+ -+ toLoginUriData(): LoginUriData { -+ const u = new LoginUriData(); -+ this.buildDataModel( -+ this, -+ u, -+ { -+ uri: null, -+ }, -+ ["match"] -+ ); -+ return u; -+ } - } -diff --git a/jslib/common/src/models/domain/masterPasswordPolicyOptions.ts b/jslib/common/src/models/domain/masterPasswordPolicyOptions.ts -index ed04a455..4ffdc91f 100644 ---- a/jslib/common/src/models/domain/masterPasswordPolicyOptions.ts -+++ b/jslib/common/src/models/domain/masterPasswordPolicyOptions.ts -@@ -1,10 +1,10 @@ --import Domain from './domainBase'; -+import Domain from "./domainBase"; - - export class MasterPasswordPolicyOptions extends Domain { -- minComplexity: number = 0; -- minLength: number = 0; -- requireUpper: boolean = false; -- requireLower: boolean = false; -- requireNumbers: boolean = false; -- requireSpecial: boolean = false; -+ minComplexity: number = 0; -+ minLength: number = 0; -+ requireUpper: boolean = false; -+ requireLower: boolean = false; -+ requireNumbers: boolean = false; -+ requireSpecial: boolean = false; - } -diff --git a/jslib/common/src/models/domain/organization.ts b/jslib/common/src/models/domain/organization.ts -index 492afda5..d21b49b9 100644 ---- a/jslib/common/src/models/domain/organization.ts -+++ b/jslib/common/src/models/domain/organization.ts -@@ -1,169 +1,185 @@ --import { OrganizationData } from '../data/organizationData'; -- --import { OrganizationUserStatusType } from '../../enums/organizationUserStatusType'; --import { OrganizationUserType } from '../../enums/organizationUserType'; --import { ProductType } from '../../enums/productType'; --import { PermissionsApi } from '../api/permissionsApi'; -+import { OrganizationData } from "../data/organizationData"; - -+import { OrganizationUserStatusType } from "../../enums/organizationUserStatusType"; -+import { OrganizationUserType } from "../../enums/organizationUserType"; -+import { ProductType } from "../../enums/productType"; -+import { PermissionsApi } from "../api/permissionsApi"; - - export class Organization { -- id: string; -- name: string; -- status: OrganizationUserStatusType; -- type: OrganizationUserType; -- enabled: boolean; -- usePolicies: boolean; -- useGroups: boolean; -- useDirectory: boolean; -- useEvents: boolean; -- useTotp: boolean; -- use2fa: boolean; -- useApi: boolean; -- useSso: boolean; -- useKeyConnector: boolean; -- useResetPassword: boolean; -- selfHost: boolean; -- usersGetPremium: boolean; -- seats: number; -- maxCollections: number; -- maxStorageGb?: number; -- ssoBound: boolean; -- identifier: string; -- permissions: PermissionsApi; -- resetPasswordEnrolled: boolean; -- userId: string; -- hasPublicAndPrivateKeys: boolean; -- providerId: string; -- providerName: string; -- isProviderUser: boolean; -- familySponsorshipFriendlyName: string; -- familySponsorshipAvailable: boolean; -- planProductType: ProductType; -- keyConnectorEnabled: boolean; -- keyConnectorUrl: string; -- -- constructor(obj?: OrganizationData) { -- if (obj == null) { -- return; -- } -- -- this.id = obj.id; -- this.name = obj.name; -- this.status = obj.status; -- this.type = obj.type; -- this.enabled = obj.enabled; -- this.usePolicies = obj.usePolicies; -- this.useGroups = obj.useGroups; -- this.useDirectory = obj.useDirectory; -- this.useEvents = obj.useEvents; -- this.useTotp = obj.useTotp; -- this.use2fa = obj.use2fa; -- this.useApi = obj.useApi; -- this.useSso = obj.useSso; -- this.useKeyConnector = obj.useKeyConnector; -- this.useResetPassword = obj.useResetPassword; -- this.selfHost = obj.selfHost; -- this.usersGetPremium = obj.usersGetPremium; -- this.seats = obj.seats; -- this.maxCollections = obj.maxCollections; -- this.maxStorageGb = obj.maxStorageGb; -- this.ssoBound = obj.ssoBound; -- this.identifier = obj.identifier; -- this.permissions = obj.permissions; -- this.resetPasswordEnrolled = obj.resetPasswordEnrolled; -- this.userId = obj.userId; -- this.hasPublicAndPrivateKeys = obj.hasPublicAndPrivateKeys; -- this.providerId = obj.providerId; -- this.providerName = obj.providerName; -- this.isProviderUser = obj.isProviderUser; -- this.familySponsorshipFriendlyName = obj.familySponsorshipFriendlyName; -- this.familySponsorshipAvailable = obj.familySponsorshipAvailable; -- this.planProductType = obj.planProductType; -- this.keyConnectorEnabled = obj.keyConnectorEnabled; -- this.keyConnectorUrl = obj.keyConnectorUrl; -- } -- -- get canAccess() { -- if (this.type === OrganizationUserType.Owner) { -- return true; -- } -- return this.enabled && this.status === OrganizationUserStatusType.Confirmed; -- } -- -- get isManager() { -- return this.type === OrganizationUserType.Manager || this.type === OrganizationUserType.Owner || -- this.type === OrganizationUserType.Admin; -- } -- -- get isAdmin() { -- return this.type === OrganizationUserType.Owner || this.type === OrganizationUserType.Admin; -- } -- -- get isOwner() { -- return this.type === OrganizationUserType.Owner || this.isProviderUser; -- } -- -- get canAccessEventLogs() { -- return this.isAdmin || this.permissions.accessEventLogs; -- } -- -- get canAccessImportExport() { -- return this.isAdmin || this.permissions.accessImportExport; -- } -- -- get canAccessReports() { -- return this.isAdmin || this.permissions.accessReports; -- } -- -- get canCreateNewCollections() { -- return this.isManager || (this.permissions.createNewCollections ?? this.permissions.manageAllCollections); -- } -- -- get canEditAnyCollection() { -- return this.isAdmin || (this.permissions.editAnyCollection ?? this.permissions.manageAllCollections); -- } -- -- get canDeleteAnyCollection() { -- return this.isAdmin || (this.permissions.deleteAnyCollection ?? this.permissions.manageAllCollections); -- } -- -- get canViewAllCollections() { -- return this.canCreateNewCollections || this.canEditAnyCollection || this.canDeleteAnyCollection; -- } -- -- get canEditAssignedCollections() { -- return this.isManager || (this.permissions.editAssignedCollections ?? this.permissions.manageAssignedCollections); -- } -- -- get canDeleteAssignedCollections() { -- return this.isManager || (this.permissions.deleteAssignedCollections ?? this.permissions.manageAssignedCollections); -- } -- -- get canViewAssignedCollections() { -- return this.canDeleteAssignedCollections || this.canEditAssignedCollections; -- } -- -- get canManageGroups() { -- return this.isAdmin || this.permissions.manageGroups; -- } -- -- get canManageSso() { -- return this.isAdmin || this.permissions.manageSso; -- } -- -- get canManagePolicies() { -- return this.isAdmin || this.permissions.managePolicies; -- } -- -- get canManageUsers() { -- return this.isAdmin || this.permissions.manageUsers; -- } -- -- get canManageUsersPassword() { -- return this.isAdmin || this.permissions.manageResetPassword; -- } -- -- get isExemptFromPolicies() { -- return this.canManagePolicies; -- } -+ id: string; -+ name: string; -+ status: OrganizationUserStatusType; -+ type: OrganizationUserType; -+ enabled: boolean; -+ usePolicies: boolean; -+ useGroups: boolean; -+ useDirectory: boolean; -+ useEvents: boolean; -+ useTotp: boolean; -+ use2fa: boolean; -+ useApi: boolean; -+ useSso: boolean; -+ useKeyConnector: boolean; -+ useResetPassword: boolean; -+ selfHost: boolean; -+ usersGetPremium: boolean; -+ seats: number; -+ maxCollections: number; -+ maxStorageGb?: number; -+ ssoBound: boolean; -+ identifier: string; -+ permissions: PermissionsApi; -+ resetPasswordEnrolled: boolean; -+ userId: string; -+ hasPublicAndPrivateKeys: boolean; -+ providerId: string; -+ providerName: string; -+ isProviderUser: boolean; -+ familySponsorshipFriendlyName: string; -+ familySponsorshipAvailable: boolean; -+ planProductType: ProductType; -+ keyConnectorEnabled: boolean; -+ keyConnectorUrl: string; -+ -+ constructor(obj?: OrganizationData) { -+ if (obj == null) { -+ return; -+ } -+ -+ this.id = obj.id; -+ this.name = obj.name; -+ this.status = obj.status; -+ this.type = obj.type; -+ this.enabled = obj.enabled; -+ this.usePolicies = obj.usePolicies; -+ this.useGroups = obj.useGroups; -+ this.useDirectory = obj.useDirectory; -+ this.useEvents = obj.useEvents; -+ this.useTotp = obj.useTotp; -+ this.use2fa = obj.use2fa; -+ this.useApi = obj.useApi; -+ this.useSso = obj.useSso; -+ this.useKeyConnector = obj.useKeyConnector; -+ this.useResetPassword = obj.useResetPassword; -+ this.selfHost = obj.selfHost; -+ this.usersGetPremium = obj.usersGetPremium; -+ this.seats = obj.seats; -+ this.maxCollections = obj.maxCollections; -+ this.maxStorageGb = obj.maxStorageGb; -+ this.ssoBound = obj.ssoBound; -+ this.identifier = obj.identifier; -+ this.permissions = obj.permissions; -+ this.resetPasswordEnrolled = obj.resetPasswordEnrolled; -+ this.userId = obj.userId; -+ this.hasPublicAndPrivateKeys = obj.hasPublicAndPrivateKeys; -+ this.providerId = obj.providerId; -+ this.providerName = obj.providerName; -+ this.isProviderUser = obj.isProviderUser; -+ this.familySponsorshipFriendlyName = obj.familySponsorshipFriendlyName; -+ this.familySponsorshipAvailable = obj.familySponsorshipAvailable; -+ this.planProductType = obj.planProductType; -+ this.keyConnectorEnabled = obj.keyConnectorEnabled; -+ this.keyConnectorUrl = obj.keyConnectorUrl; -+ } -+ -+ get canAccess() { -+ if (this.type === OrganizationUserType.Owner) { -+ return true; -+ } -+ return this.enabled && this.status === OrganizationUserStatusType.Confirmed; -+ } -+ -+ get isManager() { -+ return ( -+ this.type === OrganizationUserType.Manager || -+ this.type === OrganizationUserType.Owner || -+ this.type === OrganizationUserType.Admin -+ ); -+ } -+ -+ get isAdmin() { -+ return this.type === OrganizationUserType.Owner || this.type === OrganizationUserType.Admin; -+ } -+ -+ get isOwner() { -+ return this.type === OrganizationUserType.Owner || this.isProviderUser; -+ } -+ -+ get canAccessEventLogs() { -+ return this.isAdmin || this.permissions.accessEventLogs; -+ } -+ -+ get canAccessImportExport() { -+ return this.isAdmin || this.permissions.accessImportExport; -+ } -+ -+ get canAccessReports() { -+ return this.isAdmin || this.permissions.accessReports; -+ } -+ -+ get canCreateNewCollections() { -+ return ( -+ this.isManager || -+ (this.permissions.createNewCollections ?? this.permissions.manageAllCollections) -+ ); -+ } -+ -+ get canEditAnyCollection() { -+ return ( -+ this.isAdmin || (this.permissions.editAnyCollection ?? this.permissions.manageAllCollections) -+ ); -+ } -+ -+ get canDeleteAnyCollection() { -+ return ( -+ this.isAdmin || -+ (this.permissions.deleteAnyCollection ?? this.permissions.manageAllCollections) -+ ); -+ } -+ -+ get canViewAllCollections() { -+ return this.canCreateNewCollections || this.canEditAnyCollection || this.canDeleteAnyCollection; -+ } -+ -+ get canEditAssignedCollections() { -+ return ( -+ this.isManager || -+ (this.permissions.editAssignedCollections ?? this.permissions.manageAssignedCollections) -+ ); -+ } -+ -+ get canDeleteAssignedCollections() { -+ return ( -+ this.isManager || -+ (this.permissions.deleteAssignedCollections ?? this.permissions.manageAssignedCollections) -+ ); -+ } -+ -+ get canViewAssignedCollections() { -+ return this.canDeleteAssignedCollections || this.canEditAssignedCollections; -+ } -+ -+ get canManageGroups() { -+ return this.isAdmin || this.permissions.manageGroups; -+ } -+ -+ get canManageSso() { -+ return this.isAdmin || this.permissions.manageSso; -+ } -+ -+ get canManagePolicies() { -+ return this.isAdmin || this.permissions.managePolicies; -+ } -+ -+ get canManageUsers() { -+ return this.isAdmin || this.permissions.manageUsers; -+ } -+ -+ get canManageUsersPassword() { -+ return this.isAdmin || this.permissions.manageResetPassword; -+ } -+ -+ get isExemptFromPolicies() { -+ return this.canManagePolicies; -+ } - } -diff --git a/jslib/common/src/models/domain/password.ts b/jslib/common/src/models/domain/password.ts -index 59c9670d..94e098c6 100644 ---- a/jslib/common/src/models/domain/password.ts -+++ b/jslib/common/src/models/domain/password.ts -@@ -1,39 +1,49 @@ --import { PasswordHistoryData } from '../data/passwordHistoryData'; -+import { PasswordHistoryData } from "../data/passwordHistoryData"; - --import Domain from './domainBase'; --import { EncString } from './encString'; -+import Domain from "./domainBase"; -+import { EncString } from "./encString"; - --import { PasswordHistoryView } from '../view/passwordHistoryView'; --import { SymmetricCryptoKey } from './symmetricCryptoKey'; -+import { PasswordHistoryView } from "../view/passwordHistoryView"; -+import { SymmetricCryptoKey } from "./symmetricCryptoKey"; - - export class Password extends Domain { -- password: EncString; -- lastUsedDate: Date; -+ password: EncString; -+ lastUsedDate: Date; - -- constructor(obj?: PasswordHistoryData, alreadyEncrypted: boolean = false) { -- super(); -- if (obj == null) { -- return; -- } -- -- this.buildDomainModel(this, obj, { -- password: null, -- }, alreadyEncrypted); -- this.lastUsedDate = new Date(obj.lastUsedDate); -+ constructor(obj?: PasswordHistoryData, alreadyEncrypted: boolean = false) { -+ super(); -+ if (obj == null) { -+ return; - } - -- decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { -- return this.decryptObj(new PasswordHistoryView(this), { -- password: null, -- }, orgId, encKey); -- } -+ this.buildDomainModel( -+ this, -+ obj, -+ { -+ password: null, -+ }, -+ alreadyEncrypted -+ ); -+ this.lastUsedDate = new Date(obj.lastUsedDate); -+ } - -- toPasswordHistoryData(): PasswordHistoryData { -- const ph = new PasswordHistoryData(); -- ph.lastUsedDate = this.lastUsedDate.toISOString(); -- this.buildDataModel(this, ph, { -- password: null, -- }); -- return ph; -- } -+ decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { -+ return this.decryptObj( -+ new PasswordHistoryView(this), -+ { -+ password: null, -+ }, -+ orgId, -+ encKey -+ ); -+ } -+ -+ toPasswordHistoryData(): PasswordHistoryData { -+ const ph = new PasswordHistoryData(); -+ ph.lastUsedDate = this.lastUsedDate.toISOString(); -+ this.buildDataModel(this, ph, { -+ password: null, -+ }); -+ return ph; -+ } - } -diff --git a/jslib/common/src/models/domain/passwordGeneratorPolicyOptions.ts b/jslib/common/src/models/domain/passwordGeneratorPolicyOptions.ts -index d84b575a..5e37bcec 100644 ---- a/jslib/common/src/models/domain/passwordGeneratorPolicyOptions.ts -+++ b/jslib/common/src/models/domain/passwordGeneratorPolicyOptions.ts -@@ -1,29 +1,31 @@ --import Domain from './domainBase'; -+import Domain from "./domainBase"; - - export class PasswordGeneratorPolicyOptions extends Domain { -- defaultType: string = ''; -- minLength: number = 0; -- useUppercase: boolean = false; -- useLowercase: boolean = false; -- useNumbers: boolean = false; -- numberCount: number = 0; -- useSpecial: boolean = false; -- specialCount: number = 0; -- minNumberWords: number = 0; -- capitalize: boolean = false; -- includeNumber: boolean = false; -+ defaultType: string = ""; -+ minLength: number = 0; -+ useUppercase: boolean = false; -+ useLowercase: boolean = false; -+ useNumbers: boolean = false; -+ numberCount: number = 0; -+ useSpecial: boolean = false; -+ specialCount: number = 0; -+ minNumberWords: number = 0; -+ capitalize: boolean = false; -+ includeNumber: boolean = false; - -- inEffect() { -- return this.defaultType !== '' || -- this.minLength > 0 || -- this.numberCount > 0 || -- this.specialCount > 0 || -- this.useUppercase || -- this.useLowercase || -- this.useNumbers || -- this.useSpecial || -- this.minNumberWords > 0 || -- this.capitalize || -- this.includeNumber; -- } -+ inEffect() { -+ return ( -+ this.defaultType !== "" || -+ this.minLength > 0 || -+ this.numberCount > 0 || -+ this.specialCount > 0 || -+ this.useUppercase || -+ this.useLowercase || -+ this.useNumbers || -+ this.useSpecial || -+ this.minNumberWords > 0 || -+ this.capitalize || -+ this.includeNumber -+ ); -+ } - } -diff --git a/jslib/common/src/models/domain/policy.ts b/jslib/common/src/models/domain/policy.ts -index e8e5d00f..174c2c7c 100644 ---- a/jslib/common/src/models/domain/policy.ts -+++ b/jslib/common/src/models/domain/policy.ts -@@ -1,26 +1,26 @@ --import { PolicyData } from '../data/policyData'; -+import { PolicyData } from "../data/policyData"; - --import Domain from './domainBase'; -+import Domain from "./domainBase"; - --import { PolicyType } from '../../enums/policyType'; -+import { PolicyType } from "../../enums/policyType"; - - export class Policy extends Domain { -- id: string; -- organizationId: string; -- type: PolicyType; -- data: any; -- enabled: boolean; -+ id: string; -+ organizationId: string; -+ type: PolicyType; -+ data: any; -+ enabled: boolean; - -- constructor(obj?: PolicyData) { -- super(); -- if (obj == null) { -- return; -- } -- -- this.id = obj.id; -- this.organizationId = obj.organizationId; -- this.type = obj.type; -- this.data = obj.data; -- this.enabled = obj.enabled; -+ constructor(obj?: PolicyData) { -+ super(); -+ if (obj == null) { -+ return; - } -+ -+ this.id = obj.id; -+ this.organizationId = obj.organizationId; -+ this.type = obj.type; -+ this.data = obj.data; -+ this.enabled = obj.enabled; -+ } - } -diff --git a/jslib/common/src/models/domain/provider.ts b/jslib/common/src/models/domain/provider.ts -index 24031ea1..6e14340b 100644 ---- a/jslib/common/src/models/domain/provider.ts -+++ b/jslib/common/src/models/domain/provider.ts -@@ -1,50 +1,50 @@ --import { ProviderUserStatusType } from '../../enums/providerUserStatusType'; --import { ProviderUserType } from '../../enums/providerUserType'; --import { ProviderData } from '../data/providerData'; -+import { ProviderUserStatusType } from "../../enums/providerUserStatusType"; -+import { ProviderUserType } from "../../enums/providerUserType"; -+import { ProviderData } from "../data/providerData"; - - export class Provider { -- id: string; -- name: string; -- status: ProviderUserStatusType; -- type: ProviderUserType; -- enabled: boolean; -- userId: string; -- useEvents: boolean; -- -- constructor(obj?: ProviderData) { -- if (obj == null) { -- return; -- } -- -- this.id = obj.id; -- this.name = obj.name; -- this.status = obj.status; -- this.type = obj.type; -- this.enabled = obj.enabled; -- this.userId = obj.userId; -- this.useEvents = obj.useEvents; -+ id: string; -+ name: string; -+ status: ProviderUserStatusType; -+ type: ProviderUserType; -+ enabled: boolean; -+ userId: string; -+ useEvents: boolean; -+ -+ constructor(obj?: ProviderData) { -+ if (obj == null) { -+ return; - } - -- get canAccess() { -- if (this.isProviderAdmin) { -- return true; -- } -- return this.enabled && this.status === ProviderUserStatusType.Confirmed; -+ this.id = obj.id; -+ this.name = obj.name; -+ this.status = obj.status; -+ this.type = obj.type; -+ this.enabled = obj.enabled; -+ this.userId = obj.userId; -+ this.useEvents = obj.useEvents; -+ } -+ -+ get canAccess() { -+ if (this.isProviderAdmin) { -+ return true; - } -+ return this.enabled && this.status === ProviderUserStatusType.Confirmed; -+ } - -- get canCreateOrganizations() { -- return this.enabled && this.isProviderAdmin; -- } -+ get canCreateOrganizations() { -+ return this.enabled && this.isProviderAdmin; -+ } - -- get canManageUsers() { -- return this.isProviderAdmin; -- } -+ get canManageUsers() { -+ return this.isProviderAdmin; -+ } - -- get canAccessEventLogs() { -- return this.isProviderAdmin; -- } -+ get canAccessEventLogs() { -+ return this.isProviderAdmin; -+ } - -- get isProviderAdmin() { -- return this.type === ProviderUserType.ProviderAdmin; -- } -+ get isProviderAdmin() { -+ return this.type === ProviderUserType.ProviderAdmin; -+ } - } -diff --git a/jslib/common/src/models/domain/resetPasswordPolicyOptions.ts b/jslib/common/src/models/domain/resetPasswordPolicyOptions.ts -index 6a650e4c..8dbde376 100644 ---- a/jslib/common/src/models/domain/resetPasswordPolicyOptions.ts -+++ b/jslib/common/src/models/domain/resetPasswordPolicyOptions.ts -@@ -1,5 +1,5 @@ --import Domain from './domainBase'; -+import Domain from "./domainBase"; - - export class ResetPasswordPolicyOptions extends Domain { -- autoEnrollEnabled: boolean = false; -+ autoEnrollEnabled: boolean = false; - } -diff --git a/jslib/common/src/models/domain/secureNote.ts b/jslib/common/src/models/domain/secureNote.ts -index 91b23fff..42f31297 100644 ---- a/jslib/common/src/models/domain/secureNote.ts -+++ b/jslib/common/src/models/domain/secureNote.ts -@@ -1,31 +1,31 @@ --import { SecureNoteType } from '../../enums/secureNoteType'; -+import { SecureNoteType } from "../../enums/secureNoteType"; - --import { SecureNoteData } from '../data/secureNoteData'; -+import { SecureNoteData } from "../data/secureNoteData"; - --import Domain from './domainBase'; -+import Domain from "./domainBase"; - --import { SecureNoteView } from '../view/secureNoteView'; --import { SymmetricCryptoKey } from './symmetricCryptoKey'; -+import { SecureNoteView } from "../view/secureNoteView"; -+import { SymmetricCryptoKey } from "./symmetricCryptoKey"; - - export class SecureNote extends Domain { -- type: SecureNoteType; -+ type: SecureNoteType; - -- constructor(obj?: SecureNoteData, alreadyEncrypted: boolean = false) { -- super(); -- if (obj == null) { -- return; -- } -- -- this.type = obj.type; -+ constructor(obj?: SecureNoteData, alreadyEncrypted: boolean = false) { -+ super(); -+ if (obj == null) { -+ return; - } - -- decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { -- return Promise.resolve(new SecureNoteView(this)); -- } -+ this.type = obj.type; -+ } - -- toSecureNoteData(): SecureNoteData { -- const n = new SecureNoteData(); -- n.type = this.type; -- return n; -- } -+ decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { -+ return Promise.resolve(new SecureNoteView(this)); -+ } -+ -+ toSecureNoteData(): SecureNoteData { -+ const n = new SecureNoteData(); -+ n.type = this.type; -+ return n; -+ } - } -diff --git a/jslib/common/src/models/domain/send.ts b/jslib/common/src/models/domain/send.ts -index 84ed24e7..ab217f92 100644 ---- a/jslib/common/src/models/domain/send.ts -+++ b/jslib/common/src/models/domain/send.ts -@@ -1,108 +1,119 @@ --import { CryptoService } from '../../abstractions/crypto.service'; -+import { CryptoService } from "../../abstractions/crypto.service"; - --import { SendType } from '../../enums/sendType'; -+import { SendType } from "../../enums/sendType"; - --import { Utils } from '../../misc/utils'; -+import { Utils } from "../../misc/utils"; - --import { SendData } from '../data/sendData'; -+import { SendData } from "../data/sendData"; - --import { SendView } from '../view/sendView'; -+import { SendView } from "../view/sendView"; - --import Domain from './domainBase'; --import { EncString } from './encString'; --import { SendFile } from './sendFile'; --import { SendText } from './sendText'; -+import Domain from "./domainBase"; -+import { EncString } from "./encString"; -+import { SendFile } from "./sendFile"; -+import { SendText } from "./sendText"; - - export class Send extends Domain { -- id: string; -- accessId: string; -- userId: string; -- type: SendType; -- name: EncString; -- notes: EncString; -- file: SendFile; -- text: SendText; -- key: EncString; -- maxAccessCount?: number; -- accessCount: number; -- revisionDate: Date; -- expirationDate: Date; -- deletionDate: Date; -- password: string; -- disabled: boolean; -- hideEmail: boolean; -- -- constructor(obj?: SendData, alreadyEncrypted: boolean = false) { -- super(); -- if (obj == null) { -- return; -- } -- -- this.buildDomainModel(this, obj, { -- id: null, -- accessId: null, -- userId: null, -- name: null, -- notes: null, -- key: null, -- }, alreadyEncrypted, ['id', 'accessId', 'userId']); -- -- this.type = obj.type; -- this.maxAccessCount = obj.maxAccessCount; -- this.accessCount = obj.accessCount; -- this.password = obj.password; -- this.disabled = obj.disabled; -- this.revisionDate = obj.revisionDate != null ? new Date(obj.revisionDate) : null; -- this.deletionDate = obj.deletionDate != null ? new Date(obj.deletionDate) : null; -- this.expirationDate = obj.expirationDate != null ? new Date(obj.expirationDate) : null; -- this.hideEmail = obj.hideEmail; -- -- switch (this.type) { -- case SendType.Text: -- this.text = new SendText(obj.text, alreadyEncrypted); -- break; -- case SendType.File: -- this.file = new SendFile(obj.file, alreadyEncrypted); -- break; -- default: -- break; -- } -+ id: string; -+ accessId: string; -+ userId: string; -+ type: SendType; -+ name: EncString; -+ notes: EncString; -+ file: SendFile; -+ text: SendText; -+ key: EncString; -+ maxAccessCount?: number; -+ accessCount: number; -+ revisionDate: Date; -+ expirationDate: Date; -+ deletionDate: Date; -+ password: string; -+ disabled: boolean; -+ hideEmail: boolean; -+ -+ constructor(obj?: SendData, alreadyEncrypted: boolean = false) { -+ super(); -+ if (obj == null) { -+ return; - } - -- async decrypt(): Promise { -- const model = new SendView(this); -- -- let cryptoService: CryptoService; -- const containerService = (Utils.global as any).bitwardenContainerService; -- if (containerService) { -- cryptoService = containerService.getCryptoService(); -- } else { -- throw new Error('global bitwardenContainerService not initialized.'); -- } -- -- try { -- model.key = await cryptoService.decryptToBytes(this.key, null); -- model.cryptoKey = await cryptoService.makeSendKey(model.key); -- } catch (e) { -- // TODO: error? -- } -- -- await this.decryptObj(model, { -- name: null, -- notes: null, -- }, null, model.cryptoKey); -- -- switch (this.type) { -- case SendType.File: -- model.file = await this.file.decrypt(model.cryptoKey); -- break; -- case SendType.Text: -- model.text = await this.text.decrypt(model.cryptoKey); -- break; -- default: -- break; -- } -- -- return model; -+ this.buildDomainModel( -+ this, -+ obj, -+ { -+ id: null, -+ accessId: null, -+ userId: null, -+ name: null, -+ notes: null, -+ key: null, -+ }, -+ alreadyEncrypted, -+ ["id", "accessId", "userId"] -+ ); -+ -+ this.type = obj.type; -+ this.maxAccessCount = obj.maxAccessCount; -+ this.accessCount = obj.accessCount; -+ this.password = obj.password; -+ this.disabled = obj.disabled; -+ this.revisionDate = obj.revisionDate != null ? new Date(obj.revisionDate) : null; -+ this.deletionDate = obj.deletionDate != null ? new Date(obj.deletionDate) : null; -+ this.expirationDate = obj.expirationDate != null ? new Date(obj.expirationDate) : null; -+ this.hideEmail = obj.hideEmail; -+ -+ switch (this.type) { -+ case SendType.Text: -+ this.text = new SendText(obj.text, alreadyEncrypted); -+ break; -+ case SendType.File: -+ this.file = new SendFile(obj.file, alreadyEncrypted); -+ break; -+ default: -+ break; - } -+ } -+ -+ async decrypt(): Promise { -+ const model = new SendView(this); -+ -+ let cryptoService: CryptoService; -+ const containerService = (Utils.global as any).bitwardenContainerService; -+ if (containerService) { -+ cryptoService = containerService.getCryptoService(); -+ } else { -+ throw new Error("global bitwardenContainerService not initialized."); -+ } -+ -+ try { -+ model.key = await cryptoService.decryptToBytes(this.key, null); -+ model.cryptoKey = await cryptoService.makeSendKey(model.key); -+ } catch (e) { -+ // TODO: error? -+ } -+ -+ await this.decryptObj( -+ model, -+ { -+ name: null, -+ notes: null, -+ }, -+ null, -+ model.cryptoKey -+ ); -+ -+ switch (this.type) { -+ case SendType.File: -+ model.file = await this.file.decrypt(model.cryptoKey); -+ break; -+ case SendType.Text: -+ model.text = await this.text.decrypt(model.cryptoKey); -+ break; -+ default: -+ break; -+ } -+ -+ return model; -+ } - } -diff --git a/jslib/common/src/models/domain/sendAccess.ts b/jslib/common/src/models/domain/sendAccess.ts -index f3ff1899..05ad9bde 100644 ---- a/jslib/common/src/models/domain/sendAccess.ts -+++ b/jslib/common/src/models/domain/sendAccess.ts -@@ -1,69 +1,80 @@ --import { SendType } from '../../enums/sendType'; -+import { SendType } from "../../enums/sendType"; - --import { SendAccessResponse } from '../response/sendAccessResponse'; -+import { SendAccessResponse } from "../response/sendAccessResponse"; - --import { SendAccessView } from '../view/sendAccessView'; -+import { SendAccessView } from "../view/sendAccessView"; - --import Domain from './domainBase'; --import { EncString } from './encString'; --import { SendFile } from './sendFile'; --import { SendText } from './sendText'; --import { SymmetricCryptoKey } from './symmetricCryptoKey'; -+import Domain from "./domainBase"; -+import { EncString } from "./encString"; -+import { SendFile } from "./sendFile"; -+import { SendText } from "./sendText"; -+import { SymmetricCryptoKey } from "./symmetricCryptoKey"; - - export class SendAccess extends Domain { -- id: string; -- type: SendType; -- name: EncString; -- file: SendFile; -- text: SendText; -- expirationDate: Date; -- creatorIdentifier: string; -+ id: string; -+ type: SendType; -+ name: EncString; -+ file: SendFile; -+ text: SendText; -+ expirationDate: Date; -+ creatorIdentifier: string; - -- constructor(obj?: SendAccessResponse, alreadyEncrypted: boolean = false) { -- super(); -- if (obj == null) { -- return; -- } -+ constructor(obj?: SendAccessResponse, alreadyEncrypted: boolean = false) { -+ super(); -+ if (obj == null) { -+ return; -+ } - -- this.buildDomainModel(this, obj, { -- id: null, -- name: null, -- expirationDate: null, -- creatorIdentifier: null, -- }, alreadyEncrypted, ['id', 'expirationDate', 'creatorIdentifier']); -+ this.buildDomainModel( -+ this, -+ obj, -+ { -+ id: null, -+ name: null, -+ expirationDate: null, -+ creatorIdentifier: null, -+ }, -+ alreadyEncrypted, -+ ["id", "expirationDate", "creatorIdentifier"] -+ ); - -- this.type = obj.type; -+ this.type = obj.type; - -- switch (this.type) { -- case SendType.Text: -- this.text = new SendText(obj.text, alreadyEncrypted); -- break; -- case SendType.File: -- this.file = new SendFile(obj.file, alreadyEncrypted); -- break; -- default: -- break; -- } -+ switch (this.type) { -+ case SendType.Text: -+ this.text = new SendText(obj.text, alreadyEncrypted); -+ break; -+ case SendType.File: -+ this.file = new SendFile(obj.file, alreadyEncrypted); -+ break; -+ default: -+ break; - } -+ } - -- async decrypt(key: SymmetricCryptoKey): Promise { -- const model = new SendAccessView(this); -- -- await this.decryptObj(model, { -- name: null, -- }, null, key); -+ async decrypt(key: SymmetricCryptoKey): Promise { -+ const model = new SendAccessView(this); - -- switch (this.type) { -- case SendType.File: -- model.file = await this.file.decrypt(key); -- break; -- case SendType.Text: -- model.text = await this.text.decrypt(key); -- break; -- default: -- break; -- } -+ await this.decryptObj( -+ model, -+ { -+ name: null, -+ }, -+ null, -+ key -+ ); - -- return model; -+ switch (this.type) { -+ case SendType.File: -+ model.file = await this.file.decrypt(key); -+ break; -+ case SendType.Text: -+ model.text = await this.text.decrypt(key); -+ break; -+ default: -+ break; - } -+ -+ return model; -+ } - } -diff --git a/jslib/common/src/models/domain/sendFile.ts b/jslib/common/src/models/domain/sendFile.ts -index 4a057301..76c566df 100644 ---- a/jslib/common/src/models/domain/sendFile.ts -+++ b/jslib/common/src/models/domain/sendFile.ts -@@ -1,35 +1,46 @@ --import Domain from './domainBase'; --import { EncString } from './encString'; --import { SymmetricCryptoKey } from './symmetricCryptoKey'; -+import Domain from "./domainBase"; -+import { EncString } from "./encString"; -+import { SymmetricCryptoKey } from "./symmetricCryptoKey"; - --import { SendFileData } from '../data/sendFileData'; -+import { SendFileData } from "../data/sendFileData"; - --import { SendFileView } from '../view/sendFileView'; -+import { SendFileView } from "../view/sendFileView"; - - export class SendFile extends Domain { -- id: string; -- size: string; -- sizeName: string; -- fileName: EncString; -+ id: string; -+ size: string; -+ sizeName: string; -+ fileName: EncString; - -- constructor(obj?: SendFileData, alreadyEncrypted: boolean = false) { -- super(); -- if (obj == null) { -- return; -- } -- -- this.size = obj.size; -- this.buildDomainModel(this, obj, { -- id: null, -- sizeName: null, -- fileName: null, -- }, alreadyEncrypted, ['id', 'sizeName']); -+ constructor(obj?: SendFileData, alreadyEncrypted: boolean = false) { -+ super(); -+ if (obj == null) { -+ return; - } - -- async decrypt(key: SymmetricCryptoKey): Promise { -- const view = await this.decryptObj(new SendFileView(this), { -- fileName: null, -- }, null, key); -- return view; -- } -+ this.size = obj.size; -+ this.buildDomainModel( -+ this, -+ obj, -+ { -+ id: null, -+ sizeName: null, -+ fileName: null, -+ }, -+ alreadyEncrypted, -+ ["id", "sizeName"] -+ ); -+ } -+ -+ async decrypt(key: SymmetricCryptoKey): Promise { -+ const view = await this.decryptObj( -+ new SendFileView(this), -+ { -+ fileName: null, -+ }, -+ null, -+ key -+ ); -+ return view; -+ } - } -diff --git a/jslib/common/src/models/domain/sendText.ts b/jslib/common/src/models/domain/sendText.ts -index edf12000..7d45332f 100644 ---- a/jslib/common/src/models/domain/sendText.ts -+++ b/jslib/common/src/models/domain/sendText.ts -@@ -1,30 +1,41 @@ --import Domain from './domainBase'; --import { EncString } from './encString'; --import { SymmetricCryptoKey } from './symmetricCryptoKey'; -+import Domain from "./domainBase"; -+import { EncString } from "./encString"; -+import { SymmetricCryptoKey } from "./symmetricCryptoKey"; - --import { SendTextData } from '../data/sendTextData'; -+import { SendTextData } from "../data/sendTextData"; - --import { SendTextView } from '../view/sendTextView'; -+import { SendTextView } from "../view/sendTextView"; - - export class SendText extends Domain { -- text: EncString; -- hidden: boolean; -+ text: EncString; -+ hidden: boolean; - -- constructor(obj?: SendTextData, alreadyEncrypted: boolean = false) { -- super(); -- if (obj == null) { -- return; -- } -- -- this.hidden = obj.hidden; -- this.buildDomainModel(this, obj, { -- text: null, -- }, alreadyEncrypted, []); -+ constructor(obj?: SendTextData, alreadyEncrypted: boolean = false) { -+ super(); -+ if (obj == null) { -+ return; - } - -- decrypt(key: SymmetricCryptoKey): Promise { -- return this.decryptObj(new SendTextView(this), { -- text: null, -- }, null, key); -- } -+ this.hidden = obj.hidden; -+ this.buildDomainModel( -+ this, -+ obj, -+ { -+ text: null, -+ }, -+ alreadyEncrypted, -+ [] -+ ); -+ } -+ -+ decrypt(key: SymmetricCryptoKey): Promise { -+ return this.decryptObj( -+ new SendTextView(this), -+ { -+ text: null, -+ }, -+ null, -+ key -+ ); -+ } - } -diff --git a/jslib/common/src/models/domain/sortedCiphersCache.ts b/jslib/common/src/models/domain/sortedCiphersCache.ts -index 64f4b049..8c32744e 100644 ---- a/jslib/common/src/models/domain/sortedCiphersCache.ts -+++ b/jslib/common/src/models/domain/sortedCiphersCache.ts -@@ -1,82 +1,87 @@ --import { CipherView } from '../view/cipherView'; -+import { CipherView } from "../view/cipherView"; - - const CacheTTL = 3000; - - export class SortedCiphersCache { -- private readonly sortedCiphersByUrl: Map = new Map(); -- private readonly timeouts: Map = new Map(); -- -- constructor(private readonly comparator: (a: CipherView, b: CipherView) => number) { } -- -- isCached(url: string) { -- return this.sortedCiphersByUrl.has(url); -- } -- -- addCiphers(url: string, ciphers: CipherView[]) { -- ciphers.sort(this.comparator); -- this.sortedCiphersByUrl.set(url, new Ciphers(ciphers)); -- this.resetTimer(url); -- } -- -- getLastUsed(url: string) { -- this.resetTimer(url); -- return this.isCached(url) ? this.sortedCiphersByUrl.get(url).getLastUsed() : null; -- } -- -- getLastLaunched(url: string) { -- return this.isCached(url) ? this.sortedCiphersByUrl.get(url).getLastLaunched() : null; -- } -- -- getNext(url: string) { -- this.resetTimer(url); -- return this.isCached(url) ? this.sortedCiphersByUrl.get(url).getNext() : null; -- } -- -- updateLastUsedIndex(url: string) { -- if (this.isCached(url)) { -- this.sortedCiphersByUrl.get(url).updateLastUsedIndex(); -- } -- } -- -- clear() { -- this.sortedCiphersByUrl.clear(); -- this.timeouts.clear(); -- } -- -- private resetTimer(url: string) { -- clearTimeout(this.timeouts.get(url)); -- this.timeouts.set(url, setTimeout(() => { -- this.sortedCiphersByUrl.delete(url); -- this.timeouts.delete(url); -- }, CacheTTL)); -+ private readonly sortedCiphersByUrl: Map = new Map(); -+ private readonly timeouts: Map = new Map(); -+ -+ constructor(private readonly comparator: (a: CipherView, b: CipherView) => number) {} -+ -+ isCached(url: string) { -+ return this.sortedCiphersByUrl.has(url); -+ } -+ -+ addCiphers(url: string, ciphers: CipherView[]) { -+ ciphers.sort(this.comparator); -+ this.sortedCiphersByUrl.set(url, new Ciphers(ciphers)); -+ this.resetTimer(url); -+ } -+ -+ getLastUsed(url: string) { -+ this.resetTimer(url); -+ return this.isCached(url) ? this.sortedCiphersByUrl.get(url).getLastUsed() : null; -+ } -+ -+ getLastLaunched(url: string) { -+ return this.isCached(url) ? this.sortedCiphersByUrl.get(url).getLastLaunched() : null; -+ } -+ -+ getNext(url: string) { -+ this.resetTimer(url); -+ return this.isCached(url) ? this.sortedCiphersByUrl.get(url).getNext() : null; -+ } -+ -+ updateLastUsedIndex(url: string) { -+ if (this.isCached(url)) { -+ this.sortedCiphersByUrl.get(url).updateLastUsedIndex(); - } -+ } -+ -+ clear() { -+ this.sortedCiphersByUrl.clear(); -+ this.timeouts.clear(); -+ } -+ -+ private resetTimer(url: string) { -+ clearTimeout(this.timeouts.get(url)); -+ this.timeouts.set( -+ url, -+ setTimeout(() => { -+ this.sortedCiphersByUrl.delete(url); -+ this.timeouts.delete(url); -+ }, CacheTTL) -+ ); -+ } - } - - class Ciphers { -- lastUsedIndex = -1; -- -- constructor(private readonly ciphers: CipherView[]) { } -- -- getLastUsed() { -- this.lastUsedIndex = Math.max(this.lastUsedIndex, 0); -- return this.ciphers[this.lastUsedIndex]; -- } -- -- getLastLaunched() { -- const usedCiphers = this.ciphers.filter(cipher => cipher.localData?.lastLaunched); -- const sortedCiphers = usedCiphers.sort((x, y) => y.localData.lastLaunched.valueOf() - x.localData.lastLaunched.valueOf()); -- return sortedCiphers[0]; -- } -- -- getNextIndex() { -- return (this.lastUsedIndex + 1) % this.ciphers.length; -- } -- -- getNext() { -- return this.ciphers[this.getNextIndex()]; -- } -- -- updateLastUsedIndex() { -- this.lastUsedIndex = this.getNextIndex(); -- } -+ lastUsedIndex = -1; -+ -+ constructor(private readonly ciphers: CipherView[]) {} -+ -+ getLastUsed() { -+ this.lastUsedIndex = Math.max(this.lastUsedIndex, 0); -+ return this.ciphers[this.lastUsedIndex]; -+ } -+ -+ getLastLaunched() { -+ const usedCiphers = this.ciphers.filter((cipher) => cipher.localData?.lastLaunched); -+ const sortedCiphers = usedCiphers.sort( -+ (x, y) => y.localData.lastLaunched.valueOf() - x.localData.lastLaunched.valueOf() -+ ); -+ return sortedCiphers[0]; -+ } -+ -+ getNextIndex() { -+ return (this.lastUsedIndex + 1) % this.ciphers.length; -+ } -+ -+ getNext() { -+ return this.ciphers[this.getNextIndex()]; -+ } -+ -+ updateLastUsedIndex() { -+ this.lastUsedIndex = this.getNextIndex(); -+ } - } -diff --git a/jslib/common/src/models/domain/state.ts b/jslib/common/src/models/domain/state.ts -new file mode 100644 -index 00000000..c87ff6e8 ---- /dev/null -+++ b/jslib/common/src/models/domain/state.ts -@@ -0,0 +1,16 @@ -+import { Account } from "./account"; -+import { GlobalState } from "./globalState"; -+ -+export class State< -+ TAccount extends Account = Account, -+ TGlobalState extends GlobalState = GlobalState -+> { -+ accounts: { [userId: string]: TAccount } = {}; -+ globals: TGlobalState; -+ activeUserId: string; -+ authenticatedAccounts: string[] = []; -+ -+ constructor(globals: TGlobalState) { -+ this.globals = globals; -+ } -+} -diff --git a/jslib/common/src/models/domain/storageOptions.ts b/jslib/common/src/models/domain/storageOptions.ts -new file mode 100644 -index 00000000..2db7e0cc ---- /dev/null -+++ b/jslib/common/src/models/domain/storageOptions.ts -@@ -0,0 +1,10 @@ -+import { HtmlStorageLocation } from "../../enums/htmlStorageLocation"; -+import { StorageLocation } from "../../enums/storageLocation"; -+ -+export type StorageOptions = { -+ storageLocation?: StorageLocation; -+ useSecureStorage?: boolean; -+ userId?: string; -+ htmlStorageLocation?: HtmlStorageLocation; -+ keySuffix?: string; -+}; -diff --git a/jslib/common/src/models/domain/symmetricCryptoKey.ts b/jslib/common/src/models/domain/symmetricCryptoKey.ts -index e6e63b82..c3c80f06 100644 ---- a/jslib/common/src/models/domain/symmetricCryptoKey.ts -+++ b/jslib/common/src/models/domain/symmetricCryptoKey.ts -@@ -1,58 +1,58 @@ --import { EncryptionType } from '../../enums/encryptionType'; -+import { EncryptionType } from "../../enums/encryptionType"; - --import { Utils } from '../../misc/utils'; -+import { Utils } from "../../misc/utils"; - - export class SymmetricCryptoKey { -- key: ArrayBuffer; -- encKey?: ArrayBuffer; -- macKey?: ArrayBuffer; -- encType: EncryptionType; -- -- keyB64: string; -- encKeyB64: string; -- macKeyB64: string; -- -- meta: any; -- -- constructor(key: ArrayBuffer, encType?: EncryptionType) { -- if (key == null) { -- throw new Error('Must provide key'); -- } -- -- if (encType == null) { -- if (key.byteLength === 32) { -- encType = EncryptionType.AesCbc256_B64; -- } else if (key.byteLength === 64) { -- encType = EncryptionType.AesCbc256_HmacSha256_B64; -- } else { -- throw new Error('Unable to determine encType.'); -- } -- } -- -- this.key = key; -- this.encType = encType; -- -- if (encType === EncryptionType.AesCbc256_B64 && key.byteLength === 32) { -- this.encKey = key; -- this.macKey = null; -- } else if (encType === EncryptionType.AesCbc128_HmacSha256_B64 && key.byteLength === 32) { -- this.encKey = key.slice(0, 16); -- this.macKey = key.slice(16, 32); -- } else if (encType === EncryptionType.AesCbc256_HmacSha256_B64 && key.byteLength === 64) { -- this.encKey = key.slice(0, 32); -- this.macKey = key.slice(32, 64); -- } else { -- throw new Error('Unsupported encType/key length.'); -- } -- -- if (this.key != null) { -- this.keyB64 = Utils.fromBufferToB64(this.key); -- } -- if (this.encKey != null) { -- this.encKeyB64 = Utils.fromBufferToB64(this.encKey); -- } -- if (this.macKey != null) { -- this.macKeyB64 = Utils.fromBufferToB64(this.macKey); -- } -+ key: ArrayBuffer; -+ encKey?: ArrayBuffer; -+ macKey?: ArrayBuffer; -+ encType: EncryptionType; -+ -+ keyB64: string; -+ encKeyB64: string; -+ macKeyB64: string; -+ -+ meta: any; -+ -+ constructor(key: ArrayBuffer, encType?: EncryptionType) { -+ if (key == null) { -+ throw new Error("Must provide key"); -+ } -+ -+ if (encType == null) { -+ if (key.byteLength === 32) { -+ encType = EncryptionType.AesCbc256_B64; -+ } else if (key.byteLength === 64) { -+ encType = EncryptionType.AesCbc256_HmacSha256_B64; -+ } else { -+ throw new Error("Unable to determine encType."); -+ } -+ } -+ -+ this.key = key; -+ this.encType = encType; -+ -+ if (encType === EncryptionType.AesCbc256_B64 && key.byteLength === 32) { -+ this.encKey = key; -+ this.macKey = null; -+ } else if (encType === EncryptionType.AesCbc128_HmacSha256_B64 && key.byteLength === 32) { -+ this.encKey = key.slice(0, 16); -+ this.macKey = key.slice(16, 32); -+ } else if (encType === EncryptionType.AesCbc256_HmacSha256_B64 && key.byteLength === 64) { -+ this.encKey = key.slice(0, 32); -+ this.macKey = key.slice(32, 64); -+ } else { -+ throw new Error("Unsupported encType/key length."); -+ } -+ -+ if (this.key != null) { -+ this.keyB64 = Utils.fromBufferToB64(this.key); -+ } -+ if (this.encKey != null) { -+ this.encKeyB64 = Utils.fromBufferToB64(this.encKey); -+ } -+ if (this.macKey != null) { -+ this.macKeyB64 = Utils.fromBufferToB64(this.macKey); - } -+ } - } -diff --git a/jslib/common/src/models/domain/treeNode.ts b/jslib/common/src/models/domain/treeNode.ts -index 9ea553bc..6af973a5 100644 ---- a/jslib/common/src/models/domain/treeNode.ts -+++ b/jslib/common/src/models/domain/treeNode.ts -@@ -1,16 +1,16 @@ - export class TreeNode { -- parent: T; -- node: T; -- children: TreeNode[] = []; -+ parent: T; -+ node: T; -+ children: TreeNode[] = []; - -- constructor(node: T, name: string, parent: T) { -- this.parent = parent; -- this.node = node; -- this.node.name = name; -- } -+ constructor(node: T, name: string, parent: T) { -+ this.parent = parent; -+ this.node = node; -+ this.node.name = name; -+ } - } - - export interface ITreeNodeObject { -- id: string; -- name: string; -+ id: string; -+ name: string; - } -diff --git a/jslib/common/src/models/domain/windowState.ts b/jslib/common/src/models/domain/windowState.ts -new file mode 100644 -index 00000000..cb260d8b ---- /dev/null -+++ b/jslib/common/src/models/domain/windowState.ts -@@ -0,0 +1,10 @@ -+export class WindowState { -+ width?: number; -+ height?: number; -+ isMaximized?: boolean; -+ // TODO: displayBounds is an Electron.Rectangle. -+ // We need to establish some kind of client-specific global state, similiar to the way we already extend a base Account. -+ displayBounds: any; -+ x?: number; -+ y?: number; -+} -diff --git a/jslib/common/src/models/export/card.ts b/jslib/common/src/models/export/card.ts -index a24c3e6e..853ce64b 100644 ---- a/jslib/common/src/models/export/card.ts -+++ b/jslib/common/src/models/export/card.ts -@@ -1,66 +1,66 @@ --import { CardView } from '../view/cardView'; -+import { CardView } from "../view/cardView"; - --import { Card as CardDomain } from '../domain/card'; --import { EncString } from '../domain/encString'; -+import { Card as CardDomain } from "../domain/card"; -+import { EncString } from "../domain/encString"; - - export class Card { -- static template(): Card { -- const req = new Card(); -- req.cardholderName = 'John Doe'; -- req.brand = 'visa'; -- req.number = '4242424242424242'; -- req.expMonth = '04'; -- req.expYear = '2023'; -- req.code = '123'; -- return req; -- } -+ static template(): Card { -+ const req = new Card(); -+ req.cardholderName = "John Doe"; -+ req.brand = "visa"; -+ req.number = "4242424242424242"; -+ req.expMonth = "04"; -+ req.expYear = "2023"; -+ req.code = "123"; -+ return req; -+ } - -- static toView(req: Card, view = new CardView()) { -- view.cardholderName = req.cardholderName; -- view.brand = req.brand; -- view.number = req.number; -- view.expMonth = req.expMonth; -- view.expYear = req.expYear; -- view.code = req.code; -- return view; -- } -+ static toView(req: Card, view = new CardView()) { -+ view.cardholderName = req.cardholderName; -+ view.brand = req.brand; -+ view.number = req.number; -+ view.expMonth = req.expMonth; -+ view.expYear = req.expYear; -+ view.code = req.code; -+ return view; -+ } - -- static toDomain(req: Card, domain = new CardDomain()) { -- domain.cardholderName = req.cardholderName != null ? new EncString(req.cardholderName) : null; -- domain.brand = req.brand != null ? new EncString(req.brand) : null; -- domain.number = req.number != null ? new EncString(req.number) : null; -- domain.expMonth = req.expMonth != null ? new EncString(req.expMonth) : null; -- domain.expYear = req.expYear != null ? new EncString(req.expYear) : null; -- domain.code = req.code != null ? new EncString(req.code) : null; -- return domain; -- } -+ static toDomain(req: Card, domain = new CardDomain()) { -+ domain.cardholderName = req.cardholderName != null ? new EncString(req.cardholderName) : null; -+ domain.brand = req.brand != null ? new EncString(req.brand) : null; -+ domain.number = req.number != null ? new EncString(req.number) : null; -+ domain.expMonth = req.expMonth != null ? new EncString(req.expMonth) : null; -+ domain.expYear = req.expYear != null ? new EncString(req.expYear) : null; -+ domain.code = req.code != null ? new EncString(req.code) : null; -+ return domain; -+ } - -- cardholderName: string; -- brand: string; -- number: string; -- expMonth: string; -- expYear: string; -- code: string; -+ cardholderName: string; -+ brand: string; -+ number: string; -+ expMonth: string; -+ expYear: string; -+ code: string; - -- constructor(o?: CardView | CardDomain) { -- if (o == null) { -- return; -- } -+ constructor(o?: CardView | CardDomain) { -+ if (o == null) { -+ return; -+ } - -- if (o instanceof CardView) { -- this.cardholderName = o.cardholderName; -- this.brand = o.brand; -- this.number = o.number; -- this.expMonth = o.expMonth; -- this.expYear = o.expYear; -- this.code = o.code; -- } else { -- this.cardholderName = o.cardholderName?.encryptedString; -- this.brand = o.brand?.encryptedString; -- this.number = o.number?.encryptedString; -- this.expMonth = o.expMonth?.encryptedString; -- this.expYear = o.expYear?.encryptedString; -- this.code = o.code?.encryptedString; -- } -+ if (o instanceof CardView) { -+ this.cardholderName = o.cardholderName; -+ this.brand = o.brand; -+ this.number = o.number; -+ this.expMonth = o.expMonth; -+ this.expYear = o.expYear; -+ this.code = o.code; -+ } else { -+ this.cardholderName = o.cardholderName?.encryptedString; -+ this.brand = o.brand?.encryptedString; -+ this.number = o.number?.encryptedString; -+ this.expMonth = o.expMonth?.encryptedString; -+ this.expYear = o.expYear?.encryptedString; -+ this.code = o.code?.encryptedString; - } -+ } - } -diff --git a/jslib/common/src/models/export/cipher.ts b/jslib/common/src/models/export/cipher.ts -index 6f050776..c433044d 100644 ---- a/jslib/common/src/models/export/cipher.ts -+++ b/jslib/common/src/models/export/cipher.ts -@@ -1,158 +1,158 @@ --import { CipherRepromptType } from '../../enums/cipherRepromptType'; --import { CipherType } from '../../enums/cipherType'; -+import { CipherRepromptType } from "../../enums/cipherRepromptType"; -+import { CipherType } from "../../enums/cipherType"; - --import { CipherView } from '../view/cipherView'; -+import { CipherView } from "../view/cipherView"; - --import { Cipher as CipherDomain } from '../domain/cipher'; --import { EncString } from '../domain/encString'; -+import { Cipher as CipherDomain } from "../domain/cipher"; -+import { EncString } from "../domain/encString"; - --import { Card } from './card'; --import { Field } from './field'; --import { Identity } from './identity'; --import { Login } from './login'; --import { SecureNote } from './secureNote'; -+import { Card } from "./card"; -+import { Field } from "./field"; -+import { Identity } from "./identity"; -+import { Login } from "./login"; -+import { SecureNote } from "./secureNote"; - - export class Cipher { -- static template(): Cipher { -- const req = new Cipher(); -- req.organizationId = null; -- req.collectionIds = null; -- req.folderId = null; -- req.type = CipherType.Login; -- req.name = 'Item name'; -- req.notes = 'Some notes about this item.'; -- req.favorite = false; -- req.fields = []; -- req.login = null; -- req.secureNote = null; -- req.card = null; -- req.identity = null; -- req.reprompt = CipherRepromptType.None; -- return req; -+ static template(): Cipher { -+ const req = new Cipher(); -+ req.organizationId = null; -+ req.collectionIds = null; -+ req.folderId = null; -+ req.type = CipherType.Login; -+ req.name = "Item name"; -+ req.notes = "Some notes about this item."; -+ req.favorite = false; -+ req.fields = []; -+ req.login = null; -+ req.secureNote = null; -+ req.card = null; -+ req.identity = null; -+ req.reprompt = CipherRepromptType.None; -+ return req; -+ } -+ -+ static toView(req: Cipher, view = new CipherView()) { -+ view.type = req.type; -+ view.folderId = req.folderId; -+ if (view.organizationId == null) { -+ view.organizationId = req.organizationId; -+ } -+ if (view.collectionIds || req.collectionIds) { -+ const set = new Set((view.collectionIds ?? []).concat(req.collectionIds ?? [])); -+ view.collectionIds = Array.from(set.values()); -+ } -+ view.name = req.name; -+ view.notes = req.notes; -+ view.favorite = req.favorite; -+ view.reprompt = req.reprompt ?? CipherRepromptType.None; -+ -+ if (req.fields != null) { -+ view.fields = req.fields.map((f) => Field.toView(f)); - } - -- static toView(req: Cipher, view = new CipherView()) { -- view.type = req.type; -- view.folderId = req.folderId; -- if (view.organizationId == null) { -- view.organizationId = req.organizationId; -- } -- if (view.collectionIds || req.collectionIds) { -- const set = new Set((view.collectionIds ?? []).concat(req.collectionIds ?? [])); -- view.collectionIds = Array.from(set.values()); -- } -- view.name = req.name; -- view.notes = req.notes; -- view.favorite = req.favorite; -- view.reprompt = req.reprompt ?? CipherRepromptType.None; -- -- if (req.fields != null) { -- view.fields = req.fields.map(f => Field.toView(f)); -- } -- -- switch (req.type) { -- case CipherType.Login: -- view.login = Login.toView(req.login); -- break; -- case CipherType.SecureNote: -- view.secureNote = SecureNote.toView(req.secureNote); -- break; -- case CipherType.Card: -- view.card = Card.toView(req.card); -- break; -- case CipherType.Identity: -- view.identity = Identity.toView(req.identity); -- break; -- } -- -- return view; -+ switch (req.type) { -+ case CipherType.Login: -+ view.login = Login.toView(req.login); -+ break; -+ case CipherType.SecureNote: -+ view.secureNote = SecureNote.toView(req.secureNote); -+ break; -+ case CipherType.Card: -+ view.card = Card.toView(req.card); -+ break; -+ case CipherType.Identity: -+ view.identity = Identity.toView(req.identity); -+ break; - } - -- static toDomain(req: Cipher, domain = new CipherDomain()) { -- domain.type = req.type; -- domain.folderId = req.folderId; -- if (domain.organizationId == null) { -- domain.organizationId = req.organizationId; -- } -- domain.name = req.name != null ? new EncString(req.name) : null; -- domain.notes = req.notes != null ? new EncString(req.notes) : null; -- domain.favorite = req.favorite; -- domain.reprompt = req.reprompt ?? CipherRepromptType.None; -- -- if (req.fields != null) { -- domain.fields = req.fields.map(f => Field.toDomain(f)); -- } -- -- switch (req.type) { -- case CipherType.Login: -- domain.login = Login.toDomain(req.login); -- break; -- case CipherType.SecureNote: -- domain.secureNote = SecureNote.toDomain(req.secureNote); -- break; -- case CipherType.Card: -- domain.card = Card.toDomain(req.card); -- break; -- case CipherType.Identity: -- domain.identity = Identity.toDomain(req.identity); -- break; -- } -- -- return domain; -+ return view; -+ } -+ -+ static toDomain(req: Cipher, domain = new CipherDomain()) { -+ domain.type = req.type; -+ domain.folderId = req.folderId; -+ if (domain.organizationId == null) { -+ domain.organizationId = req.organizationId; -+ } -+ domain.name = req.name != null ? new EncString(req.name) : null; -+ domain.notes = req.notes != null ? new EncString(req.notes) : null; -+ domain.favorite = req.favorite; -+ domain.reprompt = req.reprompt ?? CipherRepromptType.None; -+ -+ if (req.fields != null) { -+ domain.fields = req.fields.map((f) => Field.toDomain(f)); -+ } -+ -+ switch (req.type) { -+ case CipherType.Login: -+ domain.login = Login.toDomain(req.login); -+ break; -+ case CipherType.SecureNote: -+ domain.secureNote = SecureNote.toDomain(req.secureNote); -+ break; -+ case CipherType.Card: -+ domain.card = Card.toDomain(req.card); -+ break; -+ case CipherType.Identity: -+ domain.identity = Identity.toDomain(req.identity); -+ break; -+ } -+ -+ return domain; -+ } -+ -+ type: CipherType; -+ folderId: string; -+ organizationId: string; -+ collectionIds: string[]; -+ name: string; -+ notes: string; -+ favorite: boolean; -+ fields: Field[]; -+ login: Login; -+ secureNote: SecureNote; -+ card: Card; -+ identity: Identity; -+ reprompt: CipherRepromptType; -+ -+ // Use build method instead of ctor so that we can control order of JSON stringify for pretty print -+ build(o: CipherView | CipherDomain) { -+ this.organizationId = o.organizationId; -+ this.folderId = o.folderId; -+ this.type = o.type; -+ this.reprompt = o.reprompt; -+ -+ if (o instanceof CipherView) { -+ this.name = o.name; -+ this.notes = o.notes; -+ } else { -+ this.name = o.name?.encryptedString; -+ this.notes = o.notes?.encryptedString; -+ } -+ -+ this.favorite = o.favorite; -+ -+ if (o.fields != null) { -+ if (o instanceof CipherView) { -+ this.fields = o.fields.map((f) => new Field(f)); -+ } else { -+ this.fields = o.fields.map((f) => new Field(f)); -+ } - } - -- type: CipherType; -- folderId: string; -- organizationId: string; -- collectionIds: string[]; -- name: string; -- notes: string; -- favorite: boolean; -- fields: Field[]; -- login: Login; -- secureNote: SecureNote; -- card: Card; -- identity: Identity; -- reprompt: CipherRepromptType; -- -- // Use build method instead of ctor so that we can control order of JSON stringify for pretty print -- build(o: CipherView | CipherDomain) { -- this.organizationId = o.organizationId; -- this.folderId = o.folderId; -- this.type = o.type; -- this.reprompt = o.reprompt; -- -- if (o instanceof CipherView) { -- this.name = o.name; -- this.notes = o.notes; -- } else { -- this.name = o.name?.encryptedString; -- this.notes = o.notes?.encryptedString; -- } -- -- this.favorite = o.favorite; -- -- if (o.fields != null) { -- if (o instanceof CipherView) { -- this.fields = o.fields.map(f => new Field(f)); -- } else { -- this.fields = o.fields.map(f => new Field(f)); -- } -- } -- -- switch (o.type) { -- case CipherType.Login: -- this.login = new Login(o.login); -- break; -- case CipherType.SecureNote: -- this.secureNote = new SecureNote(o.secureNote); -- break; -- case CipherType.Card: -- this.card = new Card(o.card); -- break; -- case CipherType.Identity: -- this.identity = new Identity(o.identity); -- break; -- } -+ switch (o.type) { -+ case CipherType.Login: -+ this.login = new Login(o.login); -+ break; -+ case CipherType.SecureNote: -+ this.secureNote = new SecureNote(o.secureNote); -+ break; -+ case CipherType.Card: -+ this.card = new Card(o.card); -+ break; -+ case CipherType.Identity: -+ this.identity = new Identity(o.identity); -+ break; - } -+ } - } -diff --git a/jslib/common/src/models/export/cipherWithIds.ts b/jslib/common/src/models/export/cipherWithIds.ts -index 184cd542..fccf713e 100644 ---- a/jslib/common/src/models/export/cipherWithIds.ts -+++ b/jslib/common/src/models/export/cipherWithIds.ts -@@ -1,17 +1,17 @@ --import { Cipher } from './cipher'; -+import { Cipher } from "./cipher"; - --import { CipherView } from '../view/cipherView'; -+import { CipherView } from "../view/cipherView"; - --import { Cipher as CipherDomain } from '../domain/cipher'; -+import { Cipher as CipherDomain } from "../domain/cipher"; - - export class CipherWithIds extends Cipher { -- id: string; -- collectionIds: string[]; -+ id: string; -+ collectionIds: string[]; - -- // Use build method instead of ctor so that we can control order of JSON stringify for pretty print -- build(o: CipherView | CipherDomain) { -- this.id = o.id; -- super.build(o); -- this.collectionIds = o.collectionIds; -- } -+ // Use build method instead of ctor so that we can control order of JSON stringify for pretty print -+ build(o: CipherView | CipherDomain) { -+ this.id = o.id; -+ super.build(o); -+ this.collectionIds = o.collectionIds; -+ } - } -diff --git a/jslib/common/src/models/export/collection.ts b/jslib/common/src/models/export/collection.ts -index e628be5b..70d52397 100644 ---- a/jslib/common/src/models/export/collection.ts -+++ b/jslib/common/src/models/export/collection.ts -@@ -1,47 +1,47 @@ --import { CollectionView } from '../view/collectionView'; -+import { CollectionView } from "../view/collectionView"; - --import { Collection as CollectionDomain } from '../domain/collection'; --import { EncString } from '../domain/encString'; -+import { Collection as CollectionDomain } from "../domain/collection"; -+import { EncString } from "../domain/encString"; - - export class Collection { -- static template(): Collection { -- const req = new Collection(); -- req.organizationId = '00000000-0000-0000-0000-000000000000'; -- req.name = 'Collection name'; -- req.externalId = null; -- return req; -- } -+ static template(): Collection { -+ const req = new Collection(); -+ req.organizationId = "00000000-0000-0000-0000-000000000000"; -+ req.name = "Collection name"; -+ req.externalId = null; -+ return req; -+ } - -- static toView(req: Collection, view = new CollectionView()) { -- view.name = req.name; -- view.externalId = req.externalId; -- if (view.organizationId == null) { -- view.organizationId = req.organizationId; -- } -- return view; -+ static toView(req: Collection, view = new CollectionView()) { -+ view.name = req.name; -+ view.externalId = req.externalId; -+ if (view.organizationId == null) { -+ view.organizationId = req.organizationId; - } -+ return view; -+ } - -- static toDomain(req: Collection, domain = new CollectionDomain()) { -- domain.name = req.name != null ? new EncString(req.name) : null; -- domain.externalId = req.externalId; -- if (domain.organizationId == null) { -- domain.organizationId = req.organizationId; -- } -- return domain; -+ static toDomain(req: Collection, domain = new CollectionDomain()) { -+ domain.name = req.name != null ? new EncString(req.name) : null; -+ domain.externalId = req.externalId; -+ if (domain.organizationId == null) { -+ domain.organizationId = req.organizationId; - } -+ return domain; -+ } - -- organizationId: string; -- name: string; -- externalId: string; -+ organizationId: string; -+ name: string; -+ externalId: string; - -- // Use build method instead of ctor so that we can control order of JSON stringify for pretty print -- build(o: CollectionView | CollectionDomain) { -- this.organizationId = o.organizationId; -- if (o instanceof CollectionView) { -- this.name = o.name; -- } else { -- this.name = o.name?.encryptedString; -- } -- this.externalId = o.externalId; -+ // Use build method instead of ctor so that we can control order of JSON stringify for pretty print -+ build(o: CollectionView | CollectionDomain) { -+ this.organizationId = o.organizationId; -+ if (o instanceof CollectionView) { -+ this.name = o.name; -+ } else { -+ this.name = o.name?.encryptedString; - } -+ this.externalId = o.externalId; -+ } - } -diff --git a/jslib/common/src/models/export/collectionWithId.ts b/jslib/common/src/models/export/collectionWithId.ts -index ef48ddd9..d5fcec58 100644 ---- a/jslib/common/src/models/export/collectionWithId.ts -+++ b/jslib/common/src/models/export/collectionWithId.ts -@@ -1,15 +1,15 @@ --import { Collection } from './collection'; -+import { Collection } from "./collection"; - --import { CollectionView } from '../view/collectionView'; -+import { CollectionView } from "../view/collectionView"; - --import { Collection as CollectionDomain } from '../domain/collection'; -+import { Collection as CollectionDomain } from "../domain/collection"; - - export class CollectionWithId extends Collection { -- id: string; -+ id: string; - -- // Use build method instead of ctor so that we can control order of JSON stringify for pretty print -- build(o: CollectionView | CollectionDomain) { -- this.id = o.id; -- super.build(o); -- } -+ // Use build method instead of ctor so that we can control order of JSON stringify for pretty print -+ build(o: CollectionView | CollectionDomain) { -+ this.id = o.id; -+ super.build(o); -+ } - } -diff --git a/jslib/common/src/models/export/event.ts b/jslib/common/src/models/export/event.ts -index 70e1f19e..09975f2f 100644 ---- a/jslib/common/src/models/export/event.ts -+++ b/jslib/common/src/models/export/event.ts -@@ -1,26 +1,26 @@ --import { EventType } from '../../enums/eventType'; --import { EventView } from '../view/eventView'; -+import { EventType } from "../../enums/eventType"; -+import { EventView } from "../view/eventView"; - - export class Event { -- message: string; -- appIcon: string; -- appName: string; -- userId: string; -- userName: string; -- userEmail: string; -- date: string; -- ip: string; -- type: string; -+ message: string; -+ appIcon: string; -+ appName: string; -+ userId: string; -+ userName: string; -+ userEmail: string; -+ date: string; -+ ip: string; -+ type: string; - -- constructor(event: EventView) { -- this.message = event.humanReadableMessage; -- this.appIcon = event.appIcon; -- this.appName = event.appName; -- this.userId = event.userId; -- this.userName = event.userName; -- this.userEmail = event.userEmail; -- this.date = event.date; -- this.ip = event.ip; -- this.type = EventType[event.type]; -- } -+ constructor(event: EventView) { -+ this.message = event.humanReadableMessage; -+ this.appIcon = event.appIcon; -+ this.appName = event.appName; -+ this.userId = event.userId; -+ this.userName = event.userName; -+ this.userEmail = event.userEmail; -+ this.date = event.date; -+ this.ip = event.ip; -+ this.type = EventType[event.type]; -+ } - } -diff --git a/jslib/common/src/models/export/field.ts b/jslib/common/src/models/export/field.ts -index 0804c123..e4353a71 100644 ---- a/jslib/common/src/models/export/field.ts -+++ b/jslib/common/src/models/export/field.ts -@@ -1,54 +1,54 @@ --import { FieldType } from '../../enums/fieldType'; --import { LinkedIdType } from '../../enums/linkedIdType'; -+import { FieldType } from "../../enums/fieldType"; -+import { LinkedIdType } from "../../enums/linkedIdType"; - --import { FieldView } from '../view/fieldView'; -+import { FieldView } from "../view/fieldView"; - --import { EncString } from '../domain/encString'; --import { Field as FieldDomain } from '../domain/field'; -+import { EncString } from "../domain/encString"; -+import { Field as FieldDomain } from "../domain/field"; - - export class Field { -- static template(): Field { -- const req = new Field(); -- req.name = 'Field name'; -- req.value = 'Some value'; -- req.type = FieldType.Text; -- return req; -+ static template(): Field { -+ const req = new Field(); -+ req.name = "Field name"; -+ req.value = "Some value"; -+ req.type = FieldType.Text; -+ return req; -+ } -+ -+ static toView(req: Field, view = new FieldView()) { -+ view.type = req.type; -+ view.value = req.value; -+ view.name = req.name; -+ view.linkedId = req.linkedId; -+ return view; -+ } -+ -+ static toDomain(req: Field, domain = new FieldDomain()) { -+ domain.type = req.type; -+ domain.value = req.value != null ? new EncString(req.value) : null; -+ domain.name = req.name != null ? new EncString(req.name) : null; -+ domain.linkedId = req.linkedId; -+ return domain; -+ } -+ -+ name: string; -+ value: string; -+ type: FieldType; -+ linkedId: LinkedIdType; -+ -+ constructor(o?: FieldView | FieldDomain) { -+ if (o == null) { -+ return; - } - -- static toView(req: Field, view = new FieldView()) { -- view.type = req.type; -- view.value = req.value; -- view.name = req.name; -- view.linkedId = req.linkedId; -- return view; -- } -- -- static toDomain(req: Field, domain = new FieldDomain()) { -- domain.type = req.type; -- domain.value = req.value != null ? new EncString(req.value) : null; -- domain.name = req.name != null ? new EncString(req.name) : null; -- domain.linkedId = req.linkedId; -- return domain; -- } -- -- name: string; -- value: string; -- type: FieldType; -- linkedId: LinkedIdType; -- -- constructor(o?: FieldView | FieldDomain) { -- if (o == null) { -- return; -- } -- -- if (o instanceof FieldView) { -- this.name = o.name; -- this.value = o.value; -- } else { -- this.name = o.name?.encryptedString; -- this.value = o.value?.encryptedString; -- } -- this.type = o.type; -- this.linkedId = o.linkedId; -+ if (o instanceof FieldView) { -+ this.name = o.name; -+ this.value = o.value; -+ } else { -+ this.name = o.name?.encryptedString; -+ this.value = o.value?.encryptedString; - } -+ this.type = o.type; -+ this.linkedId = o.linkedId; -+ } - } -diff --git a/jslib/common/src/models/export/folder.ts b/jslib/common/src/models/export/folder.ts -index 8391fb61..9f015b56 100644 ---- a/jslib/common/src/models/export/folder.ts -+++ b/jslib/common/src/models/export/folder.ts -@@ -1,33 +1,33 @@ --import { FolderView } from '../view/folderView'; -+import { FolderView } from "../view/folderView"; - --import { EncString } from '../domain/encString'; --import { Folder as FolderDomain } from '../domain/folder'; -+import { EncString } from "../domain/encString"; -+import { Folder as FolderDomain } from "../domain/folder"; - - export class Folder { -- static template(): Folder { -- const req = new Folder(); -- req.name = 'Folder name'; -- return req; -- } -+ static template(): Folder { -+ const req = new Folder(); -+ req.name = "Folder name"; -+ return req; -+ } - -- static toView(req: Folder, view = new FolderView()) { -- view.name = req.name; -- return view; -- } -+ static toView(req: Folder, view = new FolderView()) { -+ view.name = req.name; -+ return view; -+ } - -- static toDomain(req: Folder, domain = new FolderDomain()) { -- domain.name = req.name != null ? new EncString(req.name) : null; -- return domain; -- } -+ static toDomain(req: Folder, domain = new FolderDomain()) { -+ domain.name = req.name != null ? new EncString(req.name) : null; -+ return domain; -+ } - -- name: string; -+ name: string; - -- // Use build method instead of ctor so that we can control order of JSON stringify for pretty print -- build(o: FolderView | FolderDomain) { -- if (o instanceof FolderView) { -- this.name = o.name; -- } else { -- this.name = o.name?.encryptedString; -- } -+ // Use build method instead of ctor so that we can control order of JSON stringify for pretty print -+ build(o: FolderView | FolderDomain) { -+ if (o instanceof FolderView) { -+ this.name = o.name; -+ } else { -+ this.name = o.name?.encryptedString; - } -+ } - } -diff --git a/jslib/common/src/models/export/folderWithId.ts b/jslib/common/src/models/export/folderWithId.ts -index 83e57e71..69983cab 100644 ---- a/jslib/common/src/models/export/folderWithId.ts -+++ b/jslib/common/src/models/export/folderWithId.ts -@@ -1,15 +1,15 @@ --import { Folder } from './folder'; -+import { Folder } from "./folder"; - --import { FolderView } from '../view/folderView'; -+import { FolderView } from "../view/folderView"; - --import { Folder as FolderDomain } from '../domain/folder'; -+import { Folder as FolderDomain } from "../domain/folder"; - - export class FolderWithId extends Folder { -- id: string; -+ id: string; - -- // Use build method instead of ctor so that we can control order of JSON stringify for pretty print -- build(o: FolderView | FolderDomain) { -- this.id = o.id; -- super.build(o); -- } -+ // Use build method instead of ctor so that we can control order of JSON stringify for pretty print -+ build(o: FolderView | FolderDomain) { -+ this.id = o.id; -+ super.build(o); -+ } - } -diff --git a/jslib/common/src/models/export/identity.ts b/jslib/common/src/models/export/identity.ts -index abe6fb6c..42fb8865 100644 ---- a/jslib/common/src/models/export/identity.ts -+++ b/jslib/common/src/models/export/identity.ts -@@ -1,138 +1,138 @@ --import { IdentityView } from '../view/identityView'; -+import { IdentityView } from "../view/identityView"; - --import { EncString } from '../domain/encString'; --import { Identity as IdentityDomain } from '../domain/identity'; -+import { EncString } from "../domain/encString"; -+import { Identity as IdentityDomain } from "../domain/identity"; - - export class Identity { -- static template(): Identity { -- const req = new Identity(); -- req.title = 'Mr'; -- req.firstName = 'John'; -- req.middleName = 'William'; -- req.lastName = 'Doe'; -- req.address1 = '123 Any St'; -- req.address2 = 'Apt #123'; -- req.address3 = null; -- req.city = 'New York'; -- req.state = 'NY'; -- req.postalCode = '10001'; -- req.country = 'US'; -- req.company = 'Acme Inc.'; -- req.email = 'john@company.com'; -- req.phone = '5555551234'; -- req.ssn = '000-123-4567'; -- req.username = 'jdoe'; -- req.passportNumber = 'US-123456789'; -- req.licenseNumber = 'D123-12-123-12333'; -- return req; -- } -+ static template(): Identity { -+ const req = new Identity(); -+ req.title = "Mr"; -+ req.firstName = "John"; -+ req.middleName = "William"; -+ req.lastName = "Doe"; -+ req.address1 = "123 Any St"; -+ req.address2 = "Apt #123"; -+ req.address3 = null; -+ req.city = "New York"; -+ req.state = "NY"; -+ req.postalCode = "10001"; -+ req.country = "US"; -+ req.company = "Acme Inc."; -+ req.email = "john@company.com"; -+ req.phone = "5555551234"; -+ req.ssn = "000-123-4567"; -+ req.username = "jdoe"; -+ req.passportNumber = "US-123456789"; -+ req.licenseNumber = "D123-12-123-12333"; -+ return req; -+ } - -- static toView(req: Identity, view = new IdentityView()) { -- view.title = req.title; -- view.firstName = req.firstName; -- view.middleName = req.middleName; -- view.lastName = req.lastName; -- view.address1 = req.address1; -- view.address2 = req.address2; -- view.address3 = req.address3; -- view.city = req.city; -- view.state = req.state; -- view.postalCode = req.postalCode; -- view.country = req.country; -- view.company = req.company; -- view.email = req.email; -- view.phone = req.phone; -- view.ssn = req.ssn; -- view.username = req.username; -- view.passportNumber = req.passportNumber; -- view.licenseNumber = req.licenseNumber; -- return view; -- } -+ static toView(req: Identity, view = new IdentityView()) { -+ view.title = req.title; -+ view.firstName = req.firstName; -+ view.middleName = req.middleName; -+ view.lastName = req.lastName; -+ view.address1 = req.address1; -+ view.address2 = req.address2; -+ view.address3 = req.address3; -+ view.city = req.city; -+ view.state = req.state; -+ view.postalCode = req.postalCode; -+ view.country = req.country; -+ view.company = req.company; -+ view.email = req.email; -+ view.phone = req.phone; -+ view.ssn = req.ssn; -+ view.username = req.username; -+ view.passportNumber = req.passportNumber; -+ view.licenseNumber = req.licenseNumber; -+ return view; -+ } - -- static toDomain(req: Identity, domain = new IdentityDomain()) { -- domain.title = req.title != null ? new EncString(req.title) : null; -- domain.firstName = req.firstName != null ? new EncString(req.firstName) : null; -- domain.middleName = req.middleName != null ? new EncString(req.middleName) : null; -- domain.lastName = req.lastName != null ? new EncString(req.lastName) : null; -- domain.address1 = req.address1 != null ? new EncString(req.address1) : null; -- domain.address2 = req.address2 != null ? new EncString(req.address2) : null; -- domain.address3 = req.address3 != null ? new EncString(req.address3) : null; -- domain.city = req.city != null ? new EncString(req.city) : null; -- domain.state = req.state != null ? new EncString(req.state) : null; -- domain.postalCode = req.postalCode != null ? new EncString(req.postalCode) : null; -- domain.country = req.country != null ? new EncString(req.country) : null; -- domain.company = req.company != null ? new EncString(req.company) : null; -- domain.email = req.email != null ? new EncString(req.email) : null; -- domain.phone = req.phone != null ? new EncString(req.phone) : null; -- domain.ssn = req.ssn != null ? new EncString(req.ssn) : null; -- domain.username = req.username != null ? new EncString(req.username) : null; -- domain.passportNumber = req.passportNumber != null ? new EncString(req.passportNumber) : null; -- domain.licenseNumber = req.licenseNumber != null ? new EncString(req.licenseNumber) : null; -- return domain; -- } -+ static toDomain(req: Identity, domain = new IdentityDomain()) { -+ domain.title = req.title != null ? new EncString(req.title) : null; -+ domain.firstName = req.firstName != null ? new EncString(req.firstName) : null; -+ domain.middleName = req.middleName != null ? new EncString(req.middleName) : null; -+ domain.lastName = req.lastName != null ? new EncString(req.lastName) : null; -+ domain.address1 = req.address1 != null ? new EncString(req.address1) : null; -+ domain.address2 = req.address2 != null ? new EncString(req.address2) : null; -+ domain.address3 = req.address3 != null ? new EncString(req.address3) : null; -+ domain.city = req.city != null ? new EncString(req.city) : null; -+ domain.state = req.state != null ? new EncString(req.state) : null; -+ domain.postalCode = req.postalCode != null ? new EncString(req.postalCode) : null; -+ domain.country = req.country != null ? new EncString(req.country) : null; -+ domain.company = req.company != null ? new EncString(req.company) : null; -+ domain.email = req.email != null ? new EncString(req.email) : null; -+ domain.phone = req.phone != null ? new EncString(req.phone) : null; -+ domain.ssn = req.ssn != null ? new EncString(req.ssn) : null; -+ domain.username = req.username != null ? new EncString(req.username) : null; -+ domain.passportNumber = req.passportNumber != null ? new EncString(req.passportNumber) : null; -+ domain.licenseNumber = req.licenseNumber != null ? new EncString(req.licenseNumber) : null; -+ return domain; -+ } - -- title: string; -- firstName: string; -- middleName: string; -- lastName: string; -- address1: string; -- address2: string; -- address3: string; -- city: string; -- state: string; -- postalCode: string; -- country: string; -- company: string; -- email: string; -- phone: string; -- ssn: string; -- username: string; -- passportNumber: string; -- licenseNumber: string; -+ title: string; -+ firstName: string; -+ middleName: string; -+ lastName: string; -+ address1: string; -+ address2: string; -+ address3: string; -+ city: string; -+ state: string; -+ postalCode: string; -+ country: string; -+ company: string; -+ email: string; -+ phone: string; -+ ssn: string; -+ username: string; -+ passportNumber: string; -+ licenseNumber: string; - -- constructor(o?: IdentityView | IdentityDomain) { -- if (o == null) { -- return; -- } -+ constructor(o?: IdentityView | IdentityDomain) { -+ if (o == null) { -+ return; -+ } - -- if (o instanceof IdentityView) { -- this.title = o.title; -- this.firstName = o.firstName; -- this.middleName = o.middleName; -- this.lastName = o.lastName; -- this.address1 = o.address1; -- this.address2 = o.address2; -- this.address3 = o.address3; -- this.city = o.city; -- this.state = o.state; -- this.postalCode = o.postalCode; -- this.country = o.country; -- this.company = o.company; -- this.email = o.email; -- this.phone = o.phone; -- this.ssn = o.ssn; -- this.username = o.username; -- this.passportNumber = o.passportNumber; -- this.licenseNumber = o.licenseNumber; -- } else { -- this.title = o.title?.encryptedString; -- this.firstName = o.firstName?.encryptedString; -- this.middleName = o.middleName?.encryptedString; -- this.lastName = o.lastName?.encryptedString; -- this.address1 = o.address1?.encryptedString; -- this.address2 = o.address2?.encryptedString; -- this.address3 = o.address3?.encryptedString; -- this.city = o.city?.encryptedString; -- this.state = o.state?.encryptedString; -- this.postalCode = o.postalCode?.encryptedString; -- this.country = o.country?.encryptedString; -- this.company = o.company?.encryptedString; -- this.email = o.email?.encryptedString; -- this.phone = o.phone?.encryptedString; -- this.ssn = o.ssn?.encryptedString; -- this.username = o.username?.encryptedString; -- this.passportNumber = o.passportNumber?.encryptedString; -- this.licenseNumber = o.licenseNumber?.encryptedString; -- } -+ if (o instanceof IdentityView) { -+ this.title = o.title; -+ this.firstName = o.firstName; -+ this.middleName = o.middleName; -+ this.lastName = o.lastName; -+ this.address1 = o.address1; -+ this.address2 = o.address2; -+ this.address3 = o.address3; -+ this.city = o.city; -+ this.state = o.state; -+ this.postalCode = o.postalCode; -+ this.country = o.country; -+ this.company = o.company; -+ this.email = o.email; -+ this.phone = o.phone; -+ this.ssn = o.ssn; -+ this.username = o.username; -+ this.passportNumber = o.passportNumber; -+ this.licenseNumber = o.licenseNumber; -+ } else { -+ this.title = o.title?.encryptedString; -+ this.firstName = o.firstName?.encryptedString; -+ this.middleName = o.middleName?.encryptedString; -+ this.lastName = o.lastName?.encryptedString; -+ this.address1 = o.address1?.encryptedString; -+ this.address2 = o.address2?.encryptedString; -+ this.address3 = o.address3?.encryptedString; -+ this.city = o.city?.encryptedString; -+ this.state = o.state?.encryptedString; -+ this.postalCode = o.postalCode?.encryptedString; -+ this.country = o.country?.encryptedString; -+ this.company = o.company?.encryptedString; -+ this.email = o.email?.encryptedString; -+ this.phone = o.phone?.encryptedString; -+ this.ssn = o.ssn?.encryptedString; -+ this.username = o.username?.encryptedString; -+ this.passportNumber = o.passportNumber?.encryptedString; -+ this.licenseNumber = o.licenseNumber?.encryptedString; - } -+ } - } -diff --git a/jslib/common/src/models/export/login.ts b/jslib/common/src/models/export/login.ts -index 3b6b23a2..d64263b3 100644 ---- a/jslib/common/src/models/export/login.ts -+++ b/jslib/common/src/models/export/login.ts -@@ -1,66 +1,66 @@ --import { LoginUri } from './loginUri'; -+import { LoginUri } from "./loginUri"; - --import { LoginView } from '../view/loginView'; -+import { LoginView } from "../view/loginView"; - --import { EncString } from '../domain/encString'; --import { Login as LoginDomain } from '../domain/login'; -+import { EncString } from "../domain/encString"; -+import { Login as LoginDomain } from "../domain/login"; - - export class Login { -- static template(): Login { -- const req = new Login(); -- req.uris = []; -- req.username = 'jdoe'; -- req.password = 'myp@ssword123'; -- req.totp = 'JBSWY3DPEHPK3PXP'; -- return req; -- } -+ static template(): Login { -+ const req = new Login(); -+ req.uris = []; -+ req.username = "jdoe"; -+ req.password = "myp@ssword123"; -+ req.totp = "JBSWY3DPEHPK3PXP"; -+ return req; -+ } - -- static toView(req: Login, view = new LoginView()) { -- if (req.uris != null) { -- view.uris = req.uris.map(u => LoginUri.toView(u)); -- } -- view.username = req.username; -- view.password = req.password; -- view.totp = req.totp; -- return view; -+ static toView(req: Login, view = new LoginView()) { -+ if (req.uris != null) { -+ view.uris = req.uris.map((u) => LoginUri.toView(u)); - } -+ view.username = req.username; -+ view.password = req.password; -+ view.totp = req.totp; -+ return view; -+ } - -- static toDomain(req: Login, domain = new LoginDomain()) { -- if (req.uris != null) { -- domain.uris = req.uris.map(u => LoginUri.toDomain(u)); -- } -- domain.username = req.username != null ? new EncString(req.username) : null; -- domain.password = req.password != null ? new EncString(req.password) : null; -- domain.totp = req.totp != null ? new EncString(req.totp) : null; -- return domain; -+ static toDomain(req: Login, domain = new LoginDomain()) { -+ if (req.uris != null) { -+ domain.uris = req.uris.map((u) => LoginUri.toDomain(u)); - } -+ domain.username = req.username != null ? new EncString(req.username) : null; -+ domain.password = req.password != null ? new EncString(req.password) : null; -+ domain.totp = req.totp != null ? new EncString(req.totp) : null; -+ return domain; -+ } - -- uris: LoginUri[]; -- username: string; -- password: string; -- totp: string; -+ uris: LoginUri[]; -+ username: string; -+ password: string; -+ totp: string; - -- constructor(o?: LoginView | LoginDomain) { -- if (o == null) { -- return; -- } -+ constructor(o?: LoginView | LoginDomain) { -+ if (o == null) { -+ return; -+ } - -- if (o.uris != null) { -- if (o instanceof LoginView) { -- this.uris = o.uris.map(u => new LoginUri(u)); -- } else { -- this.uris = o.uris.map(u => new LoginUri(u)); -- } -- } -+ if (o.uris != null) { -+ if (o instanceof LoginView) { -+ this.uris = o.uris.map((u) => new LoginUri(u)); -+ } else { -+ this.uris = o.uris.map((u) => new LoginUri(u)); -+ } -+ } - -- if (o instanceof LoginView) { -- this.username = o.username; -- this.password = o.password; -- this.totp = o.totp; -- } else { -- this.username = o.username?.encryptedString; -- this.password = o.password?.encryptedString; -- this.totp = o.totp?.encryptedString; -- } -+ if (o instanceof LoginView) { -+ this.username = o.username; -+ this.password = o.password; -+ this.totp = o.totp; -+ } else { -+ this.username = o.username?.encryptedString; -+ this.password = o.password?.encryptedString; -+ this.totp = o.totp?.encryptedString; - } -+ } - } -diff --git a/jslib/common/src/models/export/loginUri.ts b/jslib/common/src/models/export/loginUri.ts -index 81d6b625..445b1a25 100644 ---- a/jslib/common/src/models/export/loginUri.ts -+++ b/jslib/common/src/models/export/loginUri.ts -@@ -1,43 +1,43 @@ --import { UriMatchType } from '../../enums/uriMatchType'; -+import { UriMatchType } from "../../enums/uriMatchType"; - --import { LoginUriView } from '../view/loginUriView'; -+import { LoginUriView } from "../view/loginUriView"; - --import { EncString } from '../domain/encString'; --import { LoginUri as LoginUriDomain } from '../domain/loginUri'; -+import { EncString } from "../domain/encString"; -+import { LoginUri as LoginUriDomain } from "../domain/loginUri"; - - export class LoginUri { -- static template(): LoginUri { -- const req = new LoginUri(); -- req.uri = 'https://google.com'; -- req.match = null; -- return req; -+ static template(): LoginUri { -+ const req = new LoginUri(); -+ req.uri = "https://google.com"; -+ req.match = null; -+ return req; -+ } -+ -+ static toView(req: LoginUri, view = new LoginUriView()) { -+ view.uri = req.uri; -+ view.match = req.match; -+ return view; -+ } -+ -+ static toDomain(req: LoginUri, domain = new LoginUriDomain()) { -+ domain.uri = req.uri != null ? new EncString(req.uri) : null; -+ domain.match = req.match; -+ return domain; -+ } -+ -+ uri: string; -+ match: UriMatchType = null; -+ -+ constructor(o?: LoginUriView | LoginUriDomain) { -+ if (o == null) { -+ return; - } - -- static toView(req: LoginUri, view = new LoginUriView()) { -- view.uri = req.uri; -- view.match = req.match; -- return view; -- } -- -- static toDomain(req: LoginUri, domain = new LoginUriDomain()) { -- domain.uri = req.uri != null ? new EncString(req.uri) : null; -- domain.match = req.match; -- return domain; -- } -- -- uri: string; -- match: UriMatchType = null; -- -- constructor(o?: LoginUriView | LoginUriDomain) { -- if (o == null) { -- return; -- } -- -- if (o instanceof LoginUriView) { -- this.uri = o.uri; -- } else { -- this.uri = o.uri?.encryptedString; -- } -- this.match = o.match; -+ if (o instanceof LoginUriView) { -+ this.uri = o.uri; -+ } else { -+ this.uri = o.uri?.encryptedString; - } -+ this.match = o.match; -+ } - } -diff --git a/jslib/common/src/models/export/secureNote.ts b/jslib/common/src/models/export/secureNote.ts -index 078f4033..2dc75a9a 100644 ---- a/jslib/common/src/models/export/secureNote.ts -+++ b/jslib/common/src/models/export/secureNote.ts -@@ -1,33 +1,33 @@ --import { SecureNoteType } from '../../enums/secureNoteType'; -+import { SecureNoteType } from "../../enums/secureNoteType"; - --import { SecureNoteView } from '../view/secureNoteView'; -+import { SecureNoteView } from "../view/secureNoteView"; - --import { SecureNote as SecureNoteDomain } from '../domain/secureNote'; -+import { SecureNote as SecureNoteDomain } from "../domain/secureNote"; - - export class SecureNote { -- static template(): SecureNote { -- const req = new SecureNote(); -- req.type = SecureNoteType.Generic; -- return req; -+ static template(): SecureNote { -+ const req = new SecureNote(); -+ req.type = SecureNoteType.Generic; -+ return req; -+ } -+ -+ static toView(req: SecureNote, view = new SecureNoteView()) { -+ view.type = req.type; -+ return view; -+ } -+ -+ static toDomain(req: SecureNote, view = new SecureNoteDomain()) { -+ view.type = req.type; -+ return view; -+ } -+ -+ type: SecureNoteType; -+ -+ constructor(o?: SecureNoteView | SecureNoteDomain) { -+ if (o == null) { -+ return; - } - -- static toView(req: SecureNote, view = new SecureNoteView()) { -- view.type = req.type; -- return view; -- } -- -- static toDomain(req: SecureNote, view = new SecureNoteDomain()) { -- view.type = req.type; -- return view; -- } -- -- type: SecureNoteType; -- -- constructor(o?: SecureNoteView | SecureNoteDomain) { -- if (o == null) { -- return; -- } -- -- this.type = o.type; -- } -+ this.type = o.type; -+ } - } -diff --git a/jslib/common/src/models/request/account/setKeyConnectorKeyRequest.ts b/jslib/common/src/models/request/account/setKeyConnectorKeyRequest.ts -index 87b580d0..b71ce290 100644 ---- a/jslib/common/src/models/request/account/setKeyConnectorKeyRequest.ts -+++ b/jslib/common/src/models/request/account/setKeyConnectorKeyRequest.ts -@@ -1,19 +1,25 @@ --import { KeysRequest } from '../keysRequest'; -+import { KeysRequest } from "../keysRequest"; - --import { KdfType } from '../../../enums/kdfType'; -+import { KdfType } from "../../../enums/kdfType"; - - export class SetKeyConnectorKeyRequest { -- key: string; -- keys: KeysRequest; -- kdf: KdfType; -- kdfIterations: number; -- orgIdentifier: string; -+ key: string; -+ keys: KeysRequest; -+ kdf: KdfType; -+ kdfIterations: number; -+ orgIdentifier: string; - -- constructor(key: string, kdf: KdfType, kdfIterations: number, orgIdentifier: string, keys: KeysRequest) { -- this.key = key; -- this.kdf = kdf; -- this.kdfIterations = kdfIterations; -- this.orgIdentifier = orgIdentifier; -- this.keys = keys; -- } -+ constructor( -+ key: string, -+ kdf: KdfType, -+ kdfIterations: number, -+ orgIdentifier: string, -+ keys: KeysRequest -+ ) { -+ this.key = key; -+ this.kdf = kdf; -+ this.kdfIterations = kdfIterations; -+ this.orgIdentifier = orgIdentifier; -+ this.keys = keys; -+ } - } -diff --git a/jslib/common/src/models/request/account/verifyOTPRequest.ts b/jslib/common/src/models/request/account/verifyOTPRequest.ts -index 8cc8f985..2eb8816e 100644 ---- a/jslib/common/src/models/request/account/verifyOTPRequest.ts -+++ b/jslib/common/src/models/request/account/verifyOTPRequest.ts -@@ -1,7 +1,7 @@ - export class VerifyOTPRequest { -- OTP: string; -+ OTP: string; - -- constructor(OTP: string) { -- this.OTP = OTP; -- } -+ constructor(OTP: string) { -+ this.OTP = OTP; -+ } - } -diff --git a/jslib/common/src/models/request/attachmentRequest.ts b/jslib/common/src/models/request/attachmentRequest.ts -index 466829aa..ea1ea821 100644 ---- a/jslib/common/src/models/request/attachmentRequest.ts -+++ b/jslib/common/src/models/request/attachmentRequest.ts -@@ -1,6 +1,6 @@ - export class AttachmentRequest { -- fileName: string; -- key: string; -- fileSize: number; -- adminRequest: boolean; -+ fileName: string; -+ key: string; -+ fileSize: number; -+ adminRequest: boolean; - } -diff --git a/jslib/common/src/models/request/bitPayInvoiceRequest.ts b/jslib/common/src/models/request/bitPayInvoiceRequest.ts -index 2d1369ea..9042611b 100644 ---- a/jslib/common/src/models/request/bitPayInvoiceRequest.ts -+++ b/jslib/common/src/models/request/bitPayInvoiceRequest.ts -@@ -1,9 +1,9 @@ - export class BitPayInvoiceRequest { -- userId: string; -- organizationId: string; -- credit: boolean; -- amount: number; -- returnUrl: string; -- name: string; -- email: string; -+ userId: string; -+ organizationId: string; -+ credit: boolean; -+ amount: number; -+ returnUrl: string; -+ name: string; -+ email: string; - } -diff --git a/jslib/common/src/models/request/captchaProtectedRequest.ts b/jslib/common/src/models/request/captchaProtectedRequest.ts -index 18b6d6c2..54721e01 100644 ---- a/jslib/common/src/models/request/captchaProtectedRequest.ts -+++ b/jslib/common/src/models/request/captchaProtectedRequest.ts -@@ -1,3 +1,3 @@ - export abstract class CaptchaProtectedRequest { -- captchaResponse: string = null; -+ captchaResponse: string = null; - } -diff --git a/jslib/common/src/models/request/cipherBulkDeleteRequest.ts b/jslib/common/src/models/request/cipherBulkDeleteRequest.ts -index 97fab41b..227f1a66 100644 ---- a/jslib/common/src/models/request/cipherBulkDeleteRequest.ts -+++ b/jslib/common/src/models/request/cipherBulkDeleteRequest.ts -@@ -1,9 +1,9 @@ - export class CipherBulkDeleteRequest { -- ids: string[]; -- organizationId: string; -+ ids: string[]; -+ organizationId: string; - -- constructor(ids: string[], organizationId?: string) { -- this.ids = ids == null ? [] : ids; -- this.organizationId = organizationId; -- } -+ constructor(ids: string[], organizationId?: string) { -+ this.ids = ids == null ? [] : ids; -+ this.organizationId = organizationId; -+ } - } -diff --git a/jslib/common/src/models/request/cipherBulkMoveRequest.ts b/jslib/common/src/models/request/cipherBulkMoveRequest.ts -index c0e8c626..06a737de 100644 ---- a/jslib/common/src/models/request/cipherBulkMoveRequest.ts -+++ b/jslib/common/src/models/request/cipherBulkMoveRequest.ts -@@ -1,9 +1,9 @@ - export class CipherBulkMoveRequest { -- ids: string[]; -- folderId: string; -+ ids: string[]; -+ folderId: string; - -- constructor(ids: string[], folderId: string) { -- this.ids = ids == null ? [] : ids; -- this.folderId = folderId; -- } -+ constructor(ids: string[], folderId: string) { -+ this.ids = ids == null ? [] : ids; -+ this.folderId = folderId; -+ } - } -diff --git a/jslib/common/src/models/request/cipherBulkRestoreRequest.ts b/jslib/common/src/models/request/cipherBulkRestoreRequest.ts -index 546cc924..70e5a4e8 100644 ---- a/jslib/common/src/models/request/cipherBulkRestoreRequest.ts -+++ b/jslib/common/src/models/request/cipherBulkRestoreRequest.ts -@@ -1,7 +1,7 @@ - export class CipherBulkRestoreRequest { -- ids: string[]; -+ ids: string[]; - -- constructor(ids: string[]) { -- this.ids = ids == null ? [] : ids; -- } -+ constructor(ids: string[]) { -+ this.ids = ids == null ? [] : ids; -+ } - } -diff --git a/jslib/common/src/models/request/cipherBulkShareRequest.ts b/jslib/common/src/models/request/cipherBulkShareRequest.ts -index 5d1e6781..be36da9d 100644 ---- a/jslib/common/src/models/request/cipherBulkShareRequest.ts -+++ b/jslib/common/src/models/request/cipherBulkShareRequest.ts -@@ -1,18 +1,18 @@ --import { CipherWithIdRequest } from './cipherWithIdRequest'; -+import { CipherWithIdRequest } from "./cipherWithIdRequest"; - --import { Cipher } from '../domain/cipher'; -+import { Cipher } from "../domain/cipher"; - - export class CipherBulkShareRequest { -- ciphers: CipherWithIdRequest[]; -- collectionIds: string[]; -+ ciphers: CipherWithIdRequest[]; -+ collectionIds: string[]; - -- constructor(ciphers: Cipher[], collectionIds: string[]) { -- if (ciphers != null) { -- this.ciphers = []; -- ciphers.forEach(c => { -- this.ciphers.push(new CipherWithIdRequest(c)); -- }); -- } -- this.collectionIds = collectionIds; -+ constructor(ciphers: Cipher[], collectionIds: string[]) { -+ if (ciphers != null) { -+ this.ciphers = []; -+ ciphers.forEach((c) => { -+ this.ciphers.push(new CipherWithIdRequest(c)); -+ }); - } -+ this.collectionIds = collectionIds; -+ } - } -diff --git a/jslib/common/src/models/request/cipherCollectionsRequest.ts b/jslib/common/src/models/request/cipherCollectionsRequest.ts -index 1473f4e3..8d555389 100644 ---- a/jslib/common/src/models/request/cipherCollectionsRequest.ts -+++ b/jslib/common/src/models/request/cipherCollectionsRequest.ts -@@ -1,7 +1,7 @@ - export class CipherCollectionsRequest { -- collectionIds: string[]; -+ collectionIds: string[]; - -- constructor(collectionIds: string[]) { -- this.collectionIds = collectionIds == null ? [] : collectionIds; -- } -+ constructor(collectionIds: string[]) { -+ this.collectionIds = collectionIds == null ? [] : collectionIds; -+ } - } -diff --git a/jslib/common/src/models/request/cipherCreateRequest.ts b/jslib/common/src/models/request/cipherCreateRequest.ts -index 683d0a52..443ef318 100644 ---- a/jslib/common/src/models/request/cipherCreateRequest.ts -+++ b/jslib/common/src/models/request/cipherCreateRequest.ts -@@ -1,13 +1,13 @@ --import { CipherRequest } from './cipherRequest'; -+import { CipherRequest } from "./cipherRequest"; - --import { Cipher } from '../domain/cipher'; -+import { Cipher } from "../domain/cipher"; - - export class CipherCreateRequest { -- cipher: CipherRequest; -- collectionIds: string[]; -+ cipher: CipherRequest; -+ collectionIds: string[]; - -- constructor(cipher: Cipher) { -- this.cipher = new CipherRequest(cipher); -- this.collectionIds = cipher.collectionIds; -- } -+ constructor(cipher: Cipher) { -+ this.cipher = new CipherRequest(cipher); -+ this.collectionIds = cipher.collectionIds; -+ } - } -diff --git a/jslib/common/src/models/request/cipherRequest.ts b/jslib/common/src/models/request/cipherRequest.ts -index 23f09462..c14274d9 100644 ---- a/jslib/common/src/models/request/cipherRequest.ts -+++ b/jslib/common/src/models/request/cipherRequest.ts -@@ -1,152 +1,166 @@ --import { CipherRepromptType } from '../../enums/cipherRepromptType'; --import { CipherType } from '../../enums/cipherType'; -+import { CipherRepromptType } from "../../enums/cipherRepromptType"; -+import { CipherType } from "../../enums/cipherType"; - --import { Cipher } from '../domain/cipher'; -+import { Cipher } from "../domain/cipher"; - --import { CardApi } from '../api/cardApi'; --import { FieldApi } from '../api/fieldApi'; --import { IdentityApi } from '../api/identityApi'; --import { LoginApi } from '../api/loginApi'; --import { LoginUriApi } from '../api/loginUriApi'; --import { SecureNoteApi } from '../api/secureNoteApi'; -+import { CardApi } from "../api/cardApi"; -+import { FieldApi } from "../api/fieldApi"; -+import { IdentityApi } from "../api/identityApi"; -+import { LoginApi } from "../api/loginApi"; -+import { LoginUriApi } from "../api/loginUriApi"; -+import { SecureNoteApi } from "../api/secureNoteApi"; - --import { AttachmentRequest } from './attachmentRequest'; --import { PasswordHistoryRequest } from './passwordHistoryRequest'; -+import { AttachmentRequest } from "./attachmentRequest"; -+import { PasswordHistoryRequest } from "./passwordHistoryRequest"; - - export class CipherRequest { -- type: CipherType; -- folderId: string; -- organizationId: string; -- name: string; -- notes: string; -- favorite: boolean; -- login: LoginApi; -- secureNote: SecureNoteApi; -- card: CardApi; -- identity: IdentityApi; -- fields: FieldApi[]; -- passwordHistory: PasswordHistoryRequest[]; -- // Deprecated, remove at some point and rename attachments2 to attachments -- attachments: { [id: string]: string; }; -- attachments2: { [id: string]: AttachmentRequest; }; -- lastKnownRevisionDate: Date; -- reprompt: CipherRepromptType; -+ type: CipherType; -+ folderId: string; -+ organizationId: string; -+ name: string; -+ notes: string; -+ favorite: boolean; -+ login: LoginApi; -+ secureNote: SecureNoteApi; -+ card: CardApi; -+ identity: IdentityApi; -+ fields: FieldApi[]; -+ passwordHistory: PasswordHistoryRequest[]; -+ // Deprecated, remove at some point and rename attachments2 to attachments -+ attachments: { [id: string]: string }; -+ attachments2: { [id: string]: AttachmentRequest }; -+ lastKnownRevisionDate: Date; -+ reprompt: CipherRepromptType; - -- constructor(cipher: Cipher) { -- this.type = cipher.type; -- this.folderId = cipher.folderId; -- this.organizationId = cipher.organizationId; -- this.name = cipher.name ? cipher.name.encryptedString : null; -- this.notes = cipher.notes ? cipher.notes.encryptedString : null; -- this.favorite = cipher.favorite; -- this.lastKnownRevisionDate = cipher.revisionDate; -- this.reprompt = cipher.reprompt; -+ constructor(cipher: Cipher) { -+ this.type = cipher.type; -+ this.folderId = cipher.folderId; -+ this.organizationId = cipher.organizationId; -+ this.name = cipher.name ? cipher.name.encryptedString : null; -+ this.notes = cipher.notes ? cipher.notes.encryptedString : null; -+ this.favorite = cipher.favorite; -+ this.lastKnownRevisionDate = cipher.revisionDate; -+ this.reprompt = cipher.reprompt; - -- switch (this.type) { -- case CipherType.Login: -- this.login = new LoginApi(); -- this.login.uris = null; -- this.login.username = cipher.login.username ? cipher.login.username.encryptedString : null; -- this.login.password = cipher.login.password ? cipher.login.password.encryptedString : null; -- this.login.passwordRevisionDate = cipher.login.passwordRevisionDate != null ? -- cipher.login.passwordRevisionDate.toISOString() : null; -- this.login.totp = cipher.login.totp ? cipher.login.totp.encryptedString : null; -- this.login.autofillOnPageLoad = cipher.login.autofillOnPageLoad; -+ switch (this.type) { -+ case CipherType.Login: -+ this.login = new LoginApi(); -+ this.login.uris = null; -+ this.login.username = cipher.login.username ? cipher.login.username.encryptedString : null; -+ this.login.password = cipher.login.password ? cipher.login.password.encryptedString : null; -+ this.login.passwordRevisionDate = -+ cipher.login.passwordRevisionDate != null -+ ? cipher.login.passwordRevisionDate.toISOString() -+ : null; -+ this.login.totp = cipher.login.totp ? cipher.login.totp.encryptedString : null; -+ this.login.autofillOnPageLoad = cipher.login.autofillOnPageLoad; - -- if (cipher.login.uris != null) { -- this.login.uris = cipher.login.uris.map(u => { -- const uri = new LoginUriApi(); -- uri.uri = u.uri != null ? u.uri.encryptedString : null; -- uri.match = u.match != null ? u.match : null; -- return uri; -- }); -- } -- break; -- case CipherType.SecureNote: -- this.secureNote = new SecureNoteApi(); -- this.secureNote.type = cipher.secureNote.type; -- break; -- case CipherType.Card: -- this.card = new CardApi(); -- this.card.cardholderName = cipher.card.cardholderName != null ? -- cipher.card.cardholderName.encryptedString : null; -- this.card.brand = cipher.card.brand != null ? cipher.card.brand.encryptedString : null; -- this.card.number = cipher.card.number != null ? cipher.card.number.encryptedString : null; -- this.card.expMonth = cipher.card.expMonth != null ? cipher.card.expMonth.encryptedString : null; -- this.card.expYear = cipher.card.expYear != null ? cipher.card.expYear.encryptedString : null; -- this.card.code = cipher.card.code != null ? cipher.card.code.encryptedString : null; -- break; -- case CipherType.Identity: -- this.identity = new IdentityApi(); -- this.identity.title = cipher.identity.title != null ? cipher.identity.title.encryptedString : null; -- this.identity.firstName = cipher.identity.firstName != null ? -- cipher.identity.firstName.encryptedString : null; -- this.identity.middleName = cipher.identity.middleName != null ? -- cipher.identity.middleName.encryptedString : null; -- this.identity.lastName = cipher.identity.lastName != null ? -- cipher.identity.lastName.encryptedString : null; -- this.identity.address1 = cipher.identity.address1 != null ? -- cipher.identity.address1.encryptedString : null; -- this.identity.address2 = cipher.identity.address2 != null ? -- cipher.identity.address2.encryptedString : null; -- this.identity.address3 = cipher.identity.address3 != null ? -- cipher.identity.address3.encryptedString : null; -- this.identity.city = cipher.identity.city != null ? cipher.identity.city.encryptedString : null; -- this.identity.state = cipher.identity.state != null ? cipher.identity.state.encryptedString : null; -- this.identity.postalCode = cipher.identity.postalCode != null ? -- cipher.identity.postalCode.encryptedString : null; -- this.identity.country = cipher.identity.country != null ? -- cipher.identity.country.encryptedString : null; -- this.identity.company = cipher.identity.company != null ? -- cipher.identity.company.encryptedString : null; -- this.identity.email = cipher.identity.email != null ? cipher.identity.email.encryptedString : null; -- this.identity.phone = cipher.identity.phone != null ? cipher.identity.phone.encryptedString : null; -- this.identity.ssn = cipher.identity.ssn != null ? cipher.identity.ssn.encryptedString : null; -- this.identity.username = cipher.identity.username != null ? -- cipher.identity.username.encryptedString : null; -- this.identity.passportNumber = cipher.identity.passportNumber != null ? -- cipher.identity.passportNumber.encryptedString : null; -- this.identity.licenseNumber = cipher.identity.licenseNumber != null ? -- cipher.identity.licenseNumber.encryptedString : null; -- break; -- default: -- break; -+ if (cipher.login.uris != null) { -+ this.login.uris = cipher.login.uris.map((u) => { -+ const uri = new LoginUriApi(); -+ uri.uri = u.uri != null ? u.uri.encryptedString : null; -+ uri.match = u.match != null ? u.match : null; -+ return uri; -+ }); - } -+ break; -+ case CipherType.SecureNote: -+ this.secureNote = new SecureNoteApi(); -+ this.secureNote.type = cipher.secureNote.type; -+ break; -+ case CipherType.Card: -+ this.card = new CardApi(); -+ this.card.cardholderName = -+ cipher.card.cardholderName != null ? cipher.card.cardholderName.encryptedString : null; -+ this.card.brand = cipher.card.brand != null ? cipher.card.brand.encryptedString : null; -+ this.card.number = cipher.card.number != null ? cipher.card.number.encryptedString : null; -+ this.card.expMonth = -+ cipher.card.expMonth != null ? cipher.card.expMonth.encryptedString : null; -+ this.card.expYear = -+ cipher.card.expYear != null ? cipher.card.expYear.encryptedString : null; -+ this.card.code = cipher.card.code != null ? cipher.card.code.encryptedString : null; -+ break; -+ case CipherType.Identity: -+ this.identity = new IdentityApi(); -+ this.identity.title = -+ cipher.identity.title != null ? cipher.identity.title.encryptedString : null; -+ this.identity.firstName = -+ cipher.identity.firstName != null ? cipher.identity.firstName.encryptedString : null; -+ this.identity.middleName = -+ cipher.identity.middleName != null ? cipher.identity.middleName.encryptedString : null; -+ this.identity.lastName = -+ cipher.identity.lastName != null ? cipher.identity.lastName.encryptedString : null; -+ this.identity.address1 = -+ cipher.identity.address1 != null ? cipher.identity.address1.encryptedString : null; -+ this.identity.address2 = -+ cipher.identity.address2 != null ? cipher.identity.address2.encryptedString : null; -+ this.identity.address3 = -+ cipher.identity.address3 != null ? cipher.identity.address3.encryptedString : null; -+ this.identity.city = -+ cipher.identity.city != null ? cipher.identity.city.encryptedString : null; -+ this.identity.state = -+ cipher.identity.state != null ? cipher.identity.state.encryptedString : null; -+ this.identity.postalCode = -+ cipher.identity.postalCode != null ? cipher.identity.postalCode.encryptedString : null; -+ this.identity.country = -+ cipher.identity.country != null ? cipher.identity.country.encryptedString : null; -+ this.identity.company = -+ cipher.identity.company != null ? cipher.identity.company.encryptedString : null; -+ this.identity.email = -+ cipher.identity.email != null ? cipher.identity.email.encryptedString : null; -+ this.identity.phone = -+ cipher.identity.phone != null ? cipher.identity.phone.encryptedString : null; -+ this.identity.ssn = -+ cipher.identity.ssn != null ? cipher.identity.ssn.encryptedString : null; -+ this.identity.username = -+ cipher.identity.username != null ? cipher.identity.username.encryptedString : null; -+ this.identity.passportNumber = -+ cipher.identity.passportNumber != null -+ ? cipher.identity.passportNumber.encryptedString -+ : null; -+ this.identity.licenseNumber = -+ cipher.identity.licenseNumber != null -+ ? cipher.identity.licenseNumber.encryptedString -+ : null; -+ break; -+ default: -+ break; -+ } - -- if (cipher.fields != null) { -- this.fields = cipher.fields.map(f => { -- const field = new FieldApi(); -- field.type = f.type; -- field.name = f.name ? f.name.encryptedString : null; -- field.value = f.value ? f.value.encryptedString : null; -- field.linkedId = f.linkedId; -- return field; -- }); -- } -+ if (cipher.fields != null) { -+ this.fields = cipher.fields.map((f) => { -+ const field = new FieldApi(); -+ field.type = f.type; -+ field.name = f.name ? f.name.encryptedString : null; -+ field.value = f.value ? f.value.encryptedString : null; -+ field.linkedId = f.linkedId; -+ return field; -+ }); -+ } - -- if (cipher.passwordHistory != null) { -- this.passwordHistory = []; -- cipher.passwordHistory.forEach(ph => { -- this.passwordHistory.push({ -- lastUsedDate: ph.lastUsedDate, -- password: ph.password ? ph.password.encryptedString : null, -- }); -- }); -- } -+ if (cipher.passwordHistory != null) { -+ this.passwordHistory = []; -+ cipher.passwordHistory.forEach((ph) => { -+ this.passwordHistory.push({ -+ lastUsedDate: ph.lastUsedDate, -+ password: ph.password ? ph.password.encryptedString : null, -+ }); -+ }); -+ } - -- if (cipher.attachments != null) { -- this.attachments = {}; -- this.attachments2 = {}; -- cipher.attachments.forEach(attachment => { -- const fileName = attachment.fileName ? attachment.fileName.encryptedString : null; -- this.attachments[attachment.id] = fileName; -- const attachmentRequest = new AttachmentRequest(); -- attachmentRequest.fileName = fileName; -- if (attachment.key != null) { -- attachmentRequest.key = attachment.key.encryptedString; -- } -- this.attachments2[attachment.id] = attachmentRequest; -- }); -+ if (cipher.attachments != null) { -+ this.attachments = {}; -+ this.attachments2 = {}; -+ cipher.attachments.forEach((attachment) => { -+ const fileName = attachment.fileName ? attachment.fileName.encryptedString : null; -+ this.attachments[attachment.id] = fileName; -+ const attachmentRequest = new AttachmentRequest(); -+ attachmentRequest.fileName = fileName; -+ if (attachment.key != null) { -+ attachmentRequest.key = attachment.key.encryptedString; - } -+ this.attachments2[attachment.id] = attachmentRequest; -+ }); - } -+ } - } -diff --git a/jslib/common/src/models/request/cipherShareRequest.ts b/jslib/common/src/models/request/cipherShareRequest.ts -index 46468902..430e4ef8 100644 ---- a/jslib/common/src/models/request/cipherShareRequest.ts -+++ b/jslib/common/src/models/request/cipherShareRequest.ts -@@ -1,13 +1,13 @@ --import { CipherRequest } from './cipherRequest'; -+import { CipherRequest } from "./cipherRequest"; - --import { Cipher } from '../domain/cipher'; -+import { Cipher } from "../domain/cipher"; - - export class CipherShareRequest { -- cipher: CipherRequest; -- collectionIds: string[]; -+ cipher: CipherRequest; -+ collectionIds: string[]; - -- constructor(cipher: Cipher) { -- this.cipher = new CipherRequest(cipher); -- this.collectionIds = cipher.collectionIds; -- } -+ constructor(cipher: Cipher) { -+ this.cipher = new CipherRequest(cipher); -+ this.collectionIds = cipher.collectionIds; -+ } - } -diff --git a/jslib/common/src/models/request/cipherWithIdRequest.ts b/jslib/common/src/models/request/cipherWithIdRequest.ts -index a3d5e9d8..a722e4a9 100644 ---- a/jslib/common/src/models/request/cipherWithIdRequest.ts -+++ b/jslib/common/src/models/request/cipherWithIdRequest.ts -@@ -1,12 +1,12 @@ --import { CipherRequest } from './cipherRequest'; -+import { CipherRequest } from "./cipherRequest"; - --import { Cipher } from '../domain/cipher'; -+import { Cipher } from "../domain/cipher"; - - export class CipherWithIdRequest extends CipherRequest { -- id: string; -+ id: string; - -- constructor(cipher: Cipher) { -- super(cipher); -- this.id = cipher.id; -- } -+ constructor(cipher: Cipher) { -+ super(cipher); -+ this.id = cipher.id; -+ } - } -diff --git a/jslib/common/src/models/request/collectionRequest.ts b/jslib/common/src/models/request/collectionRequest.ts -index 20f86956..c2176095 100644 ---- a/jslib/common/src/models/request/collectionRequest.ts -+++ b/jslib/common/src/models/request/collectionRequest.ts -@@ -1,17 +1,17 @@ --import { Collection } from '../domain/collection'; -+import { Collection } from "../domain/collection"; - --import { SelectionReadOnlyRequest } from './selectionReadOnlyRequest'; -+import { SelectionReadOnlyRequest } from "./selectionReadOnlyRequest"; - - export class CollectionRequest { -- name: string; -- externalId: string; -- groups: SelectionReadOnlyRequest[] = []; -+ name: string; -+ externalId: string; -+ groups: SelectionReadOnlyRequest[] = []; - -- constructor(collection?: Collection) { -- if (collection == null) { -- return; -- } -- this.name = collection.name ? collection.name.encryptedString : null; -- this.externalId = collection.externalId; -+ constructor(collection?: Collection) { -+ if (collection == null) { -+ return; - } -+ this.name = collection.name ? collection.name.encryptedString : null; -+ this.externalId = collection.externalId; -+ } - } -diff --git a/jslib/common/src/models/request/deleteRecoverRequest.ts b/jslib/common/src/models/request/deleteRecoverRequest.ts -index 90d6064f..02a019b8 100644 ---- a/jslib/common/src/models/request/deleteRecoverRequest.ts -+++ b/jslib/common/src/models/request/deleteRecoverRequest.ts -@@ -1,3 +1,3 @@ - export class DeleteRecoverRequest { -- email: string; -+ email: string; - } -diff --git a/jslib/common/src/models/request/deviceRequest.ts b/jslib/common/src/models/request/deviceRequest.ts -index 2aaa42cb..06da38a1 100644 ---- a/jslib/common/src/models/request/deviceRequest.ts -+++ b/jslib/common/src/models/request/deviceRequest.ts -@@ -1,17 +1,17 @@ --import { DeviceType } from '../../enums/deviceType'; -+import { DeviceType } from "../../enums/deviceType"; - --import { PlatformUtilsService } from '../../abstractions/platformUtils.service'; -+import { PlatformUtilsService } from "../../abstractions/platformUtils.service"; - - export class DeviceRequest { -- type: DeviceType; -- name: string; -- identifier: string; -- pushToken?: string; -+ type: DeviceType; -+ name: string; -+ identifier: string; -+ pushToken?: string; - -- constructor(appId: string, platformUtilsService: PlatformUtilsService) { -- this.type = platformUtilsService.getDevice(); -- this.name = platformUtilsService.getDeviceString(); -- this.identifier = appId; -- this.pushToken = null; -- } -+ constructor(appId: string, platformUtilsService: PlatformUtilsService) { -+ this.type = platformUtilsService.getDevice(); -+ this.name = platformUtilsService.getDeviceString(); -+ this.identifier = appId; -+ this.pushToken = null; -+ } - } -diff --git a/jslib/common/src/models/request/deviceTokenRequest.ts b/jslib/common/src/models/request/deviceTokenRequest.ts -index 5d663f60..99ca69a2 100644 ---- a/jslib/common/src/models/request/deviceTokenRequest.ts -+++ b/jslib/common/src/models/request/deviceTokenRequest.ts -@@ -1,7 +1,7 @@ - export class DeviceTokenRequest { -- pushToken: string; -+ pushToken: string; - -- constructor() { -- this.pushToken = null; -- } -+ constructor() { -+ this.pushToken = null; -+ } - } -diff --git a/jslib/common/src/models/request/emailRequest.ts b/jslib/common/src/models/request/emailRequest.ts -index a2172a1e..0b559615 100644 ---- a/jslib/common/src/models/request/emailRequest.ts -+++ b/jslib/common/src/models/request/emailRequest.ts -@@ -1,7 +1,7 @@ --import { EmailTokenRequest } from './emailTokenRequest'; -+import { EmailTokenRequest } from "./emailTokenRequest"; - - export class EmailRequest extends EmailTokenRequest { -- newMasterPasswordHash: string; -- token: string; -- key: string; -+ newMasterPasswordHash: string; -+ token: string; -+ key: string; - } -diff --git a/jslib/common/src/models/request/emailTokenRequest.ts b/jslib/common/src/models/request/emailTokenRequest.ts -index 90a806b6..7e0515ca 100644 ---- a/jslib/common/src/models/request/emailTokenRequest.ts -+++ b/jslib/common/src/models/request/emailTokenRequest.ts -@@ -1,6 +1,6 @@ --import { SecretVerificationRequest } from './secretVerificationRequest'; -+import { SecretVerificationRequest } from "./secretVerificationRequest"; - - export class EmailTokenRequest extends SecretVerificationRequest { -- newEmail: string; -- masterPasswordHash: string; -+ newEmail: string; -+ masterPasswordHash: string; - } -diff --git a/jslib/common/src/models/request/emergencyAccessAcceptRequest.ts b/jslib/common/src/models/request/emergencyAccessAcceptRequest.ts -index ff6f41c4..1cb10253 100644 ---- a/jslib/common/src/models/request/emergencyAccessAcceptRequest.ts -+++ b/jslib/common/src/models/request/emergencyAccessAcceptRequest.ts -@@ -1,3 +1,3 @@ - export class EmergencyAccessAcceptRequest { -- token: string; -+ token: string; - } -diff --git a/jslib/common/src/models/request/emergencyAccessConfirmRequest.ts b/jslib/common/src/models/request/emergencyAccessConfirmRequest.ts -index 5b138c84..ee54a4fe 100644 ---- a/jslib/common/src/models/request/emergencyAccessConfirmRequest.ts -+++ b/jslib/common/src/models/request/emergencyAccessConfirmRequest.ts -@@ -1,3 +1,3 @@ - export class EmergencyAccessConfirmRequest { -- key: string; -+ key: string; - } -diff --git a/jslib/common/src/models/request/emergencyAccessInviteRequest.ts b/jslib/common/src/models/request/emergencyAccessInviteRequest.ts -index f97620d5..d75ed419 100644 ---- a/jslib/common/src/models/request/emergencyAccessInviteRequest.ts -+++ b/jslib/common/src/models/request/emergencyAccessInviteRequest.ts -@@ -1,7 +1,7 @@ --import { EmergencyAccessType } from '../../enums/emergencyAccessType'; -+import { EmergencyAccessType } from "../../enums/emergencyAccessType"; - - export class EmergencyAccessInviteRequest { -- email: string; -- type: EmergencyAccessType; -- waitTimeDays: number; -+ email: string; -+ type: EmergencyAccessType; -+ waitTimeDays: number; - } -diff --git a/jslib/common/src/models/request/emergencyAccessPasswordRequest.ts b/jslib/common/src/models/request/emergencyAccessPasswordRequest.ts -index 9d95f86b..3fb459e1 100644 ---- a/jslib/common/src/models/request/emergencyAccessPasswordRequest.ts -+++ b/jslib/common/src/models/request/emergencyAccessPasswordRequest.ts -@@ -1,4 +1,4 @@ - export class EmergencyAccessPasswordRequest { -- newMasterPasswordHash: string; -- key: string; -+ newMasterPasswordHash: string; -+ key: string; - } -diff --git a/jslib/common/src/models/request/emergencyAccessUpdateRequest.ts b/jslib/common/src/models/request/emergencyAccessUpdateRequest.ts -index ed535cb0..d7c55c94 100644 ---- a/jslib/common/src/models/request/emergencyAccessUpdateRequest.ts -+++ b/jslib/common/src/models/request/emergencyAccessUpdateRequest.ts -@@ -1,7 +1,7 @@ --import { EmergencyAccessType } from '../../enums/emergencyAccessType'; -+import { EmergencyAccessType } from "../../enums/emergencyAccessType"; - - export class EmergencyAccessUpdateRequest { -- type: EmergencyAccessType; -- waitTimeDays: number; -- keyEncrypted?: string; -+ type: EmergencyAccessType; -+ waitTimeDays: number; -+ keyEncrypted?: string; - } -diff --git a/jslib/common/src/models/request/eventRequest.ts b/jslib/common/src/models/request/eventRequest.ts -index 83bfa390..a6228fd6 100644 ---- a/jslib/common/src/models/request/eventRequest.ts -+++ b/jslib/common/src/models/request/eventRequest.ts -@@ -1,7 +1,7 @@ --import { EventType } from '../../enums/eventType'; -+import { EventType } from "../../enums/eventType"; - - export class EventRequest { -- type: EventType; -- cipherId: string; -- date: string; -+ type: EventType; -+ cipherId: string; -+ date: string; - } -diff --git a/jslib/common/src/models/request/folderRequest.ts b/jslib/common/src/models/request/folderRequest.ts -index 54ec76ca..a37f66dd 100644 ---- a/jslib/common/src/models/request/folderRequest.ts -+++ b/jslib/common/src/models/request/folderRequest.ts -@@ -1,9 +1,9 @@ --import { Folder } from '../domain/folder'; -+import { Folder } from "../domain/folder"; - - export class FolderRequest { -- name: string; -+ name: string; - -- constructor(folder: Folder) { -- this.name = folder.name ? folder.name.encryptedString : null; -- } -+ constructor(folder: Folder) { -+ this.name = folder.name ? folder.name.encryptedString : null; -+ } - } -diff --git a/jslib/common/src/models/request/folderWithIdRequest.ts b/jslib/common/src/models/request/folderWithIdRequest.ts -index a97ad6d8..1c83e73f 100644 ---- a/jslib/common/src/models/request/folderWithIdRequest.ts -+++ b/jslib/common/src/models/request/folderWithIdRequest.ts -@@ -1,12 +1,12 @@ --import { FolderRequest } from './folderRequest'; -+import { FolderRequest } from "./folderRequest"; - --import { Folder } from '../domain/folder'; -+import { Folder } from "../domain/folder"; - - export class FolderWithIdRequest extends FolderRequest { -- id: string; -+ id: string; - -- constructor(folder: Folder) { -- super(folder); -- this.id = folder.id; -- } -+ constructor(folder: Folder) { -+ super(folder); -+ this.id = folder.id; -+ } - } -diff --git a/jslib/common/src/models/request/groupRequest.ts b/jslib/common/src/models/request/groupRequest.ts -index d3e18cf1..c4c349c8 100644 ---- a/jslib/common/src/models/request/groupRequest.ts -+++ b/jslib/common/src/models/request/groupRequest.ts -@@ -1,8 +1,8 @@ --import { SelectionReadOnlyRequest } from './selectionReadOnlyRequest'; -+import { SelectionReadOnlyRequest } from "./selectionReadOnlyRequest"; - - export class GroupRequest { -- name: string; -- accessAll: boolean; -- externalId: string; -- collections: SelectionReadOnlyRequest[] = []; -+ name: string; -+ accessAll: boolean; -+ externalId: string; -+ collections: SelectionReadOnlyRequest[] = []; - } -diff --git a/jslib/common/src/models/request/iapCheckRequest.ts b/jslib/common/src/models/request/iapCheckRequest.ts -index 75ad723c..b8796e8c 100644 ---- a/jslib/common/src/models/request/iapCheckRequest.ts -+++ b/jslib/common/src/models/request/iapCheckRequest.ts -@@ -1,5 +1,5 @@ --import { PaymentMethodType } from '../../enums/paymentMethodType'; -+import { PaymentMethodType } from "../../enums/paymentMethodType"; - - export class IapCheckRequest { -- paymentMethodType: PaymentMethodType; -+ paymentMethodType: PaymentMethodType; - } -diff --git a/jslib/common/src/models/request/importCiphersRequest.ts b/jslib/common/src/models/request/importCiphersRequest.ts -index eec62602..ebaa03a3 100644 ---- a/jslib/common/src/models/request/importCiphersRequest.ts -+++ b/jslib/common/src/models/request/importCiphersRequest.ts -@@ -1,9 +1,9 @@ --import { CipherRequest } from './cipherRequest'; --import { FolderRequest } from './folderRequest'; --import { KvpRequest } from './kvpRequest'; -+import { CipherRequest } from "./cipherRequest"; -+import { FolderRequest } from "./folderRequest"; -+import { KvpRequest } from "./kvpRequest"; - - export class ImportCiphersRequest { -- ciphers: CipherRequest[] = []; -- folders: FolderRequest[] = []; -- folderRelationships: KvpRequest[] = []; -+ ciphers: CipherRequest[] = []; -+ folders: FolderRequest[] = []; -+ folderRelationships: KvpRequest[] = []; - } -diff --git a/jslib/common/src/models/request/importDirectoryRequest.ts b/jslib/common/src/models/request/importDirectoryRequest.ts -index 16bf69b9..a13ce456 100644 ---- a/jslib/common/src/models/request/importDirectoryRequest.ts -+++ b/jslib/common/src/models/request/importDirectoryRequest.ts -@@ -1,9 +1,9 @@ --import { ImportDirectoryRequestGroup } from './importDirectoryRequestGroup'; --import { ImportDirectoryRequestUser } from './importDirectoryRequestUser'; -+import { ImportDirectoryRequestGroup } from "./importDirectoryRequestGroup"; -+import { ImportDirectoryRequestUser } from "./importDirectoryRequestUser"; - - export class ImportDirectoryRequest { -- groups: ImportDirectoryRequestGroup[] = []; -- users: ImportDirectoryRequestUser[] = []; -- overwriteExisting = false; -- largeImport = false; -+ groups: ImportDirectoryRequestGroup[] = []; -+ users: ImportDirectoryRequestUser[] = []; -+ overwriteExisting = false; -+ largeImport = false; - } -diff --git a/jslib/common/src/models/request/importDirectoryRequestGroup.ts b/jslib/common/src/models/request/importDirectoryRequestGroup.ts -index 8e4f7f4a..4b7b3567 100644 ---- a/jslib/common/src/models/request/importDirectoryRequestGroup.ts -+++ b/jslib/common/src/models/request/importDirectoryRequestGroup.ts -@@ -1,5 +1,5 @@ - export class ImportDirectoryRequestGroup { -- name: string; -- externalId: string; -- users: string[]; -+ name: string; -+ externalId: string; -+ users: string[]; - } -diff --git a/jslib/common/src/models/request/importDirectoryRequestUser.ts b/jslib/common/src/models/request/importDirectoryRequestUser.ts -index 99d699e7..9dbf6a34 100644 ---- a/jslib/common/src/models/request/importDirectoryRequestUser.ts -+++ b/jslib/common/src/models/request/importDirectoryRequestUser.ts -@@ -1,5 +1,5 @@ - export class ImportDirectoryRequestUser { -- externalId: string; -- email: string; -- deleted: boolean; -+ externalId: string; -+ email: string; -+ deleted: boolean; - } -diff --git a/jslib/common/src/models/request/importOrganizationCiphersRequest.ts b/jslib/common/src/models/request/importOrganizationCiphersRequest.ts -index a19293c2..f2936afb 100644 ---- a/jslib/common/src/models/request/importOrganizationCiphersRequest.ts -+++ b/jslib/common/src/models/request/importOrganizationCiphersRequest.ts -@@ -1,9 +1,9 @@ --import { CipherRequest } from './cipherRequest'; --import { CollectionRequest } from './collectionRequest'; --import { KvpRequest } from './kvpRequest'; -+import { CipherRequest } from "./cipherRequest"; -+import { CollectionRequest } from "./collectionRequest"; -+import { KvpRequest } from "./kvpRequest"; - - export class ImportOrganizationCiphersRequest { -- ciphers: CipherRequest[] = []; -- collections: CollectionRequest[] = []; -- collectionRelationships: KvpRequest[] = []; -+ ciphers: CipherRequest[] = []; -+ collections: CollectionRequest[] = []; -+ collectionRelationships: KvpRequest[] = []; - } -diff --git a/jslib/common/src/models/request/kdfRequest.ts b/jslib/common/src/models/request/kdfRequest.ts -index 996aab0f..82a031b1 100644 ---- a/jslib/common/src/models/request/kdfRequest.ts -+++ b/jslib/common/src/models/request/kdfRequest.ts -@@ -1,8 +1,8 @@ --import { PasswordRequest } from './passwordRequest'; -+import { PasswordRequest } from "./passwordRequest"; - --import { KdfType } from '../../enums/kdfType'; -+import { KdfType } from "../../enums/kdfType"; - - export class KdfRequest extends PasswordRequest { -- kdf: KdfType; -- kdfIterations: number; -+ kdf: KdfType; -+ kdfIterations: number; - } -diff --git a/jslib/common/src/models/request/keyConnectorUserKeyRequest.ts b/jslib/common/src/models/request/keyConnectorUserKeyRequest.ts -index 182de57f..3df2db82 100644 ---- a/jslib/common/src/models/request/keyConnectorUserKeyRequest.ts -+++ b/jslib/common/src/models/request/keyConnectorUserKeyRequest.ts -@@ -1,7 +1,7 @@ - export class KeyConnectorUserKeyRequest { -- key: string; -+ key: string; - -- constructor(key: string) { -- this.key = key; -- } -+ constructor(key: string) { -+ this.key = key; -+ } - } -diff --git a/jslib/common/src/models/request/keysRequest.ts b/jslib/common/src/models/request/keysRequest.ts -index 6ce11e8e..da4144e9 100644 ---- a/jslib/common/src/models/request/keysRequest.ts -+++ b/jslib/common/src/models/request/keysRequest.ts -@@ -1,9 +1,9 @@ - export class KeysRequest { -- publicKey: string; -- encryptedPrivateKey: string; -+ publicKey: string; -+ encryptedPrivateKey: string; - -- constructor(publicKey: string, encryptedPrivateKey: string) { -- this.publicKey = publicKey; -- this.encryptedPrivateKey = encryptedPrivateKey; -- } -+ constructor(publicKey: string, encryptedPrivateKey: string) { -+ this.publicKey = publicKey; -+ this.encryptedPrivateKey = encryptedPrivateKey; -+ } - } -diff --git a/jslib/common/src/models/request/kvpRequest.ts b/jslib/common/src/models/request/kvpRequest.ts -index 0611a4e0..ca37a85d 100644 ---- a/jslib/common/src/models/request/kvpRequest.ts -+++ b/jslib/common/src/models/request/kvpRequest.ts -@@ -1,9 +1,9 @@ - export class KvpRequest { -- key: TK; -- value: TV; -+ key: TK; -+ value: TV; - -- constructor(key: TK, value: TV) { -- this.key = key; -- this.value = value; -- } -+ constructor(key: TK, value: TV) { -+ this.key = key; -+ this.value = value; -+ } - } -diff --git a/jslib/common/src/models/request/organization/organizationSponsorshipCreateRequest.ts b/jslib/common/src/models/request/organization/organizationSponsorshipCreateRequest.ts -index 6dd8c267..7cc854b6 100644 ---- a/jslib/common/src/models/request/organization/organizationSponsorshipCreateRequest.ts -+++ b/jslib/common/src/models/request/organization/organizationSponsorshipCreateRequest.ts -@@ -1,4 +1,4 @@ --import { PlanSponsorshipType } from '../../../enums/planSponsorshipType'; -+import { PlanSponsorshipType } from "../../../enums/planSponsorshipType"; - - export class OrganizationSponsorshipCreateRequest { - sponsoredEmail: string; -diff --git a/jslib/common/src/models/request/organization/organizationSponsorshipRedeemRequest.ts b/jslib/common/src/models/request/organization/organizationSponsorshipRedeemRequest.ts -index 7f325ea1..4c73836e 100644 ---- a/jslib/common/src/models/request/organization/organizationSponsorshipRedeemRequest.ts -+++ b/jslib/common/src/models/request/organization/organizationSponsorshipRedeemRequest.ts -@@ -1,6 +1,6 @@ --import { PlanSponsorshipType } from '../../../enums/planSponsorshipType'; -+import { PlanSponsorshipType } from "../../../enums/planSponsorshipType"; - - export class OrganizationSponsorshipRedeemRequest { -- planSponsorshipType: PlanSponsorshipType; -- sponsoredOrganizationId: string; -+ planSponsorshipType: PlanSponsorshipType; -+ sponsoredOrganizationId: string; - } -diff --git a/jslib/common/src/models/request/organization/organizationSsoRequest.ts b/jslib/common/src/models/request/organization/organizationSsoRequest.ts -index 74afb318..8a2cbab9 100644 ---- a/jslib/common/src/models/request/organization/organizationSsoRequest.ts -+++ b/jslib/common/src/models/request/organization/organizationSsoRequest.ts -@@ -1,6 +1,6 @@ --import { SsoConfigApi } from '../../api/ssoConfigApi'; -+import { SsoConfigApi } from "../../api/ssoConfigApi"; - - export class OrganizationSsoRequest { -- enabled: boolean = false; -- data: SsoConfigApi; -+ enabled: boolean = false; -+ data: SsoConfigApi; - } -diff --git a/jslib/common/src/models/request/organizationCreateRequest.ts b/jslib/common/src/models/request/organizationCreateRequest.ts -index 7be02660..db713d90 100644 ---- a/jslib/common/src/models/request/organizationCreateRequest.ts -+++ b/jslib/common/src/models/request/organizationCreateRequest.ts -@@ -1,27 +1,27 @@ --import { PaymentMethodType } from '../../enums/paymentMethodType'; --import { PlanType } from '../../enums/planType'; -+import { PaymentMethodType } from "../../enums/paymentMethodType"; -+import { PlanType } from "../../enums/planType"; - --import { OrganizationKeysRequest } from './organizationKeysRequest'; -+import { OrganizationKeysRequest } from "./organizationKeysRequest"; - - export class OrganizationCreateRequest { -- name: string; -- businessName: string; -- billingEmail: string; -- planType: PlanType; -- key: string; -- keys: OrganizationKeysRequest; -- paymentMethodType: PaymentMethodType; -- paymentToken: string; -- additionalSeats: number; -- maxAutoscaleSeats: number; -- additionalStorageGb: number; -- premiumAccessAddon: boolean; -- collectionName: string; -- taxIdNumber: string; -- billingAddressLine1: string; -- billingAddressLine2: string; -- billingAddressCity: string; -- billingAddressState: string; -- billingAddressPostalCode: string; -- billingAddressCountry: string; -+ name: string; -+ businessName: string; -+ billingEmail: string; -+ planType: PlanType; -+ key: string; -+ keys: OrganizationKeysRequest; -+ paymentMethodType: PaymentMethodType; -+ paymentToken: string; -+ additionalSeats: number; -+ maxAutoscaleSeats: number; -+ additionalStorageGb: number; -+ premiumAccessAddon: boolean; -+ collectionName: string; -+ taxIdNumber: string; -+ billingAddressLine1: string; -+ billingAddressLine2: string; -+ billingAddressCity: string; -+ billingAddressState: string; -+ billingAddressPostalCode: string; -+ billingAddressCountry: string; - } -diff --git a/jslib/common/src/models/request/organizationImportGroupRequest.ts b/jslib/common/src/models/request/organizationImportGroupRequest.ts -index 0cbb0faf..086ce653 100644 ---- a/jslib/common/src/models/request/organizationImportGroupRequest.ts -+++ b/jslib/common/src/models/request/organizationImportGroupRequest.ts -@@ -1,19 +1,18 @@ --import { ImportDirectoryRequestGroup } from './importDirectoryRequestGroup'; -+import { ImportDirectoryRequestGroup } from "./importDirectoryRequestGroup"; - - export class OrganizationImportGroupRequest { -- name: string; -- externalId: string; -- memberExternalIds: string[]; -+ name: string; -+ externalId: string; -+ memberExternalIds: string[]; - -- constructor(model: Required | ImportDirectoryRequestGroup) { -- this.name = model.name; -- this.externalId = model.externalId; -+ constructor(model: Required | ImportDirectoryRequestGroup) { -+ this.name = model.name; -+ this.externalId = model.externalId; - -- if (model instanceof ImportDirectoryRequestGroup) { -- this.memberExternalIds = model.users; -- } -- else { -- this.memberExternalIds = model.memberExternalIds; -- } -+ if (model instanceof ImportDirectoryRequestGroup) { -+ this.memberExternalIds = model.users; -+ } else { -+ this.memberExternalIds = model.memberExternalIds; - } -+ } - } -diff --git a/jslib/common/src/models/request/organizationImportMemberRequest.ts b/jslib/common/src/models/request/organizationImportMemberRequest.ts -index 78b09498..8161365c 100644 ---- a/jslib/common/src/models/request/organizationImportMemberRequest.ts -+++ b/jslib/common/src/models/request/organizationImportMemberRequest.ts -@@ -1,13 +1,13 @@ --import { ImportDirectoryRequestUser } from './importDirectoryRequestUser'; -+import { ImportDirectoryRequestUser } from "./importDirectoryRequestUser"; - - export class OrganizationImportMemberRequest { -- email: string; -- externalId: string; -- deleted: boolean; -+ email: string; -+ externalId: string; -+ deleted: boolean; - -- constructor(model: Required | ImportDirectoryRequestUser) { -- this.email = model.email; -- this.externalId = model.externalId; -- this.deleted = model.deleted; -- } -+ constructor(model: Required | ImportDirectoryRequestUser) { -+ this.email = model.email; -+ this.externalId = model.externalId; -+ this.deleted = model.deleted; -+ } - } -diff --git a/jslib/common/src/models/request/organizationImportRequest.ts b/jslib/common/src/models/request/organizationImportRequest.ts -index 6b1bc304..4ff23df7 100644 ---- a/jslib/common/src/models/request/organizationImportRequest.ts -+++ b/jslib/common/src/models/request/organizationImportRequest.ts -@@ -1,25 +1,31 @@ --import { ImportDirectoryRequest } from './importDirectoryRequest'; --import { OrganizationImportGroupRequest } from './organizationImportGroupRequest'; --import { OrganizationImportMemberRequest } from './organizationImportMemberRequest'; -+import { ImportDirectoryRequest } from "./importDirectoryRequest"; -+import { OrganizationImportGroupRequest } from "./organizationImportGroupRequest"; -+import { OrganizationImportMemberRequest } from "./organizationImportMemberRequest"; - - export class OrganizationImportRequest { -- groups: OrganizationImportGroupRequest[] = []; -- members: OrganizationImportMemberRequest[] = []; -- overwriteExisting: boolean = false; -- largeImport: boolean = false; -+ groups: OrganizationImportGroupRequest[] = []; -+ members: OrganizationImportMemberRequest[] = []; -+ overwriteExisting: boolean = false; -+ largeImport: boolean = false; - -- constructor(model: { -- groups: Required[], -- users: Required[], overwriteExisting: boolean, largeImport: boolean; -- } | ImportDirectoryRequest) { -- if (model instanceof ImportDirectoryRequest) { -- this.groups = model.groups.map(g => new OrganizationImportGroupRequest(g)); -- this.members = model.users.map(u => new OrganizationImportMemberRequest(u)); -- } else { -- this.groups = model.groups.map(g => new OrganizationImportGroupRequest(g)); -- this.members = model.users.map(u => new OrganizationImportMemberRequest(u)); -+ constructor( -+ model: -+ | { -+ groups: Required[]; -+ users: Required[]; -+ overwriteExisting: boolean; -+ largeImport: boolean; - } -- this.overwriteExisting = model.overwriteExisting; -- this.largeImport = model.largeImport; -+ | ImportDirectoryRequest -+ ) { -+ if (model instanceof ImportDirectoryRequest) { -+ this.groups = model.groups.map((g) => new OrganizationImportGroupRequest(g)); -+ this.members = model.users.map((u) => new OrganizationImportMemberRequest(u)); -+ } else { -+ this.groups = model.groups.map((g) => new OrganizationImportGroupRequest(g)); -+ this.members = model.users.map((u) => new OrganizationImportMemberRequest(u)); - } -+ this.overwriteExisting = model.overwriteExisting; -+ this.largeImport = model.largeImport; -+ } - } -diff --git a/jslib/common/src/models/request/organizationKeysRequest.ts b/jslib/common/src/models/request/organizationKeysRequest.ts -index 932f28ce..c63e05ab 100644 ---- a/jslib/common/src/models/request/organizationKeysRequest.ts -+++ b/jslib/common/src/models/request/organizationKeysRequest.ts -@@ -1,7 +1,7 @@ --import { KeysRequest } from './keysRequest'; -+import { KeysRequest } from "./keysRequest"; - - export class OrganizationKeysRequest extends KeysRequest { -- constructor(publicKey: string, encryptedPrivateKey: string) { -- super(publicKey, encryptedPrivateKey); -- } -+ constructor(publicKey: string, encryptedPrivateKey: string) { -+ super(publicKey, encryptedPrivateKey); -+ } - } -diff --git a/jslib/common/src/models/request/organizationSubscriptionUpdateRequest.ts b/jslib/common/src/models/request/organizationSubscriptionUpdateRequest.ts -index 85ea6f4f..9db3d4be 100644 ---- a/jslib/common/src/models/request/organizationSubscriptionUpdateRequest.ts -+++ b/jslib/common/src/models/request/organizationSubscriptionUpdateRequest.ts -@@ -1,3 +1,3 @@ - export class OrganizationSubscriptionUpdateRequest { -- constructor(public seatAdjustment: number, public maxAutoscaleSeats?: number) { } -+ constructor(public seatAdjustment: number, public maxAutoscaleSeats?: number) {} - } -diff --git a/jslib/common/src/models/request/organizationTaxInfoUpdateRequest.ts b/jslib/common/src/models/request/organizationTaxInfoUpdateRequest.ts -index 73df4ad5..283151f4 100644 ---- a/jslib/common/src/models/request/organizationTaxInfoUpdateRequest.ts -+++ b/jslib/common/src/models/request/organizationTaxInfoUpdateRequest.ts -@@ -1,9 +1,9 @@ --import { TaxInfoUpdateRequest } from './taxInfoUpdateRequest'; -+import { TaxInfoUpdateRequest } from "./taxInfoUpdateRequest"; - - export class OrganizationTaxInfoUpdateRequest extends TaxInfoUpdateRequest { -- taxId: string; -- line1: string; -- line2: string; -- city: string; -- state: string; -+ taxId: string; -+ line1: string; -+ line2: string; -+ city: string; -+ state: string; - } -diff --git a/jslib/common/src/models/request/organizationUpdateRequest.ts b/jslib/common/src/models/request/organizationUpdateRequest.ts -index 6e20b671..faddf641 100644 ---- a/jslib/common/src/models/request/organizationUpdateRequest.ts -+++ b/jslib/common/src/models/request/organizationUpdateRequest.ts -@@ -1,9 +1,9 @@ --import { OrganizationKeysRequest } from './organizationKeysRequest'; -+import { OrganizationKeysRequest } from "./organizationKeysRequest"; - - export class OrganizationUpdateRequest { -- name: string; -- identifier: string; -- businessName: string; -- billingEmail: string; -- keys: OrganizationKeysRequest; -+ name: string; -+ identifier: string; -+ businessName: string; -+ billingEmail: string; -+ keys: OrganizationKeysRequest; - } -diff --git a/jslib/common/src/models/request/organizationUpgradeRequest.ts b/jslib/common/src/models/request/organizationUpgradeRequest.ts -index 1000e19e..b62976fa 100644 ---- a/jslib/common/src/models/request/organizationUpgradeRequest.ts -+++ b/jslib/common/src/models/request/organizationUpgradeRequest.ts -@@ -1,14 +1,14 @@ --import { PlanType } from '../../enums/planType'; -+import { PlanType } from "../../enums/planType"; - --import { OrganizationKeysRequest } from './organizationKeysRequest'; -+import { OrganizationKeysRequest } from "./organizationKeysRequest"; - - export class OrganizationUpgradeRequest { -- businessName: string; -- planType: PlanType; -- additionalSeats: number; -- additionalStorageGb: number; -- premiumAccessAddon: boolean; -- billingAddressCountry: string; -- billingAddressPostalCode: string; -- keys: OrganizationKeysRequest; -+ businessName: string; -+ planType: PlanType; -+ additionalSeats: number; -+ additionalStorageGb: number; -+ premiumAccessAddon: boolean; -+ billingAddressCountry: string; -+ billingAddressPostalCode: string; -+ keys: OrganizationKeysRequest; - } -diff --git a/jslib/common/src/models/request/organizationUserAcceptRequest.ts b/jslib/common/src/models/request/organizationUserAcceptRequest.ts -index acd36a67..c4b2a4d3 100644 ---- a/jslib/common/src/models/request/organizationUserAcceptRequest.ts -+++ b/jslib/common/src/models/request/organizationUserAcceptRequest.ts -@@ -1,3 +1,3 @@ - export class OrganizationUserAcceptRequest { -- token: string; -+ token: string; - } -diff --git a/jslib/common/src/models/request/organizationUserBulkConfirmRequest.ts b/jslib/common/src/models/request/organizationUserBulkConfirmRequest.ts -index 8412baed..35e05602 100644 ---- a/jslib/common/src/models/request/organizationUserBulkConfirmRequest.ts -+++ b/jslib/common/src/models/request/organizationUserBulkConfirmRequest.ts -@@ -1,12 +1,12 @@ - type OrganizationUserBulkRequestEntry = { -- id: string; -- key: string; -+ id: string; -+ key: string; - }; - - export class OrganizationUserBulkConfirmRequest { -- keys: OrganizationUserBulkRequestEntry[]; -+ keys: OrganizationUserBulkRequestEntry[]; - -- constructor(keys: OrganizationUserBulkRequestEntry[]) { -- this.keys = keys; -- } -+ constructor(keys: OrganizationUserBulkRequestEntry[]) { -+ this.keys = keys; -+ } - } -diff --git a/jslib/common/src/models/request/organizationUserBulkRequest.ts b/jslib/common/src/models/request/organizationUserBulkRequest.ts -index 4b92620c..c73800eb 100644 ---- a/jslib/common/src/models/request/organizationUserBulkRequest.ts -+++ b/jslib/common/src/models/request/organizationUserBulkRequest.ts -@@ -1,7 +1,7 @@ - export class OrganizationUserBulkRequest { -- ids: string[]; -+ ids: string[]; - -- constructor(ids: string[]) { -- this.ids = ids == null ? [] : ids; -- } -+ constructor(ids: string[]) { -+ this.ids = ids == null ? [] : ids; -+ } - } -diff --git a/jslib/common/src/models/request/organizationUserConfirmRequest.ts b/jslib/common/src/models/request/organizationUserConfirmRequest.ts -index c4d23305..abd48749 100644 ---- a/jslib/common/src/models/request/organizationUserConfirmRequest.ts -+++ b/jslib/common/src/models/request/organizationUserConfirmRequest.ts -@@ -1,3 +1,3 @@ - export class OrganizationUserConfirmRequest { -- key: string; -+ key: string; - } -diff --git a/jslib/common/src/models/request/organizationUserInviteRequest.ts b/jslib/common/src/models/request/organizationUserInviteRequest.ts -index 4195eec8..5c4046af 100644 ---- a/jslib/common/src/models/request/organizationUserInviteRequest.ts -+++ b/jslib/common/src/models/request/organizationUserInviteRequest.ts -@@ -1,12 +1,12 @@ --import { SelectionReadOnlyRequest } from './selectionReadOnlyRequest'; -+import { SelectionReadOnlyRequest } from "./selectionReadOnlyRequest"; - --import { OrganizationUserType } from '../../enums/organizationUserType'; --import { PermissionsApi } from '../api/permissionsApi'; -+import { OrganizationUserType } from "../../enums/organizationUserType"; -+import { PermissionsApi } from "../api/permissionsApi"; - - export class OrganizationUserInviteRequest { -- emails: string[] = []; -- type: OrganizationUserType; -- accessAll: boolean; -- collections: SelectionReadOnlyRequest[] = []; -- permissions: PermissionsApi; -+ emails: string[] = []; -+ type: OrganizationUserType; -+ accessAll: boolean; -+ collections: SelectionReadOnlyRequest[] = []; -+ permissions: PermissionsApi; - } -diff --git a/jslib/common/src/models/request/organizationUserResetPasswordEnrollmentRequest.ts b/jslib/common/src/models/request/organizationUserResetPasswordEnrollmentRequest.ts -index d017c4a6..8d88164d 100644 ---- a/jslib/common/src/models/request/organizationUserResetPasswordEnrollmentRequest.ts -+++ b/jslib/common/src/models/request/organizationUserResetPasswordEnrollmentRequest.ts -@@ -1,3 +1,3 @@ - export class OrganizationUserResetPasswordEnrollmentRequest { -- resetPasswordKey: string; -+ resetPasswordKey: string; - } -diff --git a/jslib/common/src/models/request/organizationUserResetPasswordRequest.ts b/jslib/common/src/models/request/organizationUserResetPasswordRequest.ts -index 7e0b1bdb..b0c4e483 100644 ---- a/jslib/common/src/models/request/organizationUserResetPasswordRequest.ts -+++ b/jslib/common/src/models/request/organizationUserResetPasswordRequest.ts -@@ -1,4 +1,4 @@ - export class OrganizationUserResetPasswordRequest { -- newMasterPasswordHash: string; -- key: string; -+ newMasterPasswordHash: string; -+ key: string; - } -diff --git a/jslib/common/src/models/request/organizationUserUpdateGroupsRequest.ts b/jslib/common/src/models/request/organizationUserUpdateGroupsRequest.ts -index 0d7805cc..cd30d940 100644 ---- a/jslib/common/src/models/request/organizationUserUpdateGroupsRequest.ts -+++ b/jslib/common/src/models/request/organizationUserUpdateGroupsRequest.ts -@@ -1,3 +1,3 @@ - export class OrganizationUserUpdateGroupsRequest { -- groupIds: string[] = []; -+ groupIds: string[] = []; - } -diff --git a/jslib/common/src/models/request/organizationUserUpdateRequest.ts b/jslib/common/src/models/request/organizationUserUpdateRequest.ts -index 80803cd6..2bd9dd62 100644 ---- a/jslib/common/src/models/request/organizationUserUpdateRequest.ts -+++ b/jslib/common/src/models/request/organizationUserUpdateRequest.ts -@@ -1,11 +1,11 @@ --import { SelectionReadOnlyRequest } from './selectionReadOnlyRequest'; -+import { SelectionReadOnlyRequest } from "./selectionReadOnlyRequest"; - --import { OrganizationUserType } from '../../enums/organizationUserType'; --import { PermissionsApi } from '../api/permissionsApi'; -+import { OrganizationUserType } from "../../enums/organizationUserType"; -+import { PermissionsApi } from "../api/permissionsApi"; - - export class OrganizationUserUpdateRequest { -- type: OrganizationUserType; -- accessAll: boolean; -- collections: SelectionReadOnlyRequest[] = []; -- permissions: PermissionsApi; -+ type: OrganizationUserType; -+ accessAll: boolean; -+ collections: SelectionReadOnlyRequest[] = []; -+ permissions: PermissionsApi; - } -diff --git a/jslib/common/src/models/request/passwordHintRequest.ts b/jslib/common/src/models/request/passwordHintRequest.ts -index 35b37f4f..7182e05e 100644 ---- a/jslib/common/src/models/request/passwordHintRequest.ts -+++ b/jslib/common/src/models/request/passwordHintRequest.ts -@@ -1,7 +1,7 @@ - export class PasswordHintRequest { -- email: string; -+ email: string; - -- constructor(email: string) { -- this.email = email; -- } -+ constructor(email: string) { -+ this.email = email; -+ } - } -diff --git a/jslib/common/src/models/request/passwordHistoryRequest.ts b/jslib/common/src/models/request/passwordHistoryRequest.ts -index 7b74cb12..6cca2b86 100644 ---- a/jslib/common/src/models/request/passwordHistoryRequest.ts -+++ b/jslib/common/src/models/request/passwordHistoryRequest.ts -@@ -1,4 +1,4 @@ - export class PasswordHistoryRequest { -- password: string; -- lastUsedDate: Date; -+ password: string; -+ lastUsedDate: Date; - } -diff --git a/jslib/common/src/models/request/passwordRequest.ts b/jslib/common/src/models/request/passwordRequest.ts -index 0d639a09..9f7df7df 100644 ---- a/jslib/common/src/models/request/passwordRequest.ts -+++ b/jslib/common/src/models/request/passwordRequest.ts -@@ -1,6 +1,6 @@ --import { SecretVerificationRequest } from './secretVerificationRequest'; -+import { SecretVerificationRequest } from "./secretVerificationRequest"; - - export class PasswordRequest extends SecretVerificationRequest { -- newMasterPasswordHash: string; -- key: string; -+ newMasterPasswordHash: string; -+ key: string; - } -diff --git a/jslib/common/src/models/request/paymentRequest.ts b/jslib/common/src/models/request/paymentRequest.ts -index e3d420ea..8e7f70a1 100644 ---- a/jslib/common/src/models/request/paymentRequest.ts -+++ b/jslib/common/src/models/request/paymentRequest.ts -@@ -1,7 +1,7 @@ --import { PaymentMethodType } from '../../enums/paymentMethodType'; --import { OrganizationTaxInfoUpdateRequest } from '../request/organizationTaxInfoUpdateRequest'; -+import { PaymentMethodType } from "../../enums/paymentMethodType"; -+import { OrganizationTaxInfoUpdateRequest } from "../request/organizationTaxInfoUpdateRequest"; - - export class PaymentRequest extends OrganizationTaxInfoUpdateRequest { -- paymentMethodType: PaymentMethodType; -- paymentToken: string; -+ paymentMethodType: PaymentMethodType; -+ paymentToken: string; - } -diff --git a/jslib/common/src/models/request/policyRequest.ts b/jslib/common/src/models/request/policyRequest.ts -index a1314d08..98b05912 100644 ---- a/jslib/common/src/models/request/policyRequest.ts -+++ b/jslib/common/src/models/request/policyRequest.ts -@@ -1,7 +1,7 @@ --import { PolicyType } from '../../enums/policyType'; -+import { PolicyType } from "../../enums/policyType"; - - export class PolicyRequest { -- type: PolicyType; -- enabled: boolean; -- data: any; -+ type: PolicyType; -+ enabled: boolean; -+ data: any; - } -diff --git a/jslib/common/src/models/request/preloginRequest.ts b/jslib/common/src/models/request/preloginRequest.ts -index bee6058c..689204b7 100644 ---- a/jslib/common/src/models/request/preloginRequest.ts -+++ b/jslib/common/src/models/request/preloginRequest.ts -@@ -1,7 +1,7 @@ - export class PreloginRequest { -- email: string; -+ email: string; - -- constructor(email: string) { -- this.email = email; -- } -+ constructor(email: string) { -+ this.email = email; -+ } - } -diff --git a/jslib/common/src/models/request/provider/providerAddOrganizationRequest.ts b/jslib/common/src/models/request/provider/providerAddOrganizationRequest.ts -index ebdd1bc6..380eea1d 100644 ---- a/jslib/common/src/models/request/provider/providerAddOrganizationRequest.ts -+++ b/jslib/common/src/models/request/provider/providerAddOrganizationRequest.ts -@@ -1,4 +1,4 @@ - export class ProviderAddOrganizationRequest { -- organizationId: string; -- key: string; -+ organizationId: string; -+ key: string; - } -diff --git a/jslib/common/src/models/request/provider/providerOrganizationCreateRequest.ts b/jslib/common/src/models/request/provider/providerOrganizationCreateRequest.ts -index e116de6a..8d02f530 100644 ---- a/jslib/common/src/models/request/provider/providerOrganizationCreateRequest.ts -+++ b/jslib/common/src/models/request/provider/providerOrganizationCreateRequest.ts -@@ -1,5 +1,8 @@ --import { OrganizationCreateRequest } from '../organizationCreateRequest'; -+import { OrganizationCreateRequest } from "../organizationCreateRequest"; - - export class ProviderOrganizationCreateRequest { -- constructor(public clientOwnerEmail: string, public organizationCreateRequest: OrganizationCreateRequest) { } -+ constructor( -+ public clientOwnerEmail: string, -+ public organizationCreateRequest: OrganizationCreateRequest -+ ) {} - } -diff --git a/jslib/common/src/models/request/provider/providerSetupRequest.ts b/jslib/common/src/models/request/provider/providerSetupRequest.ts -index 5063fec0..61eb943f 100644 ---- a/jslib/common/src/models/request/provider/providerSetupRequest.ts -+++ b/jslib/common/src/models/request/provider/providerSetupRequest.ts -@@ -1,7 +1,7 @@ - export class ProviderSetupRequest { -- name: string; -- businessName: string; -- billingEmail: string; -- token: string; -- key: string; -+ name: string; -+ businessName: string; -+ billingEmail: string; -+ token: string; -+ key: string; - } -diff --git a/jslib/common/src/models/request/provider/providerUpdateRequest.ts b/jslib/common/src/models/request/provider/providerUpdateRequest.ts -index d30e6346..dafa7418 100644 ---- a/jslib/common/src/models/request/provider/providerUpdateRequest.ts -+++ b/jslib/common/src/models/request/provider/providerUpdateRequest.ts -@@ -1,5 +1,5 @@ - export class ProviderUpdateRequest { -- name: string; -- businessName: string; -- billingEmail: string; -+ name: string; -+ businessName: string; -+ billingEmail: string; - } -diff --git a/jslib/common/src/models/request/provider/providerUserAcceptRequest.ts b/jslib/common/src/models/request/provider/providerUserAcceptRequest.ts -index 15e0370b..0435e1df 100644 ---- a/jslib/common/src/models/request/provider/providerUserAcceptRequest.ts -+++ b/jslib/common/src/models/request/provider/providerUserAcceptRequest.ts -@@ -1,3 +1,3 @@ - export class ProviderUserAcceptRequest { -- token: string; -+ token: string; - } -diff --git a/jslib/common/src/models/request/provider/providerUserBulkConfirmRequest.ts b/jslib/common/src/models/request/provider/providerUserBulkConfirmRequest.ts -index cb5a252c..76628b09 100644 ---- a/jslib/common/src/models/request/provider/providerUserBulkConfirmRequest.ts -+++ b/jslib/common/src/models/request/provider/providerUserBulkConfirmRequest.ts -@@ -1,12 +1,12 @@ - type ProviderUserBulkRequestEntry = { -- id: string; -- key: string; -+ id: string; -+ key: string; - }; - - export class ProviderUserBulkConfirmRequest { -- keys: ProviderUserBulkRequestEntry[]; -+ keys: ProviderUserBulkRequestEntry[]; - -- constructor(keys: ProviderUserBulkRequestEntry[]) { -- this.keys = keys; -- } -+ constructor(keys: ProviderUserBulkRequestEntry[]) { -+ this.keys = keys; -+ } - } -diff --git a/jslib/common/src/models/request/provider/providerUserBulkRequest.ts b/jslib/common/src/models/request/provider/providerUserBulkRequest.ts -index e676b1f8..f45ed1bb 100644 ---- a/jslib/common/src/models/request/provider/providerUserBulkRequest.ts -+++ b/jslib/common/src/models/request/provider/providerUserBulkRequest.ts -@@ -1,7 +1,7 @@ - export class ProviderUserBulkRequest { -- ids: string[]; -+ ids: string[]; - -- constructor(ids: string[]) { -- this.ids = ids == null ? [] : ids; -- } -+ constructor(ids: string[]) { -+ this.ids = ids == null ? [] : ids; -+ } - } -diff --git a/jslib/common/src/models/request/provider/providerUserConfirmRequest.ts b/jslib/common/src/models/request/provider/providerUserConfirmRequest.ts -index 8d7203d6..1b7d4a06 100644 ---- a/jslib/common/src/models/request/provider/providerUserConfirmRequest.ts -+++ b/jslib/common/src/models/request/provider/providerUserConfirmRequest.ts -@@ -1,3 +1,3 @@ - export class ProviderUserConfirmRequest { -- key: string; -+ key: string; - } -diff --git a/jslib/common/src/models/request/provider/providerUserInviteRequest.ts b/jslib/common/src/models/request/provider/providerUserInviteRequest.ts -index d8daedd2..65d8ba67 100644 ---- a/jslib/common/src/models/request/provider/providerUserInviteRequest.ts -+++ b/jslib/common/src/models/request/provider/providerUserInviteRequest.ts -@@ -1,6 +1,6 @@ --import { ProviderUserType } from '../../../enums/providerUserType'; -+import { ProviderUserType } from "../../../enums/providerUserType"; - - export class ProviderUserInviteRequest { -- emails: string[] = []; -- type: ProviderUserType; -+ emails: string[] = []; -+ type: ProviderUserType; - } -diff --git a/jslib/common/src/models/request/provider/providerUserUpdateRequest.ts b/jslib/common/src/models/request/provider/providerUserUpdateRequest.ts -index 77519e24..25efdd89 100644 ---- a/jslib/common/src/models/request/provider/providerUserUpdateRequest.ts -+++ b/jslib/common/src/models/request/provider/providerUserUpdateRequest.ts -@@ -1,5 +1,5 @@ --import { ProviderUserType } from '../../../enums/providerUserType'; -+import { ProviderUserType } from "../../../enums/providerUserType"; - - export class ProviderUserUpdateRequest { -- type: ProviderUserType; -+ type: ProviderUserType; - } -diff --git a/jslib/common/src/models/request/referenceEventRequest.ts b/jslib/common/src/models/request/referenceEventRequest.ts -index 4cd8c507..7a0b535a 100644 ---- a/jslib/common/src/models/request/referenceEventRequest.ts -+++ b/jslib/common/src/models/request/referenceEventRequest.ts -@@ -1,5 +1,5 @@ - export class ReferenceEventRequest { -- id: string; -- layout: string; -- flow: string; -+ id: string; -+ layout: string; -+ flow: string; - } -diff --git a/jslib/common/src/models/request/registerRequest.ts b/jslib/common/src/models/request/registerRequest.ts -index 04f06b49..5c53ce59 100644 ---- a/jslib/common/src/models/request/registerRequest.ts -+++ b/jslib/common/src/models/request/registerRequest.ts -@@ -1,20 +1,27 @@ --import { KeysRequest } from './keysRequest'; --import { ReferenceEventRequest } from './referenceEventRequest'; -+import { KeysRequest } from "./keysRequest"; -+import { ReferenceEventRequest } from "./referenceEventRequest"; - --import { KdfType } from '../../enums/kdfType'; -+import { KdfType } from "../../enums/kdfType"; - --import { CaptchaProtectedRequest } from './captchaProtectedRequest'; -+import { CaptchaProtectedRequest } from "./captchaProtectedRequest"; - - export class RegisterRequest implements CaptchaProtectedRequest { -- masterPasswordHint: string; -- keys: KeysRequest; -- token: string; -- organizationUserId: string; -+ masterPasswordHint: string; -+ keys: KeysRequest; -+ token: string; -+ organizationUserId: string; - -- -- constructor(public email: string, public name: string, public masterPasswordHash: string, -- masterPasswordHint: string, public key: string, public kdf: KdfType, public kdfIterations: number, -- public referenceData: ReferenceEventRequest, public captchaResponse: string) { -- this.masterPasswordHint = masterPasswordHint ? masterPasswordHint : null; -- } -+ constructor( -+ public email: string, -+ public name: string, -+ public masterPasswordHash: string, -+ masterPasswordHint: string, -+ public key: string, -+ public kdf: KdfType, -+ public kdfIterations: number, -+ public referenceData: ReferenceEventRequest, -+ public captchaResponse: string -+ ) { -+ this.masterPasswordHint = masterPasswordHint ? masterPasswordHint : null; -+ } - } -diff --git a/jslib/common/src/models/request/seatRequest.ts b/jslib/common/src/models/request/seatRequest.ts -index 92d47669..d60e41fa 100644 ---- a/jslib/common/src/models/request/seatRequest.ts -+++ b/jslib/common/src/models/request/seatRequest.ts -@@ -1,3 +1,3 @@ - export class SeatRequest { -- seatAdjustment: number; -+ seatAdjustment: number; - } -diff --git a/jslib/common/src/models/request/secretVerificationRequest.ts b/jslib/common/src/models/request/secretVerificationRequest.ts -index 554aa1c6..c0170a3e 100644 ---- a/jslib/common/src/models/request/secretVerificationRequest.ts -+++ b/jslib/common/src/models/request/secretVerificationRequest.ts -@@ -1,4 +1,4 @@ - export class SecretVerificationRequest { -- masterPasswordHash: string; -- otp: string; -+ masterPasswordHash: string; -+ otp: string; - } -diff --git a/jslib/common/src/models/request/selectionReadOnlyRequest.ts b/jslib/common/src/models/request/selectionReadOnlyRequest.ts -index d001edb2..7b007324 100644 ---- a/jslib/common/src/models/request/selectionReadOnlyRequest.ts -+++ b/jslib/common/src/models/request/selectionReadOnlyRequest.ts -@@ -1,11 +1,11 @@ - export class SelectionReadOnlyRequest { -- id: string; -- readOnly: boolean; -- hidePasswords: boolean; -+ id: string; -+ readOnly: boolean; -+ hidePasswords: boolean; - -- constructor(id: string, readOnly: boolean, hidePasswords: boolean) { -- this.id = id; -- this.readOnly = readOnly; -- this.hidePasswords = hidePasswords; -- } -+ constructor(id: string, readOnly: boolean, hidePasswords: boolean) { -+ this.id = id; -+ this.readOnly = readOnly; -+ this.hidePasswords = hidePasswords; -+ } - } -diff --git a/jslib/common/src/models/request/sendAccessRequest.ts b/jslib/common/src/models/request/sendAccessRequest.ts -index 3d49c0c2..7607b03c 100644 ---- a/jslib/common/src/models/request/sendAccessRequest.ts -+++ b/jslib/common/src/models/request/sendAccessRequest.ts -@@ -1,3 +1,3 @@ - export class SendAccessRequest { -- password: string; -+ password: string; - } -diff --git a/jslib/common/src/models/request/sendRequest.ts b/jslib/common/src/models/request/sendRequest.ts -index 5e5861f0..6fa481ee 100644 ---- a/jslib/common/src/models/request/sendRequest.ts -+++ b/jslib/common/src/models/request/sendRequest.ts -@@ -1,50 +1,50 @@ --import { SendType } from '../../enums/sendType'; -+import { SendType } from "../../enums/sendType"; - --import { SendFileApi } from '../api/sendFileApi'; --import { SendTextApi } from '../api/sendTextApi'; -+import { SendFileApi } from "../api/sendFileApi"; -+import { SendTextApi } from "../api/sendTextApi"; - --import { Send } from '../domain/send'; -+import { Send } from "../domain/send"; - - export class SendRequest { -- type: SendType; -- fileLength?: number; -- name: string; -- notes: string; -- key: string; -- maxAccessCount?: number; -- expirationDate: string; -- deletionDate: string; -- text: SendTextApi; -- file: SendFileApi; -- password: string; -- disabled: boolean; -- hideEmail: boolean; -+ type: SendType; -+ fileLength?: number; -+ name: string; -+ notes: string; -+ key: string; -+ maxAccessCount?: number; -+ expirationDate: string; -+ deletionDate: string; -+ text: SendTextApi; -+ file: SendFileApi; -+ password: string; -+ disabled: boolean; -+ hideEmail: boolean; - -- constructor(send: Send, fileLength?: number) { -- this.type = send.type; -- this.fileLength = fileLength; -- this.name = send.name ? send.name.encryptedString : null; -- this.notes = send.notes ? send.notes.encryptedString : null; -- this.maxAccessCount = send.maxAccessCount; -- this.expirationDate = send.expirationDate != null ? send.expirationDate.toISOString() : null; -- this.deletionDate = send.deletionDate != null ? send.deletionDate.toISOString() : null; -- this.key = send.key != null ? send.key.encryptedString : null; -- this.password = send.password; -- this.disabled = send.disabled; -- this.hideEmail = send.hideEmail; -+ constructor(send: Send, fileLength?: number) { -+ this.type = send.type; -+ this.fileLength = fileLength; -+ this.name = send.name ? send.name.encryptedString : null; -+ this.notes = send.notes ? send.notes.encryptedString : null; -+ this.maxAccessCount = send.maxAccessCount; -+ this.expirationDate = send.expirationDate != null ? send.expirationDate.toISOString() : null; -+ this.deletionDate = send.deletionDate != null ? send.deletionDate.toISOString() : null; -+ this.key = send.key != null ? send.key.encryptedString : null; -+ this.password = send.password; -+ this.disabled = send.disabled; -+ this.hideEmail = send.hideEmail; - -- switch (this.type) { -- case SendType.Text: -- this.text = new SendTextApi(); -- this.text.text = send.text.text != null ? send.text.text.encryptedString : null; -- this.text.hidden = send.text.hidden; -- break; -- case SendType.File: -- this.file = new SendFileApi(); -- this.file.fileName = send.file.fileName != null ? send.file.fileName.encryptedString : null; -- break; -- default: -- break; -- } -+ switch (this.type) { -+ case SendType.Text: -+ this.text = new SendTextApi(); -+ this.text.text = send.text.text != null ? send.text.text.encryptedString : null; -+ this.text.hidden = send.text.hidden; -+ break; -+ case SendType.File: -+ this.file = new SendFileApi(); -+ this.file.fileName = send.file.fileName != null ? send.file.fileName.encryptedString : null; -+ break; -+ default: -+ break; - } -+ } - } -diff --git a/jslib/common/src/models/request/sendWithIdRequest.ts b/jslib/common/src/models/request/sendWithIdRequest.ts -index 277cfa48..903151e3 100644 ---- a/jslib/common/src/models/request/sendWithIdRequest.ts -+++ b/jslib/common/src/models/request/sendWithIdRequest.ts -@@ -1,12 +1,12 @@ --import { SendRequest } from './sendRequest'; -+import { SendRequest } from "./sendRequest"; - --import { Send } from '../domain/send'; -+import { Send } from "../domain/send"; - - export class SendWithIdRequest extends SendRequest { -- id: string; -+ id: string; - -- constructor(send: Send) { -- super(send); -- this.id = send.id; -- } -+ constructor(send: Send) { -+ super(send); -+ this.id = send.id; -+ } - } -diff --git a/jslib/common/src/models/request/setPasswordRequest.ts b/jslib/common/src/models/request/setPasswordRequest.ts -index 70c77e62..de3b8489 100644 ---- a/jslib/common/src/models/request/setPasswordRequest.ts -+++ b/jslib/common/src/models/request/setPasswordRequest.ts -@@ -1,24 +1,31 @@ --import { KeysRequest } from './keysRequest'; -+import { KeysRequest } from "./keysRequest"; - --import { KdfType } from '../../enums/kdfType'; -+import { KdfType } from "../../enums/kdfType"; - - export class SetPasswordRequest { -- masterPasswordHash: string; -- key: string; -- masterPasswordHint: string; -- keys: KeysRequest; -- kdf: KdfType; -- kdfIterations: number; -- orgIdentifier: string; -+ masterPasswordHash: string; -+ key: string; -+ masterPasswordHint: string; -+ keys: KeysRequest; -+ kdf: KdfType; -+ kdfIterations: number; -+ orgIdentifier: string; - -- constructor(masterPasswordHash: string, key: string, masterPasswordHint: string, kdf: KdfType, -- kdfIterations: number, orgIdentifier: string, keys: KeysRequest) { -- this.masterPasswordHash = masterPasswordHash; -- this.key = key; -- this.masterPasswordHint = masterPasswordHint; -- this.kdf = kdf; -- this.kdfIterations = kdfIterations; -- this.orgIdentifier = orgIdentifier; -- this.keys = keys; -- } -+ constructor( -+ masterPasswordHash: string, -+ key: string, -+ masterPasswordHint: string, -+ kdf: KdfType, -+ kdfIterations: number, -+ orgIdentifier: string, -+ keys: KeysRequest -+ ) { -+ this.masterPasswordHash = masterPasswordHash; -+ this.key = key; -+ this.masterPasswordHint = masterPasswordHint; -+ this.kdf = kdf; -+ this.kdfIterations = kdfIterations; -+ this.orgIdentifier = orgIdentifier; -+ this.keys = keys; -+ } - } -diff --git a/jslib/common/src/models/request/storageRequest.ts b/jslib/common/src/models/request/storageRequest.ts -index f4b78559..4b3b614d 100644 ---- a/jslib/common/src/models/request/storageRequest.ts -+++ b/jslib/common/src/models/request/storageRequest.ts -@@ -1,3 +1,3 @@ - export class StorageRequest { -- storageGbAdjustment: number; -+ storageGbAdjustment: number; - } -diff --git a/jslib/common/src/models/request/taxInfoUpdateRequest.ts b/jslib/common/src/models/request/taxInfoUpdateRequest.ts -index 5485164e..6881a8a4 100644 ---- a/jslib/common/src/models/request/taxInfoUpdateRequest.ts -+++ b/jslib/common/src/models/request/taxInfoUpdateRequest.ts -@@ -1,4 +1,4 @@ - export class TaxInfoUpdateRequest { -- country: string; -- postalCode: string; -+ country: string; -+ postalCode: string; - } -diff --git a/jslib/common/src/models/request/tokenRequest.ts b/jslib/common/src/models/request/tokenRequest.ts -index 41797eb0..72b8ac07 100644 ---- a/jslib/common/src/models/request/tokenRequest.ts -+++ b/jslib/common/src/models/request/tokenRequest.ts -@@ -1,84 +1,96 @@ --import { TwoFactorProviderType } from '../../enums/twoFactorProviderType'; -+import { TwoFactorProviderType } from "../../enums/twoFactorProviderType"; - --import { CaptchaProtectedRequest } from './captchaProtectedRequest'; --import { DeviceRequest } from './deviceRequest'; -+import { CaptchaProtectedRequest } from "./captchaProtectedRequest"; -+import { DeviceRequest } from "./deviceRequest"; - --import { Utils } from '../../misc/utils'; -+import { Utils } from "../../misc/utils"; - - export class TokenRequest implements CaptchaProtectedRequest { -- email: string; -- masterPasswordHash: string; -- code: string; -- codeVerifier: string; -- redirectUri: string; -- clientId: string; -- clientSecret: string; -- device?: DeviceRequest; -+ email: string; -+ masterPasswordHash: string; -+ code: string; -+ codeVerifier: string; -+ redirectUri: string; -+ clientId: string; -+ clientSecret: string; -+ device?: DeviceRequest; -+ orgId?: string; - -- constructor(credentials: string[], codes: string[], clientIdClientSecret: string[], public provider: TwoFactorProviderType, -- public token: string, public remember: boolean, public captchaResponse: string, device?: DeviceRequest) { -- if (credentials != null && credentials.length > 1) { -- this.email = credentials[0]; -- this.masterPasswordHash = credentials[1]; -- } else if (codes != null && codes.length > 2) { -- this.code = codes[0]; -- this.codeVerifier = codes[1]; -- this.redirectUri = codes[2]; -- } else if (clientIdClientSecret != null && clientIdClientSecret.length > 1) { -- this.clientId = clientIdClientSecret[0]; -- this.clientSecret = clientIdClientSecret[1]; -- } -- this.device = device != null ? device : null; -+ constructor( -+ credentials: string[], -+ codes: string[], -+ clientIdClientSecret: string[], -+ public provider: TwoFactorProviderType, -+ public token: string, -+ public remember: boolean, -+ public captchaResponse: string, -+ device?: DeviceRequest, -+ orgId?: string -+ ) { -+ if (credentials != null && credentials.length > 1) { -+ this.email = credentials[0]; -+ this.masterPasswordHash = credentials[1]; -+ } else if (codes != null && codes.length > 2) { -+ this.code = codes[0]; -+ this.codeVerifier = codes[1]; -+ this.redirectUri = codes[2]; -+ } else if (clientIdClientSecret != null && clientIdClientSecret.length > 1) { -+ this.clientId = clientIdClientSecret[0]; -+ this.clientSecret = clientIdClientSecret[1]; - } -+ if (orgId && orgId !== '') { -+ this.orgId = orgId; -+ } -+ this.device = device != null ? device : null; -+ } - -- toIdentityToken(clientId: string) { -- const obj: any = { -- scope: 'api offline_access', -- client_id: clientId, -- }; -- -- if (this.clientSecret != null) { -- obj.scope = clientId.startsWith('organization') ? 'api.organization' : 'api'; -- obj.grant_type = 'client_credentials'; -- obj.client_secret = this.clientSecret; -- } else if (this.masterPasswordHash != null && this.email != null) { -- obj.grant_type = 'password'; -- obj.username = this.email; -- obj.password = this.masterPasswordHash; -- } else if (this.code != null && this.codeVerifier != null && this.redirectUri != null) { -- obj.grant_type = 'authorization_code'; -- obj.code = this.code; -- obj.code_verifier = this.codeVerifier; -- obj.redirect_uri = this.redirectUri; -- } else { -- throw new Error('must provide credentials or codes'); -- } -- -- if (this.device) { -- obj.deviceType = this.device.type; -- obj.deviceIdentifier = this.device.identifier; -- obj.deviceName = this.device.name; -- // no push tokens for browser apps yet -- // obj.devicePushToken = this.device.pushToken; -- } -+ toIdentityToken(clientId: string) { -+ const obj: any = { -+ scope: "api offline_access", -+ client_id: clientId, -+ }; - -- if (this.token && this.provider != null) { -- obj.twoFactorToken = this.token; -- obj.twoFactorProvider = this.provider; -- obj.twoFactorRemember = this.remember ? '1' : '0'; -- } -+ if (this.clientSecret != null) { -+ obj.scope = clientId.startsWith("organization") ? "api.organization" : "api"; -+ obj.grant_type = "client_credentials"; -+ obj.client_secret = this.clientSecret; -+ } else if (this.masterPasswordHash != null && this.email != null) { -+ obj.grant_type = "password"; -+ obj.username = this.email; -+ obj.password = this.masterPasswordHash; -+ } else if (this.code != null && this.codeVerifier != null && this.redirectUri != null) { -+ obj.grant_type = "authorization_code"; -+ obj.code = this.code; -+ obj.code_verifier = this.codeVerifier; -+ obj.redirect_uri = this.redirectUri; -+ } else { -+ throw new Error("must provide credentials or codes"); -+ } - -- if (this.captchaResponse != null) { -- obj.captchaResponse = this.captchaResponse; -- } -+ if (this.device) { -+ obj.deviceType = this.device.type; -+ obj.deviceIdentifier = this.device.identifier; -+ obj.deviceName = this.device.name; -+ // no push tokens for browser apps yet -+ // obj.devicePushToken = this.device.pushToken; -+ } - -+ if (this.token && this.provider != null) { -+ obj.twoFactorToken = this.token; -+ obj.twoFactorProvider = this.provider; -+ obj.twoFactorRemember = this.remember ? "1" : "0"; -+ } - -- return obj; -+ if (this.captchaResponse != null) { -+ obj.captchaResponse = this.captchaResponse; - } - -- alterIdentityTokenHeaders(headers: Headers) { -- if (this.clientSecret == null && this.masterPasswordHash != null && this.email != null) { -- headers.set('Auth-Email', Utils.fromUtf8ToUrlB64(this.email)); -- } -+ return obj; -+ } -+ -+ alterIdentityTokenHeaders(headers: Headers) { -+ if (this.clientSecret == null && this.masterPasswordHash != null && this.email != null) { -+ headers.set("Auth-Email", Utils.fromUtf8ToUrlB64(this.email)); - } -+ } - } -diff --git a/jslib/common/src/models/request/twoFactorEmailRequest.ts b/jslib/common/src/models/request/twoFactorEmailRequest.ts -index 6eeb0185..36c168b6 100644 ---- a/jslib/common/src/models/request/twoFactorEmailRequest.ts -+++ b/jslib/common/src/models/request/twoFactorEmailRequest.ts -@@ -1,5 +1,5 @@ --import { SecretVerificationRequest } from './secretVerificationRequest'; -+import { SecretVerificationRequest } from "./secretVerificationRequest"; - - export class TwoFactorEmailRequest extends SecretVerificationRequest { -- email: string; -+ email: string; - } -diff --git a/jslib/common/src/models/request/twoFactorProviderRequest.ts b/jslib/common/src/models/request/twoFactorProviderRequest.ts -index c9138597..d80ba5ba 100644 ---- a/jslib/common/src/models/request/twoFactorProviderRequest.ts -+++ b/jslib/common/src/models/request/twoFactorProviderRequest.ts -@@ -1,7 +1,7 @@ --import { SecretVerificationRequest } from './secretVerificationRequest'; -+import { SecretVerificationRequest } from "./secretVerificationRequest"; - --import { TwoFactorProviderType } from '../../enums/twoFactorProviderType'; -+import { TwoFactorProviderType } from "../../enums/twoFactorProviderType"; - - export class TwoFactorProviderRequest extends SecretVerificationRequest { -- type: TwoFactorProviderType; -+ type: TwoFactorProviderType; - } -diff --git a/jslib/common/src/models/request/twoFactorRecoveryRequest.ts b/jslib/common/src/models/request/twoFactorRecoveryRequest.ts -index 3352f327..39135a37 100644 ---- a/jslib/common/src/models/request/twoFactorRecoveryRequest.ts -+++ b/jslib/common/src/models/request/twoFactorRecoveryRequest.ts -@@ -1,6 +1,6 @@ --import { SecretVerificationRequest } from './secretVerificationRequest'; -+import { SecretVerificationRequest } from "./secretVerificationRequest"; - - export class TwoFactorRecoveryRequest extends SecretVerificationRequest { -- recoveryCode: string; -- email: string; -+ recoveryCode: string; -+ email: string; - } -diff --git a/jslib/common/src/models/request/updateDomainsRequest.ts b/jslib/common/src/models/request/updateDomainsRequest.ts -index d624369d..528d3682 100644 ---- a/jslib/common/src/models/request/updateDomainsRequest.ts -+++ b/jslib/common/src/models/request/updateDomainsRequest.ts -@@ -1,4 +1,4 @@ - export class UpdateDomainsRequest { -- equivalentDomains: string[][]; -- excludedGlobalEquivalentDomains: number[]; -+ equivalentDomains: string[][]; -+ excludedGlobalEquivalentDomains: number[]; - } -diff --git a/jslib/common/src/models/request/updateKeyRequest.ts b/jslib/common/src/models/request/updateKeyRequest.ts -index c0521b9b..a9fceffd 100644 ---- a/jslib/common/src/models/request/updateKeyRequest.ts -+++ b/jslib/common/src/models/request/updateKeyRequest.ts -@@ -1,12 +1,12 @@ --import { CipherWithIdRequest } from './cipherWithIdRequest'; --import { FolderWithIdRequest } from './folderWithIdRequest'; --import { SendWithIdRequest } from './sendWithIdRequest'; -+import { CipherWithIdRequest } from "./cipherWithIdRequest"; -+import { FolderWithIdRequest } from "./folderWithIdRequest"; -+import { SendWithIdRequest } from "./sendWithIdRequest"; - - export class UpdateKeyRequest { -- ciphers: CipherWithIdRequest[] = []; -- folders: FolderWithIdRequest[] = []; -- sends: SendWithIdRequest[] = []; -- masterPasswordHash: string; -- privateKey: string; -- key: string; -+ ciphers: CipherWithIdRequest[] = []; -+ folders: FolderWithIdRequest[] = []; -+ sends: SendWithIdRequest[] = []; -+ masterPasswordHash: string; -+ privateKey: string; -+ key: string; - } -diff --git a/jslib/common/src/models/request/updateProfileRequest.ts b/jslib/common/src/models/request/updateProfileRequest.ts -index e029f3f7..14ed554d 100644 ---- a/jslib/common/src/models/request/updateProfileRequest.ts -+++ b/jslib/common/src/models/request/updateProfileRequest.ts -@@ -1,10 +1,10 @@ - export class UpdateProfileRequest { -- name: string; -- masterPasswordHint: string; -- culture = 'en-US'; // deprecated -+ name: string; -+ masterPasswordHint: string; -+ culture = "en-US"; // deprecated - -- constructor(name: string, masterPasswordHint: string) { -- this.name = name; -- this.masterPasswordHint = masterPasswordHint ? masterPasswordHint : null; -- } -+ constructor(name: string, masterPasswordHint: string) { -+ this.name = name; -+ this.masterPasswordHint = masterPasswordHint ? masterPasswordHint : null; -+ } - } -diff --git a/jslib/common/src/models/request/updateTempPasswordRequest.ts b/jslib/common/src/models/request/updateTempPasswordRequest.ts -index 044311d3..1bd97697 100644 ---- a/jslib/common/src/models/request/updateTempPasswordRequest.ts -+++ b/jslib/common/src/models/request/updateTempPasswordRequest.ts -@@ -1,5 +1,5 @@ --import { OrganizationUserResetPasswordRequest } from './organizationUserResetPasswordRequest'; -+import { OrganizationUserResetPasswordRequest } from "./organizationUserResetPasswordRequest"; - - export class UpdateTempPasswordRequest extends OrganizationUserResetPasswordRequest { -- masterPasswordHint: string; -+ masterPasswordHint: string; - } -diff --git a/jslib/common/src/models/request/updateTwoFactorAuthenticatorRequest.ts b/jslib/common/src/models/request/updateTwoFactorAuthenticatorRequest.ts -index fa0a96c0..9b718287 100644 ---- a/jslib/common/src/models/request/updateTwoFactorAuthenticatorRequest.ts -+++ b/jslib/common/src/models/request/updateTwoFactorAuthenticatorRequest.ts -@@ -1,6 +1,6 @@ --import { SecretVerificationRequest } from './secretVerificationRequest'; -+import { SecretVerificationRequest } from "./secretVerificationRequest"; - - export class UpdateTwoFactorAuthenticatorRequest extends SecretVerificationRequest { -- token: string; -- key: string; -+ token: string; -+ key: string; - } -diff --git a/jslib/common/src/models/request/updateTwoFactorDuoRequest.ts b/jslib/common/src/models/request/updateTwoFactorDuoRequest.ts -index 9465e2f9..cd82215d 100644 ---- a/jslib/common/src/models/request/updateTwoFactorDuoRequest.ts -+++ b/jslib/common/src/models/request/updateTwoFactorDuoRequest.ts -@@ -1,7 +1,7 @@ --import { SecretVerificationRequest } from './secretVerificationRequest'; -+import { SecretVerificationRequest } from "./secretVerificationRequest"; - - export class UpdateTwoFactorDuoRequest extends SecretVerificationRequest { -- integrationKey: string; -- secretKey: string; -- host: string; -+ integrationKey: string; -+ secretKey: string; -+ host: string; - } -diff --git a/jslib/common/src/models/request/updateTwoFactorEmailRequest.ts b/jslib/common/src/models/request/updateTwoFactorEmailRequest.ts -index ce2fedb5..be7da1fd 100644 ---- a/jslib/common/src/models/request/updateTwoFactorEmailRequest.ts -+++ b/jslib/common/src/models/request/updateTwoFactorEmailRequest.ts -@@ -1,6 +1,6 @@ --import { SecretVerificationRequest } from './secretVerificationRequest'; -+import { SecretVerificationRequest } from "./secretVerificationRequest"; - - export class UpdateTwoFactorEmailRequest extends SecretVerificationRequest { -- token: string; -- email: string; -+ token: string; -+ email: string; - } -diff --git a/jslib/common/src/models/request/updateTwoFactorWebAuthnDeleteRequest.ts b/jslib/common/src/models/request/updateTwoFactorWebAuthnDeleteRequest.ts -index 8fa5fcd0..1decda72 100644 ---- a/jslib/common/src/models/request/updateTwoFactorWebAuthnDeleteRequest.ts -+++ b/jslib/common/src/models/request/updateTwoFactorWebAuthnDeleteRequest.ts -@@ -1,5 +1,5 @@ --import { SecretVerificationRequest } from './secretVerificationRequest'; -+import { SecretVerificationRequest } from "./secretVerificationRequest"; - - export class UpdateTwoFactorWebAuthnDeleteRequest extends SecretVerificationRequest { -- id: number; -+ id: number; - } -diff --git a/jslib/common/src/models/request/updateTwoFactorWebAuthnRequest.ts b/jslib/common/src/models/request/updateTwoFactorWebAuthnRequest.ts -index 09976c31..2e788b52 100644 ---- a/jslib/common/src/models/request/updateTwoFactorWebAuthnRequest.ts -+++ b/jslib/common/src/models/request/updateTwoFactorWebAuthnRequest.ts -@@ -1,7 +1,7 @@ --import { SecretVerificationRequest } from './secretVerificationRequest'; -+import { SecretVerificationRequest } from "./secretVerificationRequest"; - - export class UpdateTwoFactorWebAuthnRequest extends SecretVerificationRequest { -- deviceResponse: PublicKeyCredential; -- name: string; -- id: number; -+ deviceResponse: PublicKeyCredential; -+ name: string; -+ id: number; - } -diff --git a/jslib/common/src/models/request/updateTwoFactorYubioOtpRequest.ts b/jslib/common/src/models/request/updateTwoFactorYubioOtpRequest.ts -index 29a9dd81..cb495b1e 100644 ---- a/jslib/common/src/models/request/updateTwoFactorYubioOtpRequest.ts -+++ b/jslib/common/src/models/request/updateTwoFactorYubioOtpRequest.ts -@@ -1,10 +1,10 @@ --import { SecretVerificationRequest } from './secretVerificationRequest'; -+import { SecretVerificationRequest } from "./secretVerificationRequest"; - - export class UpdateTwoFactorYubioOtpRequest extends SecretVerificationRequest { -- key1: string; -- key2: string; -- key3: string; -- key4: string; -- key5: string; -- nfc: boolean; -+ key1: string; -+ key2: string; -+ key3: string; -+ key4: string; -+ key5: string; -+ nfc: boolean; - } -diff --git a/jslib/common/src/models/request/verifyBankRequest.ts b/jslib/common/src/models/request/verifyBankRequest.ts -index bddd452d..823eaf46 100644 ---- a/jslib/common/src/models/request/verifyBankRequest.ts -+++ b/jslib/common/src/models/request/verifyBankRequest.ts -@@ -1,4 +1,4 @@ - export class VerifyBankRequest { -- amount1: number; -- amount2: number; -+ amount1: number; -+ amount2: number; - } -diff --git a/jslib/common/src/models/request/verifyDeleteRecoverRequest.ts b/jslib/common/src/models/request/verifyDeleteRecoverRequest.ts -index 9372b4bf..2374d32d 100644 ---- a/jslib/common/src/models/request/verifyDeleteRecoverRequest.ts -+++ b/jslib/common/src/models/request/verifyDeleteRecoverRequest.ts -@@ -1,9 +1,9 @@ - export class VerifyDeleteRecoverRequest { -- userId: string; -- token: string; -+ userId: string; -+ token: string; - -- constructor(userId: string, token: string) { -- this.userId = userId; -- this.token = token; -- } -+ constructor(userId: string, token: string) { -+ this.userId = userId; -+ this.token = token; -+ } - } -diff --git a/jslib/common/src/models/request/verifyEmailRequest.ts b/jslib/common/src/models/request/verifyEmailRequest.ts -index 7609fd06..ecee0190 100644 ---- a/jslib/common/src/models/request/verifyEmailRequest.ts -+++ b/jslib/common/src/models/request/verifyEmailRequest.ts -@@ -1,9 +1,9 @@ - export class VerifyEmailRequest { -- userId: string; -- token: string; -+ userId: string; -+ token: string; - -- constructor(userId: string, token: string) { -- this.userId = userId; -- this.token = token; -- } -+ constructor(userId: string, token: string) { -+ this.userId = userId; -+ this.token = token; -+ } - } -diff --git a/jslib/common/src/models/response/apiKeyResponse.ts b/jslib/common/src/models/response/apiKeyResponse.ts -index b530b2dc..f1a24be0 100644 ---- a/jslib/common/src/models/response/apiKeyResponse.ts -+++ b/jslib/common/src/models/response/apiKeyResponse.ts -@@ -1,10 +1,10 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class ApiKeyResponse extends BaseResponse { -- apiKey: string; -+ apiKey: string; - -- constructor(response: any) { -- super(response); -- this.apiKey = this.getResponseProperty('ApiKey'); -- } -+ constructor(response: any) { -+ super(response); -+ this.apiKey = this.getResponseProperty("ApiKey"); -+ } - } -diff --git a/jslib/common/src/models/response/attachmentResponse.ts b/jslib/common/src/models/response/attachmentResponse.ts -index 47c01cad..a9ebfaf3 100644 ---- a/jslib/common/src/models/response/attachmentResponse.ts -+++ b/jslib/common/src/models/response/attachmentResponse.ts -@@ -1,20 +1,20 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class AttachmentResponse extends BaseResponse { -- id: string; -- url: string; -- fileName: string; -- key: string; -- size: string; -- sizeName: string; -+ id: string; -+ url: string; -+ fileName: string; -+ key: string; -+ size: string; -+ sizeName: string; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.url = this.getResponseProperty('Url'); -- this.fileName = this.getResponseProperty('FileName'); -- this.key = this.getResponseProperty('Key'); -- this.size = this.getResponseProperty('Size'); -- this.sizeName = this.getResponseProperty('SizeName'); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.url = this.getResponseProperty("Url"); -+ this.fileName = this.getResponseProperty("FileName"); -+ this.key = this.getResponseProperty("Key"); -+ this.size = this.getResponseProperty("Size"); -+ this.sizeName = this.getResponseProperty("SizeName"); -+ } - } -diff --git a/jslib/common/src/models/response/attachmentUploadDataResponse.ts b/jslib/common/src/models/response/attachmentUploadDataResponse.ts -index dd71276c..e9651452 100644 ---- a/jslib/common/src/models/response/attachmentUploadDataResponse.ts -+++ b/jslib/common/src/models/response/attachmentUploadDataResponse.ts -@@ -1,22 +1,22 @@ --import { FileUploadType } from '../../enums/fileUploadType'; --import { BaseResponse } from './baseResponse'; --import { CipherResponse } from './cipherResponse'; -+import { FileUploadType } from "../../enums/fileUploadType"; -+import { BaseResponse } from "./baseResponse"; -+import { CipherResponse } from "./cipherResponse"; - - export class AttachmentUploadDataResponse extends BaseResponse { -- attachmentId: string; -- fileUploadType: FileUploadType; -- cipherResponse: CipherResponse; -- cipherMiniResponse: CipherResponse; -- url: string = null; -- constructor(response: any) { -- super(response); -- this.attachmentId = this.getResponseProperty('AttachmentId'); -- this.fileUploadType = this.getResponseProperty('FileUploadType'); -- const cipherResponse = this.getResponseProperty('CipherResponse'); -- const cipherMiniResponse = this.getResponseProperty('CipherMiniResponse'); -- this.cipherResponse = cipherResponse == null ? null : new CipherResponse(cipherResponse); -- this.cipherMiniResponse = cipherMiniResponse == null ? null : new CipherResponse(cipherMiniResponse); -- this.url = this.getResponseProperty('Url'); -- } -- -+ attachmentId: string; -+ fileUploadType: FileUploadType; -+ cipherResponse: CipherResponse; -+ cipherMiniResponse: CipherResponse; -+ url: string = null; -+ constructor(response: any) { -+ super(response); -+ this.attachmentId = this.getResponseProperty("AttachmentId"); -+ this.fileUploadType = this.getResponseProperty("FileUploadType"); -+ const cipherResponse = this.getResponseProperty("CipherResponse"); -+ const cipherMiniResponse = this.getResponseProperty("CipherMiniResponse"); -+ this.cipherResponse = cipherResponse == null ? null : new CipherResponse(cipherResponse); -+ this.cipherMiniResponse = -+ cipherMiniResponse == null ? null : new CipherResponse(cipherMiniResponse); -+ this.url = this.getResponseProperty("Url"); -+ } - } -diff --git a/jslib/common/src/models/response/baseResponse.ts b/jslib/common/src/models/response/baseResponse.ts -index 38184322..8c599fba 100644 ---- a/jslib/common/src/models/response/baseResponse.ts -+++ b/jslib/common/src/models/response/baseResponse.ts -@@ -1,39 +1,43 @@ - export abstract class BaseResponse { -- private response: any; -+ private response: any; - -- constructor(response: any) { -- this.response = response; -- } -+ constructor(response: any) { -+ this.response = response; -+ } - -- protected getResponseProperty(propertyName: string, response: any = null, exactName = false): any { -- if (propertyName == null || propertyName === '') { -- throw new Error('propertyName must not be null/empty.'); -- } -- if (response == null && this.response != null) { -- response = this.response; -- } -- if (response == null) { -- return null; -- } -- if (!exactName && response[propertyName] === undefined) { -- let otherCasePropertyName: string = null; -- if (propertyName.charAt(0) === propertyName.charAt(0).toUpperCase()) { -- otherCasePropertyName = propertyName.charAt(0).toLowerCase(); -- } else { -- otherCasePropertyName = propertyName.charAt(0).toUpperCase(); -- } -- if (propertyName.length > 1) { -- otherCasePropertyName += propertyName.slice(1); -- } -+ protected getResponseProperty( -+ propertyName: string, -+ response: any = null, -+ exactName = false -+ ): any { -+ if (propertyName == null || propertyName === "") { -+ throw new Error("propertyName must not be null/empty."); -+ } -+ if (response == null && this.response != null) { -+ response = this.response; -+ } -+ if (response == null) { -+ return null; -+ } -+ if (!exactName && response[propertyName] === undefined) { -+ let otherCasePropertyName: string = null; -+ if (propertyName.charAt(0) === propertyName.charAt(0).toUpperCase()) { -+ otherCasePropertyName = propertyName.charAt(0).toLowerCase(); -+ } else { -+ otherCasePropertyName = propertyName.charAt(0).toUpperCase(); -+ } -+ if (propertyName.length > 1) { -+ otherCasePropertyName += propertyName.slice(1); -+ } - -- propertyName = otherCasePropertyName; -- if (response[propertyName] === undefined) { -- propertyName = propertyName.toLowerCase(); -- } -- if (response[propertyName] === undefined) { -- propertyName = propertyName.toUpperCase(); -- } -- } -- return response[propertyName]; -+ propertyName = otherCasePropertyName; -+ if (response[propertyName] === undefined) { -+ propertyName = propertyName.toLowerCase(); -+ } -+ if (response[propertyName] === undefined) { -+ propertyName = propertyName.toUpperCase(); -+ } - } -+ return response[propertyName]; -+ } - } -diff --git a/jslib/common/src/models/response/billingResponse.ts b/jslib/common/src/models/response/billingResponse.ts -index 36954db6..91905cb2 100644 ---- a/jslib/common/src/models/response/billingResponse.ts -+++ b/jslib/common/src/models/response/billingResponse.ts -@@ -1,83 +1,83 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - --import { PaymentMethodType } from '../../enums/paymentMethodType'; --import { TransactionType } from '../../enums/transactionType'; -+import { PaymentMethodType } from "../../enums/paymentMethodType"; -+import { TransactionType } from "../../enums/transactionType"; - - export class BillingResponse extends BaseResponse { -- balance: number; -- paymentSource: BillingSourceResponse; -- invoices: BillingInvoiceResponse[] = []; -- transactions: BillingTransactionResponse[] = []; -+ balance: number; -+ paymentSource: BillingSourceResponse; -+ invoices: BillingInvoiceResponse[] = []; -+ transactions: BillingTransactionResponse[] = []; - -- constructor(response: any) { -- super(response); -- this.balance = this.getResponseProperty('Balance'); -- const paymentSource = this.getResponseProperty('PaymentSource'); -- const transactions = this.getResponseProperty('Transactions'); -- const invoices = this.getResponseProperty('Invoices'); -- this.paymentSource = paymentSource == null ? null : new BillingSourceResponse(paymentSource); -- if (transactions != null) { -- this.transactions = transactions.map((t: any) => new BillingTransactionResponse(t)); -- } -- if (invoices != null) { -- this.invoices = invoices.map((i: any) => new BillingInvoiceResponse(i)); -- } -+ constructor(response: any) { -+ super(response); -+ this.balance = this.getResponseProperty("Balance"); -+ const paymentSource = this.getResponseProperty("PaymentSource"); -+ const transactions = this.getResponseProperty("Transactions"); -+ const invoices = this.getResponseProperty("Invoices"); -+ this.paymentSource = paymentSource == null ? null : new BillingSourceResponse(paymentSource); -+ if (transactions != null) { -+ this.transactions = transactions.map((t: any) => new BillingTransactionResponse(t)); - } -+ if (invoices != null) { -+ this.invoices = invoices.map((i: any) => new BillingInvoiceResponse(i)); -+ } -+ } - } - - export class BillingSourceResponse extends BaseResponse { -- type: PaymentMethodType; -- cardBrand: string; -- description: string; -- needsVerification: boolean; -+ type: PaymentMethodType; -+ cardBrand: string; -+ description: string; -+ needsVerification: boolean; - -- constructor(response: any) { -- super(response); -- this.type = this.getResponseProperty('Type'); -- this.cardBrand = this.getResponseProperty('CardBrand'); -- this.description = this.getResponseProperty('Description'); -- this.needsVerification = this.getResponseProperty('NeedsVerification'); -- } -+ constructor(response: any) { -+ super(response); -+ this.type = this.getResponseProperty("Type"); -+ this.cardBrand = this.getResponseProperty("CardBrand"); -+ this.description = this.getResponseProperty("Description"); -+ this.needsVerification = this.getResponseProperty("NeedsVerification"); -+ } - } - - export class BillingInvoiceResponse extends BaseResponse { -- url: string; -- pdfUrl: string; -- number: string; -- paid: boolean; -- date: string; -- amount: number; -+ url: string; -+ pdfUrl: string; -+ number: string; -+ paid: boolean; -+ date: string; -+ amount: number; - -- constructor(response: any) { -- super(response); -- this.url = this.getResponseProperty('Url'); -- this.pdfUrl = this.getResponseProperty('PdfUrl'); -- this.number = this.getResponseProperty('Number'); -- this.paid = this.getResponseProperty('Paid'); -- this.date = this.getResponseProperty('Date'); -- this.amount = this.getResponseProperty('Amount'); -- } -+ constructor(response: any) { -+ super(response); -+ this.url = this.getResponseProperty("Url"); -+ this.pdfUrl = this.getResponseProperty("PdfUrl"); -+ this.number = this.getResponseProperty("Number"); -+ this.paid = this.getResponseProperty("Paid"); -+ this.date = this.getResponseProperty("Date"); -+ this.amount = this.getResponseProperty("Amount"); -+ } - } - - export class BillingTransactionResponse extends BaseResponse { -- createdDate: string; -- amount: number; -- refunded: boolean; -- partiallyRefunded: boolean; -- refundedAmount: number; -- type: TransactionType; -- paymentMethodType: PaymentMethodType; -- details: string; -+ createdDate: string; -+ amount: number; -+ refunded: boolean; -+ partiallyRefunded: boolean; -+ refundedAmount: number; -+ type: TransactionType; -+ paymentMethodType: PaymentMethodType; -+ details: string; - -- constructor(response: any) { -- super(response); -- this.createdDate = this.getResponseProperty('CreatedDate'); -- this.amount = this.getResponseProperty('Amount'); -- this.refunded = this.getResponseProperty('Refunded'); -- this.partiallyRefunded = this.getResponseProperty('PartiallyRefunded'); -- this.refundedAmount = this.getResponseProperty('RefundedAmount'); -- this.type = this.getResponseProperty('Type'); -- this.paymentMethodType = this.getResponseProperty('PaymentMethodType'); -- this.details = this.getResponseProperty('Details'); -- } -+ constructor(response: any) { -+ super(response); -+ this.createdDate = this.getResponseProperty("CreatedDate"); -+ this.amount = this.getResponseProperty("Amount"); -+ this.refunded = this.getResponseProperty("Refunded"); -+ this.partiallyRefunded = this.getResponseProperty("PartiallyRefunded"); -+ this.refundedAmount = this.getResponseProperty("RefundedAmount"); -+ this.type = this.getResponseProperty("Type"); -+ this.paymentMethodType = this.getResponseProperty("PaymentMethodType"); -+ this.details = this.getResponseProperty("Details"); -+ } - } -diff --git a/jslib/common/src/models/response/breachAccountResponse.ts b/jslib/common/src/models/response/breachAccountResponse.ts -index bfa4f70c..00d39acc 100644 ---- a/jslib/common/src/models/response/breachAccountResponse.ts -+++ b/jslib/common/src/models/response/breachAccountResponse.ts -@@ -1,32 +1,32 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class BreachAccountResponse extends BaseResponse { -- addedDate: string; -- breachDate: string; -- dataClasses: string[]; -- description: string; -- domain: string; -- isActive: boolean; -- isVerified: boolean; -- logoPath: string; -- modifiedDate: string; -- name: string; -- pwnCount: number; -- title: string; -+ addedDate: string; -+ breachDate: string; -+ dataClasses: string[]; -+ description: string; -+ domain: string; -+ isActive: boolean; -+ isVerified: boolean; -+ logoPath: string; -+ modifiedDate: string; -+ name: string; -+ pwnCount: number; -+ title: string; - -- constructor(response: any) { -- super(response); -- this.addedDate = this.getResponseProperty('AddedDate'); -- this.breachDate = this.getResponseProperty('BreachDate'); -- this.dataClasses = this.getResponseProperty('DataClasses'); -- this.description = this.getResponseProperty('Description'); -- this.domain = this.getResponseProperty('Domain'); -- this.isActive = this.getResponseProperty('IsActive'); -- this.isVerified = this.getResponseProperty('IsVerified'); -- this.logoPath = this.getResponseProperty('LogoPath'); -- this.modifiedDate = this.getResponseProperty('ModifiedDate'); -- this.name = this.getResponseProperty('Name'); -- this.pwnCount = this.getResponseProperty('PwnCount'); -- this.title = this.getResponseProperty('Title'); -- } -+ constructor(response: any) { -+ super(response); -+ this.addedDate = this.getResponseProperty("AddedDate"); -+ this.breachDate = this.getResponseProperty("BreachDate"); -+ this.dataClasses = this.getResponseProperty("DataClasses"); -+ this.description = this.getResponseProperty("Description"); -+ this.domain = this.getResponseProperty("Domain"); -+ this.isActive = this.getResponseProperty("IsActive"); -+ this.isVerified = this.getResponseProperty("IsVerified"); -+ this.logoPath = this.getResponseProperty("LogoPath"); -+ this.modifiedDate = this.getResponseProperty("ModifiedDate"); -+ this.name = this.getResponseProperty("Name"); -+ this.pwnCount = this.getResponseProperty("PwnCount"); -+ this.title = this.getResponseProperty("Title"); -+ } - } -diff --git a/jslib/common/src/models/response/cipherResponse.ts b/jslib/common/src/models/response/cipherResponse.ts -index 55548939..0be26463 100644 ---- a/jslib/common/src/models/response/cipherResponse.ts -+++ b/jslib/common/src/models/response/cipherResponse.ts -@@ -1,92 +1,92 @@ --import { AttachmentResponse } from './attachmentResponse'; --import { BaseResponse } from './baseResponse'; --import { PasswordHistoryResponse } from './passwordHistoryResponse'; -+import { AttachmentResponse } from "./attachmentResponse"; -+import { BaseResponse } from "./baseResponse"; -+import { PasswordHistoryResponse } from "./passwordHistoryResponse"; - --import { CipherRepromptType } from '../../enums/cipherRepromptType'; --import { CardApi } from '../api/cardApi'; --import { FieldApi } from '../api/fieldApi'; --import { IdentityApi } from '../api/identityApi'; --import { LoginApi } from '../api/loginApi'; --import { SecureNoteApi } from '../api/secureNoteApi'; -+import { CipherRepromptType } from "../../enums/cipherRepromptType"; -+import { CardApi } from "../api/cardApi"; -+import { FieldApi } from "../api/fieldApi"; -+import { IdentityApi } from "../api/identityApi"; -+import { LoginApi } from "../api/loginApi"; -+import { SecureNoteApi } from "../api/secureNoteApi"; - - export class CipherResponse extends BaseResponse { -- id: string; -- organizationId: string; -- folderId: string; -- type: number; -- name: string; -- notes: string; -- fields: FieldApi[]; -- login: LoginApi; -- card: CardApi; -- identity: IdentityApi; -- secureNote: SecureNoteApi; -- favorite: boolean; -- edit: boolean; -- viewPassword: boolean; -- organizationUseTotp: boolean; -- revisionDate: string; -- attachments: AttachmentResponse[]; -- passwordHistory: PasswordHistoryResponse[]; -- collectionIds: string[]; -- deletedDate: string; -- reprompt: CipherRepromptType; -+ id: string; -+ organizationId: string; -+ folderId: string; -+ type: number; -+ name: string; -+ notes: string; -+ fields: FieldApi[]; -+ login: LoginApi; -+ card: CardApi; -+ identity: IdentityApi; -+ secureNote: SecureNoteApi; -+ favorite: boolean; -+ edit: boolean; -+ viewPassword: boolean; -+ organizationUseTotp: boolean; -+ revisionDate: string; -+ attachments: AttachmentResponse[]; -+ passwordHistory: PasswordHistoryResponse[]; -+ collectionIds: string[]; -+ deletedDate: string; -+ reprompt: CipherRepromptType; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.organizationId = this.getResponseProperty('OrganizationId'); -- this.folderId = this.getResponseProperty('FolderId') || null; -- this.type = this.getResponseProperty('Type'); -- this.name = this.getResponseProperty('Name'); -- this.notes = this.getResponseProperty('Notes'); -- this.favorite = this.getResponseProperty('Favorite') || false; -- this.edit = !!this.getResponseProperty('Edit'); -- if (this.getResponseProperty('ViewPassword') == null) { -- this.viewPassword = true; -- } else { -- this.viewPassword = this.getResponseProperty('ViewPassword'); -- } -- this.organizationUseTotp = this.getResponseProperty('OrganizationUseTotp'); -- this.revisionDate = this.getResponseProperty('RevisionDate'); -- this.collectionIds = this.getResponseProperty('CollectionIds'); -- this.deletedDate = this.getResponseProperty('DeletedDate'); -- -- const login = this.getResponseProperty('Login'); -- if (login != null) { -- this.login = new LoginApi(login); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.organizationId = this.getResponseProperty("OrganizationId"); -+ this.folderId = this.getResponseProperty("FolderId") || null; -+ this.type = this.getResponseProperty("Type"); -+ this.name = this.getResponseProperty("Name"); -+ this.notes = this.getResponseProperty("Notes"); -+ this.favorite = this.getResponseProperty("Favorite") || false; -+ this.edit = !!this.getResponseProperty("Edit"); -+ if (this.getResponseProperty("ViewPassword") == null) { -+ this.viewPassword = true; -+ } else { -+ this.viewPassword = this.getResponseProperty("ViewPassword"); -+ } -+ this.organizationUseTotp = this.getResponseProperty("OrganizationUseTotp"); -+ this.revisionDate = this.getResponseProperty("RevisionDate"); -+ this.collectionIds = this.getResponseProperty("CollectionIds"); -+ this.deletedDate = this.getResponseProperty("DeletedDate"); - -- const card = this.getResponseProperty('Card'); -- if (card != null) { -- this.card = new CardApi(card); -- } -+ const login = this.getResponseProperty("Login"); -+ if (login != null) { -+ this.login = new LoginApi(login); -+ } - -- const identity = this.getResponseProperty('Identity'); -- if (identity != null) { -- this.identity = new IdentityApi(identity); -- } -+ const card = this.getResponseProperty("Card"); -+ if (card != null) { -+ this.card = new CardApi(card); -+ } - -- const secureNote = this.getResponseProperty('SecureNote'); -- if (secureNote != null) { -- this.secureNote = new SecureNoteApi(secureNote); -- } -+ const identity = this.getResponseProperty("Identity"); -+ if (identity != null) { -+ this.identity = new IdentityApi(identity); -+ } - -- const fields = this.getResponseProperty('Fields'); -- if (fields != null) { -- this.fields = fields.map((f: any) => new FieldApi(f)); -- } -+ const secureNote = this.getResponseProperty("SecureNote"); -+ if (secureNote != null) { -+ this.secureNote = new SecureNoteApi(secureNote); -+ } - -- const attachments = this.getResponseProperty('Attachments'); -- if (attachments != null) { -- this.attachments = attachments.map((a: any) => new AttachmentResponse(a)); -- } -+ const fields = this.getResponseProperty("Fields"); -+ if (fields != null) { -+ this.fields = fields.map((f: any) => new FieldApi(f)); -+ } - -- const passwordHistory = this.getResponseProperty('PasswordHistory'); -- if (passwordHistory != null) { -- this.passwordHistory = passwordHistory.map((h: any) => new PasswordHistoryResponse(h)); -- } -+ const attachments = this.getResponseProperty("Attachments"); -+ if (attachments != null) { -+ this.attachments = attachments.map((a: any) => new AttachmentResponse(a)); -+ } - -- this.reprompt = this.getResponseProperty('Reprompt') || CipherRepromptType.None; -+ const passwordHistory = this.getResponseProperty("PasswordHistory"); -+ if (passwordHistory != null) { -+ this.passwordHistory = passwordHistory.map((h: any) => new PasswordHistoryResponse(h)); - } -+ -+ this.reprompt = this.getResponseProperty("Reprompt") || CipherRepromptType.None; -+ } - } -diff --git a/jslib/common/src/models/response/collectionResponse.ts b/jslib/common/src/models/response/collectionResponse.ts -index eb9441c4..2f677f7d 100644 ---- a/jslib/common/src/models/response/collectionResponse.ts -+++ b/jslib/common/src/models/response/collectionResponse.ts -@@ -1,38 +1,38 @@ --import { BaseResponse } from './baseResponse'; --import { SelectionReadOnlyResponse } from './selectionReadOnlyResponse'; -+import { BaseResponse } from "./baseResponse"; -+import { SelectionReadOnlyResponse } from "./selectionReadOnlyResponse"; - - export class CollectionResponse extends BaseResponse { -- id: string; -- organizationId: string; -- name: string; -- externalId: string; -+ id: string; -+ organizationId: string; -+ name: string; -+ externalId: string; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.organizationId = this.getResponseProperty('OrganizationId'); -- this.name = this.getResponseProperty('Name'); -- this.externalId = this.getResponseProperty('ExternalId'); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.organizationId = this.getResponseProperty("OrganizationId"); -+ this.name = this.getResponseProperty("Name"); -+ this.externalId = this.getResponseProperty("ExternalId"); -+ } - } - - export class CollectionDetailsResponse extends CollectionResponse { -- readOnly: boolean; -+ readOnly: boolean; - -- constructor(response: any) { -- super(response); -- this.readOnly = this.getResponseProperty('ReadOnly') || false; -- } -+ constructor(response: any) { -+ super(response); -+ this.readOnly = this.getResponseProperty("ReadOnly") || false; -+ } - } - - export class CollectionGroupDetailsResponse extends CollectionResponse { -- groups: SelectionReadOnlyResponse[] = []; -+ groups: SelectionReadOnlyResponse[] = []; - -- constructor(response: any) { -- super(response); -- const groups = this.getResponseProperty('Groups'); -- if (groups != null) { -- this.groups = groups.map((g: any) => new SelectionReadOnlyResponse(g)); -- } -+ constructor(response: any) { -+ super(response); -+ const groups = this.getResponseProperty("Groups"); -+ if (groups != null) { -+ this.groups = groups.map((g: any) => new SelectionReadOnlyResponse(g)); - } -+ } - } -diff --git a/jslib/common/src/models/response/deviceResponse.ts b/jslib/common/src/models/response/deviceResponse.ts -index 30f4e400..e3857cc1 100644 ---- a/jslib/common/src/models/response/deviceResponse.ts -+++ b/jslib/common/src/models/response/deviceResponse.ts -@@ -1,20 +1,20 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - --import { DeviceType } from '../../enums/deviceType'; -+import { DeviceType } from "../../enums/deviceType"; - - export class DeviceResponse extends BaseResponse { -- id: string; -- name: number; -- identifier: string; -- type: DeviceType; -- creationDate: string; -+ id: string; -+ name: number; -+ identifier: string; -+ type: DeviceType; -+ creationDate: string; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.name = this.getResponseProperty('Name'); -- this.identifier = this.getResponseProperty('Identifier'); -- this.type = this.getResponseProperty('Type'); -- this.creationDate = this.getResponseProperty('CreationDate'); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.name = this.getResponseProperty("Name"); -+ this.identifier = this.getResponseProperty("Identifier"); -+ this.type = this.getResponseProperty("Type"); -+ this.creationDate = this.getResponseProperty("CreationDate"); -+ } - } -diff --git a/jslib/common/src/models/response/domainsResponse.ts b/jslib/common/src/models/response/domainsResponse.ts -index 3e7727e9..3014c2f2 100644 ---- a/jslib/common/src/models/response/domainsResponse.ts -+++ b/jslib/common/src/models/response/domainsResponse.ts -@@ -1,18 +1,20 @@ --import { BaseResponse } from './baseResponse'; --import { GlobalDomainResponse } from './globalDomainResponse'; -+import { BaseResponse } from "./baseResponse"; -+import { GlobalDomainResponse } from "./globalDomainResponse"; - - export class DomainsResponse extends BaseResponse { -- equivalentDomains: string[][]; -- globalEquivalentDomains: GlobalDomainResponse[] = []; -+ equivalentDomains: string[][]; -+ globalEquivalentDomains: GlobalDomainResponse[] = []; - -- constructor(response: any) { -- super(response); -- this.equivalentDomains = this.getResponseProperty('EquivalentDomains'); -- const globalEquivalentDomains = this.getResponseProperty('GlobalEquivalentDomains'); -- if (globalEquivalentDomains != null) { -- this.globalEquivalentDomains = globalEquivalentDomains.map((d: any) => new GlobalDomainResponse(d)); -- } else { -- this.globalEquivalentDomains = []; -- } -+ constructor(response: any) { -+ super(response); -+ this.equivalentDomains = this.getResponseProperty("EquivalentDomains"); -+ const globalEquivalentDomains = this.getResponseProperty("GlobalEquivalentDomains"); -+ if (globalEquivalentDomains != null) { -+ this.globalEquivalentDomains = globalEquivalentDomains.map( -+ (d: any) => new GlobalDomainResponse(d) -+ ); -+ } else { -+ this.globalEquivalentDomains = []; - } -+ } - } -diff --git a/jslib/common/src/models/response/emergencyAccessResponse.ts b/jslib/common/src/models/response/emergencyAccessResponse.ts -index fbd60849..703543b2 100644 ---- a/jslib/common/src/models/response/emergencyAccessResponse.ts -+++ b/jslib/common/src/models/response/emergencyAccessResponse.ts -@@ -1,81 +1,81 @@ --import { EmergencyAccessStatusType } from '../../enums/emergencyAccessStatusType'; --import { EmergencyAccessType } from '../../enums/emergencyAccessType'; --import { KdfType } from '../../enums/kdfType'; --import { BaseResponse } from './baseResponse'; --import { CipherResponse } from './cipherResponse'; -+import { EmergencyAccessStatusType } from "../../enums/emergencyAccessStatusType"; -+import { EmergencyAccessType } from "../../enums/emergencyAccessType"; -+import { KdfType } from "../../enums/kdfType"; -+import { BaseResponse } from "./baseResponse"; -+import { CipherResponse } from "./cipherResponse"; - - export class EmergencyAccessGranteeDetailsResponse extends BaseResponse { -- id: string; -- granteeId: string; -- name: string; -- email: string; -- type: EmergencyAccessType; -- status: EmergencyAccessStatusType; -- waitTimeDays: number; -- creationDate: string; -+ id: string; -+ granteeId: string; -+ name: string; -+ email: string; -+ type: EmergencyAccessType; -+ status: EmergencyAccessStatusType; -+ waitTimeDays: number; -+ creationDate: string; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.granteeId = this.getResponseProperty('GranteeId'); -- this.name = this.getResponseProperty('Name'); -- this.email = this.getResponseProperty('Email'); -- this.type = this.getResponseProperty('Type'); -- this.status = this.getResponseProperty('Status'); -- this.waitTimeDays = this.getResponseProperty('WaitTimeDays'); -- this.creationDate = this.getResponseProperty('CreationDate'); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.granteeId = this.getResponseProperty("GranteeId"); -+ this.name = this.getResponseProperty("Name"); -+ this.email = this.getResponseProperty("Email"); -+ this.type = this.getResponseProperty("Type"); -+ this.status = this.getResponseProperty("Status"); -+ this.waitTimeDays = this.getResponseProperty("WaitTimeDays"); -+ this.creationDate = this.getResponseProperty("CreationDate"); -+ } - } - - export class EmergencyAccessGrantorDetailsResponse extends BaseResponse { -- id: string; -- grantorId: string; -- name: string; -- email: string; -- type: EmergencyAccessType; -- status: EmergencyAccessStatusType; -- waitTimeDays: number; -- creationDate: string; -+ id: string; -+ grantorId: string; -+ name: string; -+ email: string; -+ type: EmergencyAccessType; -+ status: EmergencyAccessStatusType; -+ waitTimeDays: number; -+ creationDate: string; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.grantorId = this.getResponseProperty('GrantorId'); -- this.name = this.getResponseProperty('Name'); -- this.email = this.getResponseProperty('Email'); -- this.type = this.getResponseProperty('Type'); -- this.status = this.getResponseProperty('Status'); -- this.waitTimeDays = this.getResponseProperty('WaitTimeDays'); -- this.creationDate = this.getResponseProperty('CreationDate'); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.grantorId = this.getResponseProperty("GrantorId"); -+ this.name = this.getResponseProperty("Name"); -+ this.email = this.getResponseProperty("Email"); -+ this.type = this.getResponseProperty("Type"); -+ this.status = this.getResponseProperty("Status"); -+ this.waitTimeDays = this.getResponseProperty("WaitTimeDays"); -+ this.creationDate = this.getResponseProperty("CreationDate"); -+ } - } - - export class EmergencyAccessTakeoverResponse extends BaseResponse { -- keyEncrypted: string; -- kdf: KdfType; -- kdfIterations: number; -+ keyEncrypted: string; -+ kdf: KdfType; -+ kdfIterations: number; - -- constructor(response: any) { -- super(response); -+ constructor(response: any) { -+ super(response); - -- this.keyEncrypted = this.getResponseProperty('KeyEncrypted'); -- this.kdf = this.getResponseProperty('Kdf'); -- this.kdfIterations = this.getResponseProperty('KdfIterations'); -- } -+ this.keyEncrypted = this.getResponseProperty("KeyEncrypted"); -+ this.kdf = this.getResponseProperty("Kdf"); -+ this.kdfIterations = this.getResponseProperty("KdfIterations"); -+ } - } - - export class EmergencyAccessViewResponse extends BaseResponse { -- keyEncrypted: string; -- ciphers: CipherResponse[] = []; -+ keyEncrypted: string; -+ ciphers: CipherResponse[] = []; - -- constructor(response: any) { -- super(response); -+ constructor(response: any) { -+ super(response); - -- this.keyEncrypted = this.getResponseProperty('KeyEncrypted'); -+ this.keyEncrypted = this.getResponseProperty("KeyEncrypted"); - -- const ciphers = this.getResponseProperty('Ciphers'); -- if (ciphers != null) { -- this.ciphers = ciphers.map((c: any) => new CipherResponse(c)); -- } -+ const ciphers = this.getResponseProperty("Ciphers"); -+ if (ciphers != null) { -+ this.ciphers = ciphers.map((c: any) => new CipherResponse(c)); - } -+ } - } -diff --git a/jslib/common/src/models/response/errorResponse.ts b/jslib/common/src/models/response/errorResponse.ts -index bac1ff12..6e19ace9 100644 ---- a/jslib/common/src/models/response/errorResponse.ts -+++ b/jslib/common/src/models/response/errorResponse.ts -@@ -1,72 +1,72 @@ --import { Utils } from '../../misc/utils'; -+import { Utils } from "../../misc/utils"; - --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class ErrorResponse extends BaseResponse { -- message: string; -- validationErrors: { [key: string]: string[]; }; -- statusCode: number; -- captchaRequired: boolean; -- captchaSiteKey: string; -+ message: string; -+ validationErrors: { [key: string]: string[] }; -+ statusCode: number; -+ captchaRequired: boolean; -+ captchaSiteKey: string; - -- constructor(response: any, status: number, identityResponse?: boolean) { -- super(response); -- let errorModel = null; -- if (response != null) { -- const responseErrorModel = this.getResponseProperty('ErrorModel'); -- if (responseErrorModel && identityResponse) { -- errorModel = responseErrorModel; -- } else { -- errorModel = response; -- } -- } -+ constructor(response: any, status: number, identityResponse?: boolean) { -+ super(response); -+ let errorModel = null; -+ if (response != null) { -+ const responseErrorModel = this.getResponseProperty("ErrorModel"); -+ if (responseErrorModel && identityResponse) { -+ errorModel = responseErrorModel; -+ } else { -+ errorModel = response; -+ } -+ } - -- if (errorModel) { -- this.message = this.getResponseProperty('Message', errorModel); -- this.validationErrors = this.getResponseProperty('ValidationErrors', errorModel); -- this.captchaSiteKey = this.validationErrors?.HCaptcha_SiteKey?.[0]; -- this.captchaRequired = !Utils.isNullOrWhitespace(this.captchaSiteKey); -- } else { -- if (status === 429) { -- this.message = 'Rate limit exceeded. Try again later.'; -- } -- } -- this.statusCode = status; -+ if (errorModel) { -+ this.message = this.getResponseProperty("Message", errorModel); -+ this.validationErrors = this.getResponseProperty("ValidationErrors", errorModel); -+ this.captchaSiteKey = this.validationErrors?.HCaptcha_SiteKey?.[0]; -+ this.captchaRequired = !Utils.isNullOrWhitespace(this.captchaSiteKey); -+ } else { -+ if (status === 429) { -+ this.message = "Rate limit exceeded. Try again later."; -+ } - } -+ this.statusCode = status; -+ } - -- getSingleMessage(): string { -- if (this.validationErrors == null) { -- return this.message; -- } -- for (const key in this.validationErrors) { -- if (!this.validationErrors.hasOwnProperty(key)) { -- continue; -- } -- if (this.validationErrors[key].length) { -- return this.validationErrors[key][0]; -- } -- } -- return this.message; -+ getSingleMessage(): string { -+ if (this.validationErrors == null) { -+ return this.message; -+ } -+ for (const key in this.validationErrors) { -+ if (!this.validationErrors.hasOwnProperty(key)) { -+ continue; -+ } -+ if (this.validationErrors[key].length) { -+ return this.validationErrors[key][0]; -+ } - } -+ return this.message; -+ } - -- getAllMessages(): string[] { -- const messages: string[] = []; -- if (this.validationErrors == null) { -- return messages; -- } -- for (const key in this.validationErrors) { -- if (!this.validationErrors.hasOwnProperty(key)) { -- continue; -- } -- this.validationErrors[key].forEach((item: string) => { -- let prefix = ''; -- if (key.indexOf('[') > -1 && key.indexOf(']') > -1) { -- const lastSep = key.lastIndexOf('.'); -- prefix = key.substr(0, lastSep > -1 ? lastSep : key.length) + ': '; -- } -- messages.push(prefix + item); -- }); -+ getAllMessages(): string[] { -+ const messages: string[] = []; -+ if (this.validationErrors == null) { -+ return messages; -+ } -+ for (const key in this.validationErrors) { -+ if (!this.validationErrors.hasOwnProperty(key)) { -+ continue; -+ } -+ this.validationErrors[key].forEach((item: string) => { -+ let prefix = ""; -+ if (key.indexOf("[") > -1 && key.indexOf("]") > -1) { -+ const lastSep = key.lastIndexOf("."); -+ prefix = key.substr(0, lastSep > -1 ? lastSep : key.length) + ": "; - } -- return messages; -+ messages.push(prefix + item); -+ }); - } -+ return messages; -+ } - } -diff --git a/jslib/common/src/models/response/eventResponse.ts b/jslib/common/src/models/response/eventResponse.ts -index ef7ab807..b56e172c 100644 ---- a/jslib/common/src/models/response/eventResponse.ts -+++ b/jslib/common/src/models/response/eventResponse.ts -@@ -1,41 +1,41 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - --import { DeviceType } from '../../enums/deviceType'; --import { EventType } from '../../enums/eventType'; -+import { DeviceType } from "../../enums/deviceType"; -+import { EventType } from "../../enums/eventType"; - - export class EventResponse extends BaseResponse { -- type: EventType; -- userId: string; -- organizationId: string; -- providerId: string; -- cipherId: string; -- collectionId: string; -- groupId: string; -- policyId: string; -- organizationUserId: string; -- providerUserId: string; -- providerOrganizationId: string; -- actingUserId: string; -- date: string; -- deviceType: DeviceType; -- ipAddress: string; -+ type: EventType; -+ userId: string; -+ organizationId: string; -+ providerId: string; -+ cipherId: string; -+ collectionId: string; -+ groupId: string; -+ policyId: string; -+ organizationUserId: string; -+ providerUserId: string; -+ providerOrganizationId: string; -+ actingUserId: string; -+ date: string; -+ deviceType: DeviceType; -+ ipAddress: string; - -- constructor(response: any) { -- super(response); -- this.type = this.getResponseProperty('Type'); -- this.userId = this.getResponseProperty('UserId'); -- this.organizationId = this.getResponseProperty('OrganizationId'); -- this.providerId = this.getResponseProperty('ProviderId'); -- this.cipherId = this.getResponseProperty('CipherId'); -- this.collectionId = this.getResponseProperty('CollectionId'); -- this.groupId = this.getResponseProperty('GroupId'); -- this.policyId = this.getResponseProperty('PolicyId'); -- this.organizationUserId = this.getResponseProperty('OrganizationUserId'); -- this.providerUserId = this.getResponseProperty('ProviderUserId'); -- this.providerOrganizationId = this.getResponseProperty('ProviderOrganizationId'); -- this.actingUserId = this.getResponseProperty('ActingUserId'); -- this.date = this.getResponseProperty('Date'); -- this.deviceType = this.getResponseProperty('DeviceType'); -- this.ipAddress = this.getResponseProperty('IpAddress'); -- } -+ constructor(response: any) { -+ super(response); -+ this.type = this.getResponseProperty("Type"); -+ this.userId = this.getResponseProperty("UserId"); -+ this.organizationId = this.getResponseProperty("OrganizationId"); -+ this.providerId = this.getResponseProperty("ProviderId"); -+ this.cipherId = this.getResponseProperty("CipherId"); -+ this.collectionId = this.getResponseProperty("CollectionId"); -+ this.groupId = this.getResponseProperty("GroupId"); -+ this.policyId = this.getResponseProperty("PolicyId"); -+ this.organizationUserId = this.getResponseProperty("OrganizationUserId"); -+ this.providerUserId = this.getResponseProperty("ProviderUserId"); -+ this.providerOrganizationId = this.getResponseProperty("ProviderOrganizationId"); -+ this.actingUserId = this.getResponseProperty("ActingUserId"); -+ this.date = this.getResponseProperty("Date"); -+ this.deviceType = this.getResponseProperty("DeviceType"); -+ this.ipAddress = this.getResponseProperty("IpAddress"); -+ } - } -diff --git a/jslib/common/src/models/response/folderResponse.ts b/jslib/common/src/models/response/folderResponse.ts -index 00b3d3d8..70d4897f 100644 ---- a/jslib/common/src/models/response/folderResponse.ts -+++ b/jslib/common/src/models/response/folderResponse.ts -@@ -1,14 +1,14 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class FolderResponse extends BaseResponse { -- id: string; -- name: string; -- revisionDate: string; -+ id: string; -+ name: string; -+ revisionDate: string; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.name = this.getResponseProperty('Name'); -- this.revisionDate = this.getResponseProperty('RevisionDate'); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.name = this.getResponseProperty("Name"); -+ this.revisionDate = this.getResponseProperty("RevisionDate"); -+ } - } -diff --git a/jslib/common/src/models/response/globalDomainResponse.ts b/jslib/common/src/models/response/globalDomainResponse.ts -index 861282a9..e3197e76 100644 ---- a/jslib/common/src/models/response/globalDomainResponse.ts -+++ b/jslib/common/src/models/response/globalDomainResponse.ts -@@ -1,14 +1,14 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class GlobalDomainResponse extends BaseResponse { -- type: number; -- domains: string[]; -- excluded: boolean; -+ type: number; -+ domains: string[]; -+ excluded: boolean; - -- constructor(response: any) { -- super(response); -- this.type = this.getResponseProperty('Type'); -- this.domains = this.getResponseProperty('Domains'); -- this.excluded = this.getResponseProperty('Excluded'); -- } -+ constructor(response: any) { -+ super(response); -+ this.type = this.getResponseProperty("Type"); -+ this.domains = this.getResponseProperty("Domains"); -+ this.excluded = this.getResponseProperty("Excluded"); -+ } - } -diff --git a/jslib/common/src/models/response/groupResponse.ts b/jslib/common/src/models/response/groupResponse.ts -index 2c3cf119..ab4b58b7 100644 ---- a/jslib/common/src/models/response/groupResponse.ts -+++ b/jslib/common/src/models/response/groupResponse.ts -@@ -1,31 +1,31 @@ --import { BaseResponse } from './baseResponse'; --import { SelectionReadOnlyResponse } from './selectionReadOnlyResponse'; -+import { BaseResponse } from "./baseResponse"; -+import { SelectionReadOnlyResponse } from "./selectionReadOnlyResponse"; - - export class GroupResponse extends BaseResponse { -- id: string; -- organizationId: string; -- name: string; -- accessAll: boolean; -- externalId: string; -+ id: string; -+ organizationId: string; -+ name: string; -+ accessAll: boolean; -+ externalId: string; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.organizationId = this.getResponseProperty('OrganizationId'); -- this.name = this.getResponseProperty('Name'); -- this.accessAll = this.getResponseProperty('AccessAll'); -- this.externalId = this.getResponseProperty('ExternalId'); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.organizationId = this.getResponseProperty("OrganizationId"); -+ this.name = this.getResponseProperty("Name"); -+ this.accessAll = this.getResponseProperty("AccessAll"); -+ this.externalId = this.getResponseProperty("ExternalId"); -+ } - } - - export class GroupDetailsResponse extends GroupResponse { -- collections: SelectionReadOnlyResponse[] = []; -+ collections: SelectionReadOnlyResponse[] = []; - -- constructor(response: any) { -- super(response); -- const collections = this.getResponseProperty('Collections'); -- if (collections != null) { -- this.collections = collections.map((c: any) => new SelectionReadOnlyResponse(c)); -- } -+ constructor(response: any) { -+ super(response); -+ const collections = this.getResponseProperty("Collections"); -+ if (collections != null) { -+ this.collections = collections.map((c: any) => new SelectionReadOnlyResponse(c)); - } -+ } - } -diff --git a/jslib/common/src/models/response/identityCaptchaResponse.ts b/jslib/common/src/models/response/identityCaptchaResponse.ts -index 082423e3..2537057e 100644 ---- a/jslib/common/src/models/response/identityCaptchaResponse.ts -+++ b/jslib/common/src/models/response/identityCaptchaResponse.ts -@@ -1,10 +1,10 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class IdentityCaptchaResponse extends BaseResponse { -- siteKey: string; -+ siteKey: string; - -- constructor(response: any) { -- super(response); -- this.siteKey = this.getResponseProperty('HCaptcha_SiteKey'); -- } -+ constructor(response: any) { -+ super(response); -+ this.siteKey = this.getResponseProperty("HCaptcha_SiteKey"); -+ } - } -diff --git a/jslib/common/src/models/response/identityTokenResponse.ts b/jslib/common/src/models/response/identityTokenResponse.ts -index c2283956..f17e144a 100644 ---- a/jslib/common/src/models/response/identityTokenResponse.ts -+++ b/jslib/common/src/models/response/identityTokenResponse.ts -@@ -1,38 +1,38 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - --import { KdfType } from '../../enums/kdfType'; -+import { KdfType } from "../../enums/kdfType"; - - export class IdentityTokenResponse extends BaseResponse { -- accessToken: string; -- expiresIn: number; -- refreshToken: string; -- tokenType: string; -+ accessToken: string; -+ expiresIn: number; -+ refreshToken: string; -+ tokenType: string; - -- resetMasterPassword: boolean; -- privateKey: string; -- key: string; -- twoFactorToken: string; -- kdf: KdfType; -- kdfIterations: number; -- forcePasswordReset: boolean; -- apiUseKeyConnector: boolean; -- keyConnectorUrl: string; -+ resetMasterPassword: boolean; -+ privateKey: string; -+ key: string; -+ twoFactorToken: string; -+ kdf: KdfType; -+ kdfIterations: number; -+ forcePasswordReset: boolean; -+ apiUseKeyConnector: boolean; -+ keyConnectorUrl: string; - -- constructor(response: any) { -- super(response); -- this.accessToken = response.access_token; -- this.expiresIn = response.expires_in; -- this.refreshToken = response.refresh_token; -- this.tokenType = response.token_type; -+ constructor(response: any) { -+ super(response); -+ this.accessToken = response.access_token; -+ this.expiresIn = response.expires_in; -+ this.refreshToken = response.refresh_token; -+ this.tokenType = response.token_type; - -- this.resetMasterPassword = this.getResponseProperty('ResetMasterPassword'); -- this.privateKey = this.getResponseProperty('PrivateKey'); -- this.key = this.getResponseProperty('Key'); -- this.twoFactorToken = this.getResponseProperty('TwoFactorToken'); -- this.kdf = this.getResponseProperty('Kdf'); -- this.kdfIterations = this.getResponseProperty('KdfIterations'); -- this.forcePasswordReset = this.getResponseProperty('ForcePasswordReset'); -- this.apiUseKeyConnector = this.getResponseProperty('ApiUseKeyConnector'); -- this.keyConnectorUrl = this.getResponseProperty('KeyConnectorUrl'); -- } -+ this.resetMasterPassword = this.getResponseProperty("ResetMasterPassword"); -+ this.privateKey = this.getResponseProperty("PrivateKey"); -+ this.key = this.getResponseProperty("Key"); -+ this.twoFactorToken = this.getResponseProperty("TwoFactorToken"); -+ this.kdf = this.getResponseProperty("Kdf"); -+ this.kdfIterations = this.getResponseProperty("KdfIterations"); -+ this.forcePasswordReset = this.getResponseProperty("ForcePasswordReset"); -+ this.apiUseKeyConnector = this.getResponseProperty("ApiUseKeyConnector"); -+ this.keyConnectorUrl = this.getResponseProperty("KeyConnectorUrl"); -+ } - } -diff --git a/jslib/common/src/models/response/identityTwoFactorResponse.ts b/jslib/common/src/models/response/identityTwoFactorResponse.ts -index 92886a40..bb826537 100644 ---- a/jslib/common/src/models/response/identityTwoFactorResponse.ts -+++ b/jslib/common/src/models/response/identityTwoFactorResponse.ts -@@ -1,23 +1,23 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - --import { TwoFactorProviderType } from '../../enums/twoFactorProviderType'; -+import { TwoFactorProviderType } from "../../enums/twoFactorProviderType"; - - export class IdentityTwoFactorResponse extends BaseResponse { -- twoFactorProviders: TwoFactorProviderType[]; -- twoFactorProviders2 = new Map(); -- captchaToken: string; -+ twoFactorProviders: TwoFactorProviderType[]; -+ twoFactorProviders2 = new Map(); -+ captchaToken: string; - -- constructor(response: any) { -- super(response); -- this.captchaToken = this.getResponseProperty('CaptchaBypassToken'); -- this.twoFactorProviders = this.getResponseProperty('TwoFactorProviders'); -- const twoFactorProviders2 = this.getResponseProperty('TwoFactorProviders2'); -- if (twoFactorProviders2 != null) { -- for (const prop in twoFactorProviders2) { -- if (twoFactorProviders2.hasOwnProperty(prop)) { -- this.twoFactorProviders2.set(parseInt(prop, null), twoFactorProviders2[prop]); -- } -- } -+ constructor(response: any) { -+ super(response); -+ this.captchaToken = this.getResponseProperty("CaptchaBypassToken"); -+ this.twoFactorProviders = this.getResponseProperty("TwoFactorProviders"); -+ const twoFactorProviders2 = this.getResponseProperty("TwoFactorProviders2"); -+ if (twoFactorProviders2 != null) { -+ for (const prop in twoFactorProviders2) { -+ if (twoFactorProviders2.hasOwnProperty(prop)) { -+ this.twoFactorProviders2.set(parseInt(prop, null), twoFactorProviders2[prop]); - } -+ } - } -+ } - } -diff --git a/jslib/common/src/models/response/keyConnectorUserKeyResponse.ts b/jslib/common/src/models/response/keyConnectorUserKeyResponse.ts -index 4da60be3..5183397b 100644 ---- a/jslib/common/src/models/response/keyConnectorUserKeyResponse.ts -+++ b/jslib/common/src/models/response/keyConnectorUserKeyResponse.ts -@@ -1,10 +1,10 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class KeyConnectorUserKeyResponse extends BaseResponse { -- key: string; -+ key: string; - -- constructor(response: any) { -- super(response); -- this.key = this.getResponseProperty('Key'); -- } -+ constructor(response: any) { -+ super(response); -+ this.key = this.getResponseProperty("Key"); -+ } - } -diff --git a/jslib/common/src/models/response/keysResponse.ts b/jslib/common/src/models/response/keysResponse.ts -index 405edcdc..e1e8e5db 100644 ---- a/jslib/common/src/models/response/keysResponse.ts -+++ b/jslib/common/src/models/response/keysResponse.ts -@@ -1,12 +1,12 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class KeysResponse extends BaseResponse { -- privateKey: string; -- publicKey: string; -+ privateKey: string; -+ publicKey: string; - -- constructor(response: any) { -- super(response); -- this.privateKey = this.getResponseProperty('PrivateKey'); -- this.publicKey = this.getResponseProperty('PublicKey'); -- } -+ constructor(response: any) { -+ super(response); -+ this.privateKey = this.getResponseProperty("PrivateKey"); -+ this.publicKey = this.getResponseProperty("PublicKey"); -+ } - } -diff --git a/jslib/common/src/models/response/listResponse.ts b/jslib/common/src/models/response/listResponse.ts -index 3b15a3f5..e37c6697 100644 ---- a/jslib/common/src/models/response/listResponse.ts -+++ b/jslib/common/src/models/response/listResponse.ts -@@ -1,13 +1,13 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class ListResponse extends BaseResponse { -- data: T[]; -- continuationToken: string; -+ data: T[]; -+ continuationToken: string; - -- constructor(response: any, t: new (dataResponse: any) => T) { -- super(response); -- const data = this.getResponseProperty('Data'); -- this.data = data == null ? [] : data.map((dr: any) => new t(dr)); -- this.continuationToken = this.getResponseProperty('ContinuationToken'); -- } -+ constructor(response: any, t: new (dataResponse: any) => T) { -+ super(response); -+ const data = this.getResponseProperty("Data"); -+ this.data = data == null ? [] : data.map((dr: any) => new t(dr)); -+ this.continuationToken = this.getResponseProperty("ContinuationToken"); -+ } - } -diff --git a/jslib/common/src/models/response/notificationResponse.ts b/jslib/common/src/models/response/notificationResponse.ts -index b60f38a6..79bbdb0e 100644 ---- a/jslib/common/src/models/response/notificationResponse.ts -+++ b/jslib/common/src/models/response/notificationResponse.ts -@@ -1,97 +1,97 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - --import { NotificationType } from '../../enums/notificationType'; -+import { NotificationType } from "../../enums/notificationType"; - - export class NotificationResponse extends BaseResponse { -- contextId: string; -- type: NotificationType; -- payload: any; -+ contextId: string; -+ type: NotificationType; -+ payload: any; - -- constructor(response: any) { -- super(response); -- this.contextId = this.getResponseProperty('ContextId'); -- this.type = this.getResponseProperty('Type'); -+ constructor(response: any) { -+ super(response); -+ this.contextId = this.getResponseProperty("ContextId"); -+ this.type = this.getResponseProperty("Type"); - -- const payload = this.getResponseProperty('Payload'); -- switch (this.type) { -- case NotificationType.SyncCipherCreate: -- case NotificationType.SyncCipherDelete: -- case NotificationType.SyncCipherUpdate: -- case NotificationType.SyncLoginDelete: -- this.payload = new SyncCipherNotification(payload); -- break; -- case NotificationType.SyncFolderCreate: -- case NotificationType.SyncFolderDelete: -- case NotificationType.SyncFolderUpdate: -- this.payload = new SyncFolderNotification(payload); -- break; -- case NotificationType.SyncVault: -- case NotificationType.SyncCiphers: -- case NotificationType.SyncOrgKeys: -- case NotificationType.SyncSettings: -- case NotificationType.LogOut: -- this.payload = new UserNotification(payload); -- break; -- case NotificationType.SyncSendCreate: -- case NotificationType.SyncSendUpdate: -- case NotificationType.SyncSendDelete: -- this.payload = new SyncSendNotification(payload); -- default: -- break; -- } -+ const payload = this.getResponseProperty("Payload"); -+ switch (this.type) { -+ case NotificationType.SyncCipherCreate: -+ case NotificationType.SyncCipherDelete: -+ case NotificationType.SyncCipherUpdate: -+ case NotificationType.SyncLoginDelete: -+ this.payload = new SyncCipherNotification(payload); -+ break; -+ case NotificationType.SyncFolderCreate: -+ case NotificationType.SyncFolderDelete: -+ case NotificationType.SyncFolderUpdate: -+ this.payload = new SyncFolderNotification(payload); -+ break; -+ case NotificationType.SyncVault: -+ case NotificationType.SyncCiphers: -+ case NotificationType.SyncOrgKeys: -+ case NotificationType.SyncSettings: -+ case NotificationType.LogOut: -+ this.payload = new UserNotification(payload); -+ break; -+ case NotificationType.SyncSendCreate: -+ case NotificationType.SyncSendUpdate: -+ case NotificationType.SyncSendDelete: -+ this.payload = new SyncSendNotification(payload); -+ default: -+ break; - } -+ } - } - - export class SyncCipherNotification extends BaseResponse { -- id: string; -- userId: string; -- organizationId: string; -- collectionIds: string[]; -- revisionDate: Date; -+ id: string; -+ userId: string; -+ organizationId: string; -+ collectionIds: string[]; -+ revisionDate: Date; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.userId = this.getResponseProperty('UserId'); -- this.organizationId = this.getResponseProperty('OrganizationId'); -- this.collectionIds = this.getResponseProperty('CollectionIds'); -- this.revisionDate = new Date(this.getResponseProperty('RevisionDate')); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.userId = this.getResponseProperty("UserId"); -+ this.organizationId = this.getResponseProperty("OrganizationId"); -+ this.collectionIds = this.getResponseProperty("CollectionIds"); -+ this.revisionDate = new Date(this.getResponseProperty("RevisionDate")); -+ } - } - - export class SyncFolderNotification extends BaseResponse { -- id: string; -- userId: string; -- revisionDate: Date; -+ id: string; -+ userId: string; -+ revisionDate: Date; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.userId = this.getResponseProperty('UserId'); -- this.revisionDate = new Date(this.getResponseProperty('RevisionDate')); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.userId = this.getResponseProperty("UserId"); -+ this.revisionDate = new Date(this.getResponseProperty("RevisionDate")); -+ } - } - - export class UserNotification extends BaseResponse { -- userId: string; -- date: Date; -+ userId: string; -+ date: Date; - -- constructor(response: any) { -- super(response); -- this.userId = this.getResponseProperty('UserId'); -- this.date = new Date(this.getResponseProperty('Date')); -- } -+ constructor(response: any) { -+ super(response); -+ this.userId = this.getResponseProperty("UserId"); -+ this.date = new Date(this.getResponseProperty("Date")); -+ } - } - - export class SyncSendNotification extends BaseResponse { -- id: string; -- userId: string; -- revisionDate: Date; -+ id: string; -+ userId: string; -+ revisionDate: Date; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.userId = this.getResponseProperty('UserId'); -- this.revisionDate = new Date(this.getResponseProperty('RevisionDate')); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.userId = this.getResponseProperty("UserId"); -+ this.revisionDate = new Date(this.getResponseProperty("RevisionDate")); -+ } - } -diff --git a/jslib/common/src/models/response/organization/organizationSsoResponse.ts b/jslib/common/src/models/response/organization/organizationSsoResponse.ts -index 46709d61..71432912 100644 ---- a/jslib/common/src/models/response/organization/organizationSsoResponse.ts -+++ b/jslib/common/src/models/response/organization/organizationSsoResponse.ts -@@ -1,32 +1,32 @@ --import { SsoConfigApi } from '../../api/ssoConfigApi'; --import { BaseResponse } from '../baseResponse'; -+import { SsoConfigApi } from "../../api/ssoConfigApi"; -+import { BaseResponse } from "../baseResponse"; - - export class OrganizationSsoResponse extends BaseResponse { -- enabled: boolean; -- data: SsoConfigApi; -- urls: SsoUrls; -+ enabled: boolean; -+ data: SsoConfigApi; -+ urls: SsoUrls; - -- constructor(response: any) { -- super(response); -- this.enabled = this.getResponseProperty('Enabled'); -- this.data = new SsoConfigApi(this.getResponseProperty('Data')); -- this.urls = new SsoUrls(this.getResponseProperty('Urls')); -- } -+ constructor(response: any) { -+ super(response); -+ this.enabled = this.getResponseProperty("Enabled"); -+ this.data = new SsoConfigApi(this.getResponseProperty("Data")); -+ this.urls = new SsoUrls(this.getResponseProperty("Urls")); -+ } - } - - class SsoUrls extends BaseResponse { -- callbackPath: string; -- signedOutCallbackPath: string; -- spEntityId: string; -- spMetadataUrl: string; -- spAcsUrl: string; -+ callbackPath: string; -+ signedOutCallbackPath: string; -+ spEntityId: string; -+ spMetadataUrl: string; -+ spAcsUrl: string; - -- constructor(response: any) { -- super(response); -- this.callbackPath = this.getResponseProperty('CallbackPath'); -- this.signedOutCallbackPath = this.getResponseProperty('SignedOutCallbackPath'); -- this.spEntityId = this.getResponseProperty('SpEntityId'); -- this.spMetadataUrl = this.getResponseProperty('SpMetadataUrl'); -- this.spAcsUrl = this.getResponseProperty('SpAcsUrl'); -- } -+ constructor(response: any) { -+ super(response); -+ this.callbackPath = this.getResponseProperty("CallbackPath"); -+ this.signedOutCallbackPath = this.getResponseProperty("SignedOutCallbackPath"); -+ this.spEntityId = this.getResponseProperty("SpEntityId"); -+ this.spMetadataUrl = this.getResponseProperty("SpMetadataUrl"); -+ this.spAcsUrl = this.getResponseProperty("SpAcsUrl"); -+ } - } -diff --git a/jslib/common/src/models/response/organizationAutoEnrollStatusResponse.ts b/jslib/common/src/models/response/organizationAutoEnrollStatusResponse.ts -index 1f9f77b5..d960463e 100644 ---- a/jslib/common/src/models/response/organizationAutoEnrollStatusResponse.ts -+++ b/jslib/common/src/models/response/organizationAutoEnrollStatusResponse.ts -@@ -1,12 +1,12 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class OrganizationAutoEnrollStatusResponse extends BaseResponse { -- id: string; -- resetPasswordEnabled: boolean; -+ id: string; -+ resetPasswordEnabled: boolean; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.resetPasswordEnabled = this.getResponseProperty('ResetPasswordEnabled'); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.resetPasswordEnabled = this.getResponseProperty("ResetPasswordEnabled"); -+ } - } -diff --git a/jslib/common/src/models/response/organizationKeysResponse.ts b/jslib/common/src/models/response/organizationKeysResponse.ts -index a5ed77d3..3d5c0d29 100644 ---- a/jslib/common/src/models/response/organizationKeysResponse.ts -+++ b/jslib/common/src/models/response/organizationKeysResponse.ts -@@ -1,7 +1,7 @@ --import { KeysResponse } from './keysResponse'; -+import { KeysResponse } from "./keysResponse"; - - export class OrganizationKeysResponse extends KeysResponse { -- constructor(response: any) { -- super(response); -- } -+ constructor(response: any) { -+ super(response); -+ } - } -diff --git a/jslib/common/src/models/response/organizationResponse.ts b/jslib/common/src/models/response/organizationResponse.ts -index 0965bea9..c1938253 100644 ---- a/jslib/common/src/models/response/organizationResponse.ts -+++ b/jslib/common/src/models/response/organizationResponse.ts -@@ -1,60 +1,60 @@ --import { BaseResponse } from './baseResponse'; --import { PlanResponse } from './planResponse'; -+import { BaseResponse } from "./baseResponse"; -+import { PlanResponse } from "./planResponse"; - --import { PlanType } from '../../enums/planType'; -+import { PlanType } from "../../enums/planType"; - - export class OrganizationResponse extends BaseResponse { -- id: string; -- identifier: string; -- name: string; -- businessName: string; -- businessAddress1: string; -- businessAddress2: string; -- businessAddress3: string; -- businessCountry: string; -- businessTaxNumber: string; -- billingEmail: string; -- plan: PlanResponse; -- planType: PlanType; -- seats: number; -- maxAutoscaleSeats: number; -- maxCollections: number; -- maxStorageGb: number; -- useGroups: boolean; -- useDirectory: boolean; -- useEvents: boolean; -- useTotp: boolean; -- use2fa: boolean; -- useApi: boolean; -- useResetPassword: boolean; -- hasPublicAndPrivateKeys: boolean; -+ id: string; -+ identifier: string; -+ name: string; -+ businessName: string; -+ businessAddress1: string; -+ businessAddress2: string; -+ businessAddress3: string; -+ businessCountry: string; -+ businessTaxNumber: string; -+ billingEmail: string; -+ plan: PlanResponse; -+ planType: PlanType; -+ seats: number; -+ maxAutoscaleSeats: number; -+ maxCollections: number; -+ maxStorageGb: number; -+ useGroups: boolean; -+ useDirectory: boolean; -+ useEvents: boolean; -+ useTotp: boolean; -+ use2fa: boolean; -+ useApi: boolean; -+ useResetPassword: boolean; -+ hasPublicAndPrivateKeys: boolean; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.identifier = this.getResponseProperty('Identifier'); -- this.name = this.getResponseProperty('Name'); -- this.businessName = this.getResponseProperty('BusinessName'); -- this.businessAddress1 = this.getResponseProperty('BusinessAddress1'); -- this.businessAddress2 = this.getResponseProperty('BusinessAddress2'); -- this.businessAddress3 = this.getResponseProperty('BusinessAddress3'); -- this.businessCountry = this.getResponseProperty('BusinessCountry'); -- this.businessTaxNumber = this.getResponseProperty('BusinessTaxNumber'); -- this.billingEmail = this.getResponseProperty('BillingEmail'); -- const plan = this.getResponseProperty('Plan'); -- this.plan = plan == null ? null : new PlanResponse(plan); -- this.planType = this.getResponseProperty('PlanType'); -- this.seats = this.getResponseProperty('Seats'); -- this.maxAutoscaleSeats = this.getResponseProperty('MaxAutoscaleSeats'); -- this.maxCollections = this.getResponseProperty('MaxCollections'); -- this.maxStorageGb = this.getResponseProperty('MaxStorageGb'); -- this.useGroups = this.getResponseProperty('UseGroups'); -- this.useDirectory = this.getResponseProperty('UseDirectory'); -- this.useEvents = this.getResponseProperty('UseEvents'); -- this.useTotp = this.getResponseProperty('UseTotp'); -- this.use2fa = this.getResponseProperty('Use2fa'); -- this.useApi = this.getResponseProperty('UseApi'); -- this.useResetPassword = this.getResponseProperty('UseResetPassword'); -- this.hasPublicAndPrivateKeys = this.getResponseProperty('HasPublicAndPrivateKeys'); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.identifier = this.getResponseProperty("Identifier"); -+ this.name = this.getResponseProperty("Name"); -+ this.businessName = this.getResponseProperty("BusinessName"); -+ this.businessAddress1 = this.getResponseProperty("BusinessAddress1"); -+ this.businessAddress2 = this.getResponseProperty("BusinessAddress2"); -+ this.businessAddress3 = this.getResponseProperty("BusinessAddress3"); -+ this.businessCountry = this.getResponseProperty("BusinessCountry"); -+ this.businessTaxNumber = this.getResponseProperty("BusinessTaxNumber"); -+ this.billingEmail = this.getResponseProperty("BillingEmail"); -+ const plan = this.getResponseProperty("Plan"); -+ this.plan = plan == null ? null : new PlanResponse(plan); -+ this.planType = this.getResponseProperty("PlanType"); -+ this.seats = this.getResponseProperty("Seats"); -+ this.maxAutoscaleSeats = this.getResponseProperty("MaxAutoscaleSeats"); -+ this.maxCollections = this.getResponseProperty("MaxCollections"); -+ this.maxStorageGb = this.getResponseProperty("MaxStorageGb"); -+ this.useGroups = this.getResponseProperty("UseGroups"); -+ this.useDirectory = this.getResponseProperty("UseDirectory"); -+ this.useEvents = this.getResponseProperty("UseEvents"); -+ this.useTotp = this.getResponseProperty("UseTotp"); -+ this.use2fa = this.getResponseProperty("Use2fa"); -+ this.useApi = this.getResponseProperty("UseApi"); -+ this.useResetPassword = this.getResponseProperty("UseResetPassword"); -+ this.hasPublicAndPrivateKeys = this.getResponseProperty("HasPublicAndPrivateKeys"); -+ } - } -diff --git a/jslib/common/src/models/response/organizationSubscriptionResponse.ts b/jslib/common/src/models/response/organizationSubscriptionResponse.ts -index 9fbd16d5..1985dc6e 100644 ---- a/jslib/common/src/models/response/organizationSubscriptionResponse.ts -+++ b/jslib/common/src/models/response/organizationSubscriptionResponse.ts -@@ -1,25 +1,27 @@ --import { OrganizationResponse } from './organizationResponse'; -+import { OrganizationResponse } from "./organizationResponse"; - import { -- BillingSubscriptionResponse, -- BillingSubscriptionUpcomingInvoiceResponse, --} from './subscriptionResponse'; -+ BillingSubscriptionResponse, -+ BillingSubscriptionUpcomingInvoiceResponse, -+} from "./subscriptionResponse"; - - export class OrganizationSubscriptionResponse extends OrganizationResponse { -- storageName: string; -- storageGb: number; -- subscription: BillingSubscriptionResponse; -- upcomingInvoice: BillingSubscriptionUpcomingInvoiceResponse; -- expiration: string; -+ storageName: string; -+ storageGb: number; -+ subscription: BillingSubscriptionResponse; -+ upcomingInvoice: BillingSubscriptionUpcomingInvoiceResponse; -+ expiration: string; - -- constructor(response: any) { -- super(response); -- this.storageName = this.getResponseProperty('StorageName'); -- this.storageGb = this.getResponseProperty('StorageGb'); -- const subscription = this.getResponseProperty('Subscription'); -- this.subscription = subscription == null ? null : new BillingSubscriptionResponse(subscription); -- const upcomingInvoice = this.getResponseProperty('UpcomingInvoice'); -- this.upcomingInvoice = upcomingInvoice == null ? null : -- new BillingSubscriptionUpcomingInvoiceResponse(upcomingInvoice); -- this.expiration = this.getResponseProperty('Expiration'); -- } -+ constructor(response: any) { -+ super(response); -+ this.storageName = this.getResponseProperty("StorageName"); -+ this.storageGb = this.getResponseProperty("StorageGb"); -+ const subscription = this.getResponseProperty("Subscription"); -+ this.subscription = subscription == null ? null : new BillingSubscriptionResponse(subscription); -+ const upcomingInvoice = this.getResponseProperty("UpcomingInvoice"); -+ this.upcomingInvoice = -+ upcomingInvoice == null -+ ? null -+ : new BillingSubscriptionUpcomingInvoiceResponse(upcomingInvoice); -+ this.expiration = this.getResponseProperty("Expiration"); -+ } - } -diff --git a/jslib/common/src/models/response/organizationUserBulkPublicKeyResponse.ts b/jslib/common/src/models/response/organizationUserBulkPublicKeyResponse.ts -index f5891c10..1b77e036 100644 ---- a/jslib/common/src/models/response/organizationUserBulkPublicKeyResponse.ts -+++ b/jslib/common/src/models/response/organizationUserBulkPublicKeyResponse.ts -@@ -1,14 +1,14 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class OrganizationUserBulkPublicKeyResponse extends BaseResponse { -- id: string; -- userId: string; -- key: string; -+ id: string; -+ userId: string; -+ key: string; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.userId = this.getResponseProperty('UserId'); -- this.key = this.getResponseProperty('Key'); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.userId = this.getResponseProperty("UserId"); -+ this.key = this.getResponseProperty("Key"); -+ } - } -diff --git a/jslib/common/src/models/response/organizationUserBulkResponse.ts b/jslib/common/src/models/response/organizationUserBulkResponse.ts -index eeee2c8f..15c6f371 100644 ---- a/jslib/common/src/models/response/organizationUserBulkResponse.ts -+++ b/jslib/common/src/models/response/organizationUserBulkResponse.ts -@@ -1,12 +1,12 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class OrganizationUserBulkResponse extends BaseResponse { -- id: string; -- error: string; -+ id: string; -+ error: string; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.error = this.getResponseProperty('Error'); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.error = this.getResponseProperty("Error"); -+ } - } -diff --git a/jslib/common/src/models/response/organizationUserResponse.ts b/jslib/common/src/models/response/organizationUserResponse.ts -index 3b155f01..493d2661 100644 ---- a/jslib/common/src/models/response/organizationUserResponse.ts -+++ b/jslib/common/src/models/response/organizationUserResponse.ts -@@ -1,71 +1,71 @@ --import { BaseResponse } from './baseResponse'; --import { SelectionReadOnlyResponse } from './selectionReadOnlyResponse'; -+import { BaseResponse } from "./baseResponse"; -+import { SelectionReadOnlyResponse } from "./selectionReadOnlyResponse"; - --import { PermissionsApi } from '../api/permissionsApi'; -+import { PermissionsApi } from "../api/permissionsApi"; - --import { KdfType } from '../../enums/kdfType'; --import { OrganizationUserStatusType } from '../../enums/organizationUserStatusType'; --import { OrganizationUserType } from '../../enums/organizationUserType'; -+import { KdfType } from "../../enums/kdfType"; -+import { OrganizationUserStatusType } from "../../enums/organizationUserStatusType"; -+import { OrganizationUserType } from "../../enums/organizationUserType"; - - export class OrganizationUserResponse extends BaseResponse { -- id: string; -- userId: string; -- type: OrganizationUserType; -- status: OrganizationUserStatusType; -- accessAll: boolean; -- permissions: PermissionsApi; -- resetPasswordEnrolled: boolean; -+ id: string; -+ userId: string; -+ type: OrganizationUserType; -+ status: OrganizationUserStatusType; -+ accessAll: boolean; -+ permissions: PermissionsApi; -+ resetPasswordEnrolled: boolean; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.userId = this.getResponseProperty('UserId'); -- this.type = this.getResponseProperty('Type'); -- this.status = this.getResponseProperty('Status'); -- this.permissions = new PermissionsApi(this.getResponseProperty('Permissions')); -- this.accessAll = this.getResponseProperty('AccessAll'); -- this.resetPasswordEnrolled = this.getResponseProperty('ResetPasswordEnrolled'); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.userId = this.getResponseProperty("UserId"); -+ this.type = this.getResponseProperty("Type"); -+ this.status = this.getResponseProperty("Status"); -+ this.permissions = new PermissionsApi(this.getResponseProperty("Permissions")); -+ this.accessAll = this.getResponseProperty("AccessAll"); -+ this.resetPasswordEnrolled = this.getResponseProperty("ResetPasswordEnrolled"); -+ } - } - - export class OrganizationUserUserDetailsResponse extends OrganizationUserResponse { -- name: string; -- email: string; -- twoFactorEnabled: boolean; -- usesKeyConnector: boolean; -+ name: string; -+ email: string; -+ twoFactorEnabled: boolean; -+ usesKeyConnector: boolean; - -- constructor(response: any) { -- super(response); -- this.name = this.getResponseProperty('Name'); -- this.email = this.getResponseProperty('Email'); -- this.twoFactorEnabled = this.getResponseProperty('TwoFactorEnabled'); -- this.usesKeyConnector = this.getResponseProperty('UsesKeyConnector') ?? false; -- } -+ constructor(response: any) { -+ super(response); -+ this.name = this.getResponseProperty("Name"); -+ this.email = this.getResponseProperty("Email"); -+ this.twoFactorEnabled = this.getResponseProperty("TwoFactorEnabled"); -+ this.usesKeyConnector = this.getResponseProperty("UsesKeyConnector") ?? false; -+ } - } - - export class OrganizationUserDetailsResponse extends OrganizationUserResponse { -- collections: SelectionReadOnlyResponse[] = []; -+ collections: SelectionReadOnlyResponse[] = []; - -- constructor(response: any) { -- super(response); -- const collections = this.getResponseProperty('Collections'); -- if (collections != null) { -- this.collections = collections.map((c: any) => new SelectionReadOnlyResponse(c)); -- } -+ constructor(response: any) { -+ super(response); -+ const collections = this.getResponseProperty("Collections"); -+ if (collections != null) { -+ this.collections = collections.map((c: any) => new SelectionReadOnlyResponse(c)); - } -+ } - } - - export class OrganizationUserResetPasswordDetailsReponse extends BaseResponse { -- kdf: KdfType; -- kdfIterations: number; -- resetPasswordKey: string; -- encryptedPrivateKey: string; -+ kdf: KdfType; -+ kdfIterations: number; -+ resetPasswordKey: string; -+ encryptedPrivateKey: string; - -- constructor(response: any) { -- super(response); -- this.kdf = this.getResponseProperty('Kdf'); -- this.kdfIterations = this.getResponseProperty('KdfIterations'); -- this.resetPasswordKey = this.getResponseProperty('ResetPasswordKey'); -- this.encryptedPrivateKey = this.getResponseProperty('EncryptedPrivateKey'); -- } -+ constructor(response: any) { -+ super(response); -+ this.kdf = this.getResponseProperty("Kdf"); -+ this.kdfIterations = this.getResponseProperty("KdfIterations"); -+ this.resetPasswordKey = this.getResponseProperty("ResetPasswordKey"); -+ this.encryptedPrivateKey = this.getResponseProperty("EncryptedPrivateKey"); -+ } - } -diff --git a/jslib/common/src/models/response/passwordHistoryResponse.ts b/jslib/common/src/models/response/passwordHistoryResponse.ts -index 805a5668..f3a8a133 100644 ---- a/jslib/common/src/models/response/passwordHistoryResponse.ts -+++ b/jslib/common/src/models/response/passwordHistoryResponse.ts -@@ -1,12 +1,12 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class PasswordHistoryResponse extends BaseResponse { -- password: string; -- lastUsedDate: string; -+ password: string; -+ lastUsedDate: string; - -- constructor(response: any) { -- super(response); -- this.password = this.getResponseProperty('Password'); -- this.lastUsedDate = this.getResponseProperty('LastUsedDate'); -- } -+ constructor(response: any) { -+ super(response); -+ this.password = this.getResponseProperty("Password"); -+ this.lastUsedDate = this.getResponseProperty("LastUsedDate"); -+ } - } -diff --git a/jslib/common/src/models/response/paymentResponse.ts b/jslib/common/src/models/response/paymentResponse.ts -index f4cc6745..ec4977cd 100644 ---- a/jslib/common/src/models/response/paymentResponse.ts -+++ b/jslib/common/src/models/response/paymentResponse.ts -@@ -1,18 +1,18 @@ --import { BaseResponse } from './baseResponse'; --import { ProfileResponse } from './profileResponse'; -+import { BaseResponse } from "./baseResponse"; -+import { ProfileResponse } from "./profileResponse"; - - export class PaymentResponse extends BaseResponse { -- userProfile: ProfileResponse; -- paymentIntentClientSecret: string; -- success: boolean; -+ userProfile: ProfileResponse; -+ paymentIntentClientSecret: string; -+ success: boolean; - -- constructor(response: any) { -- super(response); -- const userProfile = this.getResponseProperty('UserProfile'); -- if (userProfile != null) { -- this.userProfile = new ProfileResponse(userProfile); -- } -- this.paymentIntentClientSecret = this.getResponseProperty('PaymentIntentClientSecret'); -- this.success = this.getResponseProperty('Success'); -+ constructor(response: any) { -+ super(response); -+ const userProfile = this.getResponseProperty("UserProfile"); -+ if (userProfile != null) { -+ this.userProfile = new ProfileResponse(userProfile); - } -+ this.paymentIntentClientSecret = this.getResponseProperty("PaymentIntentClientSecret"); -+ this.success = this.getResponseProperty("Success"); -+ } - } -diff --git a/jslib/common/src/models/response/planResponse.ts b/jslib/common/src/models/response/planResponse.ts -index cd201db9..04652542 100644 ---- a/jslib/common/src/models/response/planResponse.ts -+++ b/jslib/common/src/models/response/planResponse.ts -@@ -1,95 +1,95 @@ --import { PlanType } from '../../enums/planType'; --import { ProductType } from '../../enums/productType'; -+import { PlanType } from "../../enums/planType"; -+import { ProductType } from "../../enums/productType"; - --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class PlanResponse extends BaseResponse { -- type: PlanType; -- product: ProductType; -- name: string; -- isAnnual: boolean; -- nameLocalizationKey: string; -- descriptionLocalizationKey: string; -- canBeUsedByBusiness: boolean; -- baseSeats: number; -- baseStorageGb: number; -- maxCollections: number; -- maxUsers: number; -+ type: PlanType; -+ product: ProductType; -+ name: string; -+ isAnnual: boolean; -+ nameLocalizationKey: string; -+ descriptionLocalizationKey: string; -+ canBeUsedByBusiness: boolean; -+ baseSeats: number; -+ baseStorageGb: number; -+ maxCollections: number; -+ maxUsers: number; - -- hasAdditionalSeatsOption: boolean; -- maxAdditionalSeats: number; -- hasAdditionalStorageOption: boolean; -- maxAdditionalStorage: number; -- hasPremiumAccessOption: boolean; -- trialPeriodDays: number; -+ hasAdditionalSeatsOption: boolean; -+ maxAdditionalSeats: number; -+ hasAdditionalStorageOption: boolean; -+ maxAdditionalStorage: number; -+ hasPremiumAccessOption: boolean; -+ trialPeriodDays: number; - -- hasSelfHost: boolean; -- hasPolicies: boolean; -- hasGroups: boolean; -- hasDirectory: boolean; -- hasEvents: boolean; -- hasTotp: boolean; -- has2fa: boolean; -- hasApi: boolean; -- hasSso: boolean; -- hasResetPassword: boolean; -- usersGetPremium: boolean; -+ hasSelfHost: boolean; -+ hasPolicies: boolean; -+ hasGroups: boolean; -+ hasDirectory: boolean; -+ hasEvents: boolean; -+ hasTotp: boolean; -+ has2fa: boolean; -+ hasApi: boolean; -+ hasSso: boolean; -+ hasResetPassword: boolean; -+ usersGetPremium: boolean; - -- upgradeSortOrder: number; -- displaySortOrder: number; -- legacyYear: number; -- disabled: boolean; -+ upgradeSortOrder: number; -+ displaySortOrder: number; -+ legacyYear: number; -+ disabled: boolean; - -- stripePlanId: string; -- stripeSeatPlanId: string; -- stripeStoragePlanId: string; -- stripePremiumAccessPlanId: string; -- basePrice: number; -- seatPrice: number; -- additionalStoragePricePerGb: number; -- premiumAccessOptionPrice: number; -+ stripePlanId: string; -+ stripeSeatPlanId: string; -+ stripeStoragePlanId: string; -+ stripePremiumAccessPlanId: string; -+ basePrice: number; -+ seatPrice: number; -+ additionalStoragePricePerGb: number; -+ premiumAccessOptionPrice: number; - -- constructor(response: any) { -- super(response); -- this.type = this.getResponseProperty('Type'); -- this.product = this.getResponseProperty('Product'); -- this.name = this.getResponseProperty('Name'); -- this.isAnnual = this.getResponseProperty('IsAnnual'); -- this.nameLocalizationKey = this.getResponseProperty('NameLocalizationKey'); -- this.descriptionLocalizationKey = this.getResponseProperty('DescriptionLocalizationKey'); -- this.canBeUsedByBusiness = this.getResponseProperty('CanBeUsedByBusiness'); -- this.baseSeats = this.getResponseProperty('BaseSeats'); -- this.baseStorageGb = this.getResponseProperty('BaseStorageGb'); -- this.maxCollections = this.getResponseProperty('MaxCollections'); -- this.maxUsers = this.getResponseProperty('MaxUsers'); -- this.hasAdditionalSeatsOption = this.getResponseProperty('HasAdditionalSeatsOption'); -- this.maxAdditionalSeats = this.getResponseProperty('MaxAdditionalSeats'); -- this.hasAdditionalStorageOption = this.getResponseProperty('HasAdditionalStorageOption'); -- this.maxAdditionalStorage = this.getResponseProperty('MaxAdditionalStorage'); -- this.hasPremiumAccessOption = this.getResponseProperty('HasPremiumAccessOption'); -- this.trialPeriodDays = this.getResponseProperty('TrialPeriodDays'); -- this.hasSelfHost = this.getResponseProperty('HasSelfHost'); -- this.hasPolicies = this.getResponseProperty('HasPolicies'); -- this.hasGroups = this.getResponseProperty('HasGroups'); -- this.hasDirectory = this.getResponseProperty('HasDirectory'); -- this.hasEvents = this.getResponseProperty('HasEvents'); -- this.hasTotp = this.getResponseProperty('HasTotp'); -- this.has2fa = this.getResponseProperty('Has2fa'); -- this.hasApi = this.getResponseProperty('HasApi'); -- this.hasSso = this.getResponseProperty('HasSso'); -- this.hasResetPassword = this.getResponseProperty('HasResetPassword'); -- this.usersGetPremium = this.getResponseProperty('UsersGetPremium'); -- this.upgradeSortOrder = this.getResponseProperty('UpgradeSortOrder'); -- this.displaySortOrder = this.getResponseProperty('SortOrder'); -- this.legacyYear = this.getResponseProperty('LegacyYear'); -- this.disabled = this.getResponseProperty('Disabled'); -- this.stripePlanId = this.getResponseProperty('StripePlanId'); -- this.stripeSeatPlanId = this.getResponseProperty('StripeSeatPlanId'); -- this.stripeStoragePlanId = this.getResponseProperty('StripeStoragePlanId'); -- this.stripePremiumAccessPlanId = this.getResponseProperty('StripePremiumAccessPlanId'); -- this.basePrice = this.getResponseProperty('BasePrice'); -- this.seatPrice = this.getResponseProperty('SeatPrice'); -- this.additionalStoragePricePerGb = this.getResponseProperty('AdditionalStoragePricePerGb'); -- this.premiumAccessOptionPrice = this.getResponseProperty('PremiumAccessOptionPrice'); -- } -+ constructor(response: any) { -+ super(response); -+ this.type = this.getResponseProperty("Type"); -+ this.product = this.getResponseProperty("Product"); -+ this.name = this.getResponseProperty("Name"); -+ this.isAnnual = this.getResponseProperty("IsAnnual"); -+ this.nameLocalizationKey = this.getResponseProperty("NameLocalizationKey"); -+ this.descriptionLocalizationKey = this.getResponseProperty("DescriptionLocalizationKey"); -+ this.canBeUsedByBusiness = this.getResponseProperty("CanBeUsedByBusiness"); -+ this.baseSeats = this.getResponseProperty("BaseSeats"); -+ this.baseStorageGb = this.getResponseProperty("BaseStorageGb"); -+ this.maxCollections = this.getResponseProperty("MaxCollections"); -+ this.maxUsers = this.getResponseProperty("MaxUsers"); -+ this.hasAdditionalSeatsOption = this.getResponseProperty("HasAdditionalSeatsOption"); -+ this.maxAdditionalSeats = this.getResponseProperty("MaxAdditionalSeats"); -+ this.hasAdditionalStorageOption = this.getResponseProperty("HasAdditionalStorageOption"); -+ this.maxAdditionalStorage = this.getResponseProperty("MaxAdditionalStorage"); -+ this.hasPremiumAccessOption = this.getResponseProperty("HasPremiumAccessOption"); -+ this.trialPeriodDays = this.getResponseProperty("TrialPeriodDays"); -+ this.hasSelfHost = this.getResponseProperty("HasSelfHost"); -+ this.hasPolicies = this.getResponseProperty("HasPolicies"); -+ this.hasGroups = this.getResponseProperty("HasGroups"); -+ this.hasDirectory = this.getResponseProperty("HasDirectory"); -+ this.hasEvents = this.getResponseProperty("HasEvents"); -+ this.hasTotp = this.getResponseProperty("HasTotp"); -+ this.has2fa = this.getResponseProperty("Has2fa"); -+ this.hasApi = this.getResponseProperty("HasApi"); -+ this.hasSso = this.getResponseProperty("HasSso"); -+ this.hasResetPassword = this.getResponseProperty("HasResetPassword"); -+ this.usersGetPremium = this.getResponseProperty("UsersGetPremium"); -+ this.upgradeSortOrder = this.getResponseProperty("UpgradeSortOrder"); -+ this.displaySortOrder = this.getResponseProperty("SortOrder"); -+ this.legacyYear = this.getResponseProperty("LegacyYear"); -+ this.disabled = this.getResponseProperty("Disabled"); -+ this.stripePlanId = this.getResponseProperty("StripePlanId"); -+ this.stripeSeatPlanId = this.getResponseProperty("StripeSeatPlanId"); -+ this.stripeStoragePlanId = this.getResponseProperty("StripeStoragePlanId"); -+ this.stripePremiumAccessPlanId = this.getResponseProperty("StripePremiumAccessPlanId"); -+ this.basePrice = this.getResponseProperty("BasePrice"); -+ this.seatPrice = this.getResponseProperty("SeatPrice"); -+ this.additionalStoragePricePerGb = this.getResponseProperty("AdditionalStoragePricePerGb"); -+ this.premiumAccessOptionPrice = this.getResponseProperty("PremiumAccessOptionPrice"); -+ } - } -diff --git a/jslib/common/src/models/response/policyResponse.ts b/jslib/common/src/models/response/policyResponse.ts -index 2fa931f3..328491cf 100644 ---- a/jslib/common/src/models/response/policyResponse.ts -+++ b/jslib/common/src/models/response/policyResponse.ts -@@ -1,20 +1,20 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - --import { PolicyType } from '../../enums/policyType'; -+import { PolicyType } from "../../enums/policyType"; - - export class PolicyResponse extends BaseResponse { -- id: string; -- organizationId: string; -- type: PolicyType; -- data: any; -- enabled: boolean; -+ id: string; -+ organizationId: string; -+ type: PolicyType; -+ data: any; -+ enabled: boolean; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.organizationId = this.getResponseProperty('OrganizationId'); -- this.type = this.getResponseProperty('Type'); -- this.data = this.getResponseProperty('Data'); -- this.enabled = this.getResponseProperty('Enabled'); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.organizationId = this.getResponseProperty("OrganizationId"); -+ this.type = this.getResponseProperty("Type"); -+ this.data = this.getResponseProperty("Data"); -+ this.enabled = this.getResponseProperty("Enabled"); -+ } - } -diff --git a/jslib/common/src/models/response/preloginResponse.ts b/jslib/common/src/models/response/preloginResponse.ts -index fcbd4cb2..d5396830 100644 ---- a/jslib/common/src/models/response/preloginResponse.ts -+++ b/jslib/common/src/models/response/preloginResponse.ts -@@ -1,14 +1,14 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - --import { KdfType } from '../../enums/kdfType'; -+import { KdfType } from "../../enums/kdfType"; - - export class PreloginResponse extends BaseResponse { -- kdf: KdfType; -- kdfIterations: number; -+ kdf: KdfType; -+ kdfIterations: number; - -- constructor(response: any) { -- super(response); -- this.kdf = this.getResponseProperty('Kdf'); -- this.kdfIterations = this.getResponseProperty('KdfIterations'); -- } -+ constructor(response: any) { -+ super(response); -+ this.kdf = this.getResponseProperty("Kdf"); -+ this.kdfIterations = this.getResponseProperty("KdfIterations"); -+ } - } -diff --git a/jslib/common/src/models/response/profileOrganizationResponse.ts b/jslib/common/src/models/response/profileOrganizationResponse.ts -index fdd873ba..efa2e3f8 100644 ---- a/jslib/common/src/models/response/profileOrganizationResponse.ts -+++ b/jslib/common/src/models/response/profileOrganizationResponse.ts -@@ -1,81 +1,81 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - --import { OrganizationUserStatusType } from '../../enums/organizationUserStatusType'; --import { OrganizationUserType } from '../../enums/organizationUserType'; --import { ProductType } from '../../enums/productType'; --import { PermissionsApi } from '../api/permissionsApi'; -+import { OrganizationUserStatusType } from "../../enums/organizationUserStatusType"; -+import { OrganizationUserType } from "../../enums/organizationUserType"; -+import { ProductType } from "../../enums/productType"; -+import { PermissionsApi } from "../api/permissionsApi"; - - export class ProfileOrganizationResponse extends BaseResponse { -- id: string; -- name: string; -- usePolicies: boolean; -- useGroups: boolean; -- useDirectory: boolean; -- useEvents: boolean; -- useTotp: boolean; -- use2fa: boolean; -- useApi: boolean; -- useSso: boolean; -- useKeyConnector: boolean; -- useResetPassword: boolean; -- selfHost: boolean; -- usersGetPremium: boolean; -- seats: number; -- maxCollections: number; -- maxStorageGb?: number; -- key: string; -- hasPublicAndPrivateKeys: boolean; -- status: OrganizationUserStatusType; -- type: OrganizationUserType; -- enabled: boolean; -- ssoBound: boolean; -- identifier: string; -- permissions: PermissionsApi; -- resetPasswordEnrolled: boolean; -- userId: string; -- providerId: string; -- providerName: string; -- familySponsorshipFriendlyName: string; -- familySponsorshipAvailable: boolean; -- planProductType: ProductType; -- keyConnectorEnabled: boolean; -- keyConnectorUrl: string; -+ id: string; -+ name: string; -+ usePolicies: boolean; -+ useGroups: boolean; -+ useDirectory: boolean; -+ useEvents: boolean; -+ useTotp: boolean; -+ use2fa: boolean; -+ useApi: boolean; -+ useSso: boolean; -+ useKeyConnector: boolean; -+ useResetPassword: boolean; -+ selfHost: boolean; -+ usersGetPremium: boolean; -+ seats: number; -+ maxCollections: number; -+ maxStorageGb?: number; -+ key: string; -+ hasPublicAndPrivateKeys: boolean; -+ status: OrganizationUserStatusType; -+ type: OrganizationUserType; -+ enabled: boolean; -+ ssoBound: boolean; -+ identifier: string; -+ permissions: PermissionsApi; -+ resetPasswordEnrolled: boolean; -+ userId: string; -+ providerId: string; -+ providerName: string; -+ familySponsorshipFriendlyName: string; -+ familySponsorshipAvailable: boolean; -+ planProductType: ProductType; -+ keyConnectorEnabled: boolean; -+ keyConnectorUrl: string; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.name = this.getResponseProperty('Name'); -- this.usePolicies = this.getResponseProperty('UsePolicies'); -- this.useGroups = this.getResponseProperty('UseGroups'); -- this.useDirectory = this.getResponseProperty('UseDirectory'); -- this.useEvents = this.getResponseProperty('UseEvents'); -- this.useTotp = this.getResponseProperty('UseTotp'); -- this.use2fa = this.getResponseProperty('Use2fa'); -- this.useApi = this.getResponseProperty('UseApi'); -- this.useSso = this.getResponseProperty('UseSso'); -- this.useKeyConnector = this.getResponseProperty('UseKeyConnector') ?? false; -- this.useResetPassword = this.getResponseProperty('UseResetPassword'); -- this.selfHost = this.getResponseProperty('SelfHost'); -- this.usersGetPremium = this.getResponseProperty('UsersGetPremium'); -- this.seats = this.getResponseProperty('Seats'); -- this.maxCollections = this.getResponseProperty('MaxCollections'); -- this.maxStorageGb = this.getResponseProperty('MaxStorageGb'); -- this.key = this.getResponseProperty('Key'); -- this.hasPublicAndPrivateKeys = this.getResponseProperty('HasPublicAndPrivateKeys'); -- this.status = this.getResponseProperty('Status'); -- this.type = this.getResponseProperty('Type'); -- this.enabled = this.getResponseProperty('Enabled'); -- this.ssoBound = this.getResponseProperty('SsoBound'); -- this.identifier = this.getResponseProperty('Identifier'); -- this.permissions = new PermissionsApi(this.getResponseProperty('permissions')); -- this.resetPasswordEnrolled = this.getResponseProperty('ResetPasswordEnrolled'); -- this.userId = this.getResponseProperty('UserId'); -- this.providerId = this.getResponseProperty('ProviderId'); -- this.providerName = this.getResponseProperty('ProviderName'); -- this.familySponsorshipFriendlyName = this.getResponseProperty('FamilySponsorshipFriendlyName'); -- this.familySponsorshipAvailable = this.getResponseProperty('FamilySponsorshipAvailable'); -- this.planProductType = this.getResponseProperty('PlanProductType'); -- this.keyConnectorEnabled = this.getResponseProperty('KeyConnectorEnabled') ?? false; -- this.keyConnectorUrl = this.getResponseProperty('KeyConnectorUrl'); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.name = this.getResponseProperty("Name"); -+ this.usePolicies = this.getResponseProperty("UsePolicies"); -+ this.useGroups = this.getResponseProperty("UseGroups"); -+ this.useDirectory = this.getResponseProperty("UseDirectory"); -+ this.useEvents = this.getResponseProperty("UseEvents"); -+ this.useTotp = this.getResponseProperty("UseTotp"); -+ this.use2fa = this.getResponseProperty("Use2fa"); -+ this.useApi = this.getResponseProperty("UseApi"); -+ this.useSso = this.getResponseProperty("UseSso"); -+ this.useKeyConnector = this.getResponseProperty("UseKeyConnector") ?? false; -+ this.useResetPassword = this.getResponseProperty("UseResetPassword"); -+ this.selfHost = this.getResponseProperty("SelfHost"); -+ this.usersGetPremium = this.getResponseProperty("UsersGetPremium"); -+ this.seats = this.getResponseProperty("Seats"); -+ this.maxCollections = this.getResponseProperty("MaxCollections"); -+ this.maxStorageGb = this.getResponseProperty("MaxStorageGb"); -+ this.key = this.getResponseProperty("Key"); -+ this.hasPublicAndPrivateKeys = this.getResponseProperty("HasPublicAndPrivateKeys"); -+ this.status = this.getResponseProperty("Status"); -+ this.type = this.getResponseProperty("Type"); -+ this.enabled = this.getResponseProperty("Enabled"); -+ this.ssoBound = this.getResponseProperty("SsoBound"); -+ this.identifier = this.getResponseProperty("Identifier"); -+ this.permissions = new PermissionsApi(this.getResponseProperty("permissions")); -+ this.resetPasswordEnrolled = this.getResponseProperty("ResetPasswordEnrolled"); -+ this.userId = this.getResponseProperty("UserId"); -+ this.providerId = this.getResponseProperty("ProviderId"); -+ this.providerName = this.getResponseProperty("ProviderName"); -+ this.familySponsorshipFriendlyName = this.getResponseProperty("FamilySponsorshipFriendlyName"); -+ this.familySponsorshipAvailable = this.getResponseProperty("FamilySponsorshipAvailable"); -+ this.planProductType = this.getResponseProperty("PlanProductType"); -+ this.keyConnectorEnabled = this.getResponseProperty("KeyConnectorEnabled") ?? false; -+ this.keyConnectorUrl = this.getResponseProperty("KeyConnectorUrl"); -+ } - } -diff --git a/jslib/common/src/models/response/profileProviderOrganizationResponse.ts b/jslib/common/src/models/response/profileProviderOrganizationResponse.ts -index d34b78d0..74b74c00 100644 ---- a/jslib/common/src/models/response/profileProviderOrganizationResponse.ts -+++ b/jslib/common/src/models/response/profileProviderOrganizationResponse.ts -@@ -1,8 +1,8 @@ --import { ProfileOrganizationResponse } from './profileOrganizationResponse'; -+import { ProfileOrganizationResponse } from "./profileOrganizationResponse"; - - export class ProfileProviderOrganizationResponse extends ProfileOrganizationResponse { -- constructor(response: any) { -- super(response); -- this.keyConnectorEnabled = false; -- } -+ constructor(response: any) { -+ super(response); -+ this.keyConnectorEnabled = false; -+ } - } -diff --git a/jslib/common/src/models/response/profileProviderResponse.ts b/jslib/common/src/models/response/profileProviderResponse.ts -index b3c23404..04610080 100644 ---- a/jslib/common/src/models/response/profileProviderResponse.ts -+++ b/jslib/common/src/models/response/profileProviderResponse.ts -@@ -1,31 +1,31 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - --import { ProviderUserStatusType } from '../../enums/providerUserStatusType'; --import { ProviderUserType } from '../../enums/providerUserType'; -+import { ProviderUserStatusType } from "../../enums/providerUserStatusType"; -+import { ProviderUserType } from "../../enums/providerUserType"; - --import { PermissionsApi } from '../api/permissionsApi'; -+import { PermissionsApi } from "../api/permissionsApi"; - - export class ProfileProviderResponse extends BaseResponse { -- id: string; -- name: string; -- key: string; -- status: ProviderUserStatusType; -- type: ProviderUserType; -- enabled: boolean; -- permissions: PermissionsApi; -- userId: string; -- useEvents: boolean; -+ id: string; -+ name: string; -+ key: string; -+ status: ProviderUserStatusType; -+ type: ProviderUserType; -+ enabled: boolean; -+ permissions: PermissionsApi; -+ userId: string; -+ useEvents: boolean; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.name = this.getResponseProperty('Name'); -- this.key = this.getResponseProperty('Key'); -- this.status = this.getResponseProperty('Status'); -- this.type = this.getResponseProperty('Type'); -- this.enabled = this.getResponseProperty('Enabled'); -- this.permissions = new PermissionsApi(this.getResponseProperty('permissions')); -- this.userId = this.getResponseProperty('UserId'); -- this.useEvents = this.getResponseProperty('UseEvents'); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.name = this.getResponseProperty("Name"); -+ this.key = this.getResponseProperty("Key"); -+ this.status = this.getResponseProperty("Status"); -+ this.type = this.getResponseProperty("Type"); -+ this.enabled = this.getResponseProperty("Enabled"); -+ this.permissions = new PermissionsApi(this.getResponseProperty("permissions")); -+ this.userId = this.getResponseProperty("UserId"); -+ this.useEvents = this.getResponseProperty("UseEvents"); -+ } - } -diff --git a/jslib/common/src/models/response/profileResponse.ts b/jslib/common/src/models/response/profileResponse.ts -index 913ff820..2c3e66fb 100644 ---- a/jslib/common/src/models/response/profileResponse.ts -+++ b/jslib/common/src/models/response/profileResponse.ts -@@ -1,53 +1,55 @@ --import { BaseResponse } from './baseResponse'; --import { ProfileOrganizationResponse } from './profileOrganizationResponse'; --import { ProfileProviderOrganizationResponse } from './profileProviderOrganizationResponse'; --import { ProfileProviderResponse } from './profileProviderResponse'; -+import { BaseResponse } from "./baseResponse"; -+import { ProfileOrganizationResponse } from "./profileOrganizationResponse"; -+import { ProfileProviderOrganizationResponse } from "./profileProviderOrganizationResponse"; -+import { ProfileProviderResponse } from "./profileProviderResponse"; - - export class ProfileResponse extends BaseResponse { -- id: string; -- name: string; -- email: string; -- emailVerified: boolean; -- masterPasswordHint: string; -- premium: boolean; -- culture: string; -- twoFactorEnabled: boolean; -- key: string; -- privateKey: string; -- securityStamp: string; -- forcePasswordReset: boolean; -- usesKeyConnector: boolean; -- organizations: ProfileOrganizationResponse[] = []; -- providers: ProfileProviderResponse[] = []; -- providerOrganizations: ProfileProviderOrganizationResponse[] = []; -+ id: string; -+ name: string; -+ email: string; -+ emailVerified: boolean; -+ masterPasswordHint: string; -+ premium: boolean; -+ culture: string; -+ twoFactorEnabled: boolean; -+ key: string; -+ privateKey: string; -+ securityStamp: string; -+ forcePasswordReset: boolean; -+ usesKeyConnector: boolean; -+ organizations: ProfileOrganizationResponse[] = []; -+ providers: ProfileProviderResponse[] = []; -+ providerOrganizations: ProfileProviderOrganizationResponse[] = []; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.name = this.getResponseProperty('Name'); -- this.email = this.getResponseProperty('Email'); -- this.emailVerified = this.getResponseProperty('EmailVerified'); -- this.masterPasswordHint = this.getResponseProperty('MasterPasswordHint'); -- this.premium = this.getResponseProperty('Premium'); -- this.culture = this.getResponseProperty('Culture'); -- this.twoFactorEnabled = this.getResponseProperty('TwoFactorEnabled'); -- this.key = this.getResponseProperty('Key'); -- this.privateKey = this.getResponseProperty('PrivateKey'); -- this.securityStamp = this.getResponseProperty('SecurityStamp'); -- this.forcePasswordReset = this.getResponseProperty('ForcePasswordReset') ?? false; -- this.usesKeyConnector = this.getResponseProperty('UsesKeyConnector') ?? false; -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.name = this.getResponseProperty("Name"); -+ this.email = this.getResponseProperty("Email"); -+ this.emailVerified = this.getResponseProperty("EmailVerified"); -+ this.masterPasswordHint = this.getResponseProperty("MasterPasswordHint"); -+ this.premium = this.getResponseProperty("Premium"); -+ this.culture = this.getResponseProperty("Culture"); -+ this.twoFactorEnabled = this.getResponseProperty("TwoFactorEnabled"); -+ this.key = this.getResponseProperty("Key"); -+ this.privateKey = this.getResponseProperty("PrivateKey"); -+ this.securityStamp = this.getResponseProperty("SecurityStamp"); -+ this.forcePasswordReset = this.getResponseProperty("ForcePasswordReset") ?? false; -+ this.usesKeyConnector = this.getResponseProperty("UsesKeyConnector") ?? false; - -- const organizations = this.getResponseProperty('Organizations'); -- if (organizations != null) { -- this.organizations = organizations.map((o: any) => new ProfileOrganizationResponse(o)); -- } -- const providers = this.getResponseProperty('Providers'); -- if (providers != null) { -- this.providers = providers.map((o: any) => new ProfileProviderResponse(o)); -- } -- const providerOrganizations = this.getResponseProperty('ProviderOrganizations'); -- if (providerOrganizations != null) { -- this.providerOrganizations = providerOrganizations.map((o: any) => new ProfileProviderOrganizationResponse(o)); -- } -+ const organizations = this.getResponseProperty("Organizations"); -+ if (organizations != null) { -+ this.organizations = organizations.map((o: any) => new ProfileOrganizationResponse(o)); - } -+ const providers = this.getResponseProperty("Providers"); -+ if (providers != null) { -+ this.providers = providers.map((o: any) => new ProfileProviderResponse(o)); -+ } -+ const providerOrganizations = this.getResponseProperty("ProviderOrganizations"); -+ if (providerOrganizations != null) { -+ this.providerOrganizations = providerOrganizations.map( -+ (o: any) => new ProfileProviderOrganizationResponse(o) -+ ); -+ } -+ } - } -diff --git a/jslib/common/src/models/response/provider/providerOrganizationResponse.ts b/jslib/common/src/models/response/provider/providerOrganizationResponse.ts -index 91fa6a46..d733362a 100644 ---- a/jslib/common/src/models/response/provider/providerOrganizationResponse.ts -+++ b/jslib/common/src/models/response/provider/providerOrganizationResponse.ts -@@ -1,31 +1,31 @@ --import { BaseResponse } from '../baseResponse'; -+import { BaseResponse } from "../baseResponse"; - - export class ProviderOrganizationResponse extends BaseResponse { -- id: string; -- providerId: string; -- organizationId: string; -- key: string; -- settings: string; -- creationDate: string; -- revisionDate: string; -+ id: string; -+ providerId: string; -+ organizationId: string; -+ key: string; -+ settings: string; -+ creationDate: string; -+ revisionDate: string; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.providerId = this.getResponseProperty('ProviderId'); -- this.organizationId = this.getResponseProperty('OrganizationId'); -- this.key = this.getResponseProperty('Key'); -- this.settings = this.getResponseProperty('Settings'); -- this.creationDate = this.getResponseProperty('CreationDate'); -- this.revisionDate = this.getResponseProperty('RevisionDate'); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.providerId = this.getResponseProperty("ProviderId"); -+ this.organizationId = this.getResponseProperty("OrganizationId"); -+ this.key = this.getResponseProperty("Key"); -+ this.settings = this.getResponseProperty("Settings"); -+ this.creationDate = this.getResponseProperty("CreationDate"); -+ this.revisionDate = this.getResponseProperty("RevisionDate"); -+ } - } - - export class ProviderOrganizationOrganizationDetailsResponse extends ProviderOrganizationResponse { -- organizationName: string; -+ organizationName: string; - -- constructor(response: any) { -- super(response); -- this.organizationName = this.getResponseProperty('OrganizationName'); -- } -+ constructor(response: any) { -+ super(response); -+ this.organizationName = this.getResponseProperty("OrganizationName"); -+ } - } -diff --git a/jslib/common/src/models/response/provider/providerResponse.ts b/jslib/common/src/models/response/provider/providerResponse.ts -index d3d2364e..2b8729fa 100644 ---- a/jslib/common/src/models/response/provider/providerResponse.ts -+++ b/jslib/common/src/models/response/provider/providerResponse.ts -@@ -1,16 +1,16 @@ --import { BaseResponse } from '../baseResponse'; -+import { BaseResponse } from "../baseResponse"; - - export class ProviderResponse extends BaseResponse { -- id: string; -- name: string; -- businessName: string; -- billingEmail: string; -+ id: string; -+ name: string; -+ businessName: string; -+ billingEmail: string; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.name = this.getResponseProperty('Name'); -- this.businessName = this.getResponseProperty('BusinessName'); -- this.billingEmail = this.getResponseProperty('BillingEmail'); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.name = this.getResponseProperty("Name"); -+ this.businessName = this.getResponseProperty("BusinessName"); -+ this.billingEmail = this.getResponseProperty("BillingEmail"); -+ } - } -diff --git a/jslib/common/src/models/response/provider/providerUserBulkPublicKeyResponse.ts b/jslib/common/src/models/response/provider/providerUserBulkPublicKeyResponse.ts -index 122be2aa..ad078f98 100644 ---- a/jslib/common/src/models/response/provider/providerUserBulkPublicKeyResponse.ts -+++ b/jslib/common/src/models/response/provider/providerUserBulkPublicKeyResponse.ts -@@ -1,5 +1,3 @@ --import { OrganizationUserBulkPublicKeyResponse } from '../organizationUserBulkPublicKeyResponse'; -+import { OrganizationUserBulkPublicKeyResponse } from "../organizationUserBulkPublicKeyResponse"; - --export class ProviderUserBulkPublicKeyResponse extends OrganizationUserBulkPublicKeyResponse { -- --} -+export class ProviderUserBulkPublicKeyResponse extends OrganizationUserBulkPublicKeyResponse {} -diff --git a/jslib/common/src/models/response/provider/providerUserBulkResponse.ts b/jslib/common/src/models/response/provider/providerUserBulkResponse.ts -index 019ee4f5..6cf12f96 100644 ---- a/jslib/common/src/models/response/provider/providerUserBulkResponse.ts -+++ b/jslib/common/src/models/response/provider/providerUserBulkResponse.ts -@@ -1,12 +1,12 @@ --import { BaseResponse } from '../baseResponse'; -+import { BaseResponse } from "../baseResponse"; - - export class ProviderUserBulkResponse extends BaseResponse { -- id: string; -- error: string; -+ id: string; -+ error: string; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.error = this.getResponseProperty('Error'); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.error = this.getResponseProperty("Error"); -+ } - } -diff --git a/jslib/common/src/models/response/provider/providerUserResponse.ts b/jslib/common/src/models/response/provider/providerUserResponse.ts -index 728b708f..4aad5814 100644 ---- a/jslib/common/src/models/response/provider/providerUserResponse.ts -+++ b/jslib/common/src/models/response/provider/providerUserResponse.ts -@@ -1,34 +1,34 @@ --import { BaseResponse } from '../baseResponse'; -+import { BaseResponse } from "../baseResponse"; - --import { PermissionsApi } from '../../api/permissionsApi'; -+import { PermissionsApi } from "../../api/permissionsApi"; - --import { ProviderUserStatusType } from '../../../enums/providerUserStatusType'; --import { ProviderUserType } from '../../../enums/providerUserType'; -+import { ProviderUserStatusType } from "../../../enums/providerUserStatusType"; -+import { ProviderUserType } from "../../../enums/providerUserType"; - - export class ProviderUserResponse extends BaseResponse { -- id: string; -- userId: string; -- type: ProviderUserType; -- status: ProviderUserStatusType; -- permissions: PermissionsApi; -+ id: string; -+ userId: string; -+ type: ProviderUserType; -+ status: ProviderUserStatusType; -+ permissions: PermissionsApi; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.userId = this.getResponseProperty('UserId'); -- this.type = this.getResponseProperty('Type'); -- this.status = this.getResponseProperty('Status'); -- this.permissions = new PermissionsApi(this.getResponseProperty('Permissions')); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.userId = this.getResponseProperty("UserId"); -+ this.type = this.getResponseProperty("Type"); -+ this.status = this.getResponseProperty("Status"); -+ this.permissions = new PermissionsApi(this.getResponseProperty("Permissions")); -+ } - } - - export class ProviderUserUserDetailsResponse extends ProviderUserResponse { -- name: string; -- email: string; -+ name: string; -+ email: string; - -- constructor(response: any) { -- super(response); -- this.name = this.getResponseProperty('Name'); -- this.email = this.getResponseProperty('Email'); -- } -+ constructor(response: any) { -+ super(response); -+ this.name = this.getResponseProperty("Name"); -+ this.email = this.getResponseProperty("Email"); -+ } - } -diff --git a/jslib/common/src/models/response/selectionReadOnlyResponse.ts b/jslib/common/src/models/response/selectionReadOnlyResponse.ts -index ebcf5247..cee9361c 100644 ---- a/jslib/common/src/models/response/selectionReadOnlyResponse.ts -+++ b/jslib/common/src/models/response/selectionReadOnlyResponse.ts -@@ -1,14 +1,14 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class SelectionReadOnlyResponse extends BaseResponse { -- id: string; -- readOnly: boolean; -- hidePasswords: boolean; -+ id: string; -+ readOnly: boolean; -+ hidePasswords: boolean; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.readOnly = this.getResponseProperty('ReadOnly'); -- this.hidePasswords = this.getResponseProperty('HidePasswords'); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.readOnly = this.getResponseProperty("ReadOnly"); -+ this.hidePasswords = this.getResponseProperty("HidePasswords"); -+ } - } -diff --git a/jslib/common/src/models/response/sendAccessResponse.ts b/jslib/common/src/models/response/sendAccessResponse.ts -index ef4a57aa..2742c7f9 100644 ---- a/jslib/common/src/models/response/sendAccessResponse.ts -+++ b/jslib/common/src/models/response/sendAccessResponse.ts -@@ -1,36 +1,36 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - --import { SendType } from '../../enums/sendType'; -+import { SendType } from "../../enums/sendType"; - --import { SendFileApi } from '../api/sendFileApi'; --import { SendTextApi } from '../api/sendTextApi'; -+import { SendFileApi } from "../api/sendFileApi"; -+import { SendTextApi } from "../api/sendTextApi"; - - export class SendAccessResponse extends BaseResponse { -- id: string; -- type: SendType; -- name: string; -- file: SendFileApi; -- text: SendTextApi; -- expirationDate: Date; -- creatorIdentifier: string; -+ id: string; -+ type: SendType; -+ name: string; -+ file: SendFileApi; -+ text: SendTextApi; -+ expirationDate: Date; -+ creatorIdentifier: string; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.type = this.getResponseProperty('Type'); -- this.name = this.getResponseProperty('Name'); -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.type = this.getResponseProperty("Type"); -+ this.name = this.getResponseProperty("Name"); - -- const text = this.getResponseProperty('Text'); -- if (text != null) { -- this.text = new SendTextApi(text); -- } -- -- const file = this.getResponseProperty('File'); -- if (file != null) { -- this.file = new SendFileApi(file); -- } -+ const text = this.getResponseProperty("Text"); -+ if (text != null) { -+ this.text = new SendTextApi(text); -+ } - -- this.expirationDate = this.getResponseProperty('ExpirationDate'); -- this.creatorIdentifier = this.getResponseProperty('CreatorIdentifier'); -+ const file = this.getResponseProperty("File"); -+ if (file != null) { -+ this.file = new SendFileApi(file); - } -+ -+ this.expirationDate = this.getResponseProperty("ExpirationDate"); -+ this.creatorIdentifier = this.getResponseProperty("CreatorIdentifier"); -+ } - } -diff --git a/jslib/common/src/models/response/sendFileDownloadDataResponse.ts b/jslib/common/src/models/response/sendFileDownloadDataResponse.ts -index 734ef224..ca2575a2 100644 ---- a/jslib/common/src/models/response/sendFileDownloadDataResponse.ts -+++ b/jslib/common/src/models/response/sendFileDownloadDataResponse.ts -@@ -1,12 +1,11 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class SendFileDownloadDataResponse extends BaseResponse { -- -- id: string = null; -- url: string = null; -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.url = this.getResponseProperty('Url'); -- } -+ id: string = null; -+ url: string = null; -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.url = this.getResponseProperty("Url"); -+ } - } -diff --git a/jslib/common/src/models/response/sendFileUploadDataResponse.ts b/jslib/common/src/models/response/sendFileUploadDataResponse.ts -index 3f7f0b3d..b787f2cc 100644 ---- a/jslib/common/src/models/response/sendFileUploadDataResponse.ts -+++ b/jslib/common/src/models/response/sendFileUploadDataResponse.ts -@@ -1,18 +1,17 @@ --import { FileUploadType } from '../../enums/fileUploadType'; -+import { FileUploadType } from "../../enums/fileUploadType"; - --import { BaseResponse } from './baseResponse'; --import { SendResponse } from './sendResponse'; -+import { BaseResponse } from "./baseResponse"; -+import { SendResponse } from "./sendResponse"; - - export class SendFileUploadDataResponse extends BaseResponse { -- -- fileUploadType: FileUploadType; -- sendResponse: SendResponse; -- url: string = null; -- constructor(response: any) { -- super(response); -- this.fileUploadType = this.getResponseProperty('FileUploadType'); -- const sendResponse = this.getResponseProperty('SendResponse'); -- this.sendResponse = sendResponse == null ? null : new SendResponse(sendResponse); -- this.url = this.getResponseProperty('Url'); -- } -+ fileUploadType: FileUploadType; -+ sendResponse: SendResponse; -+ url: string = null; -+ constructor(response: any) { -+ super(response); -+ this.fileUploadType = this.getResponseProperty("FileUploadType"); -+ const sendResponse = this.getResponseProperty("SendResponse"); -+ this.sendResponse = sendResponse == null ? null : new SendResponse(sendResponse); -+ this.url = this.getResponseProperty("Url"); -+ } - } -diff --git a/jslib/common/src/models/response/sendResponse.ts b/jslib/common/src/models/response/sendResponse.ts -index fdbbf334..edb009c3 100644 ---- a/jslib/common/src/models/response/sendResponse.ts -+++ b/jslib/common/src/models/response/sendResponse.ts -@@ -1,53 +1,53 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - --import { SendType } from '../../enums/sendType'; -+import { SendType } from "../../enums/sendType"; - --import { SendFileApi } from '../api/sendFileApi'; --import { SendTextApi } from '../api/sendTextApi'; -+import { SendFileApi } from "../api/sendFileApi"; -+import { SendTextApi } from "../api/sendTextApi"; - - export class SendResponse extends BaseResponse { -- id: string; -- accessId: string; -- type: SendType; -- name: string; -- notes: string; -- file: SendFileApi; -- text: SendTextApi; -- key: string; -- maxAccessCount?: number; -- accessCount: number; -- revisionDate: string; -- expirationDate: string; -- deletionDate: string; -- password: string; -- disable: boolean; -- hideEmail: boolean; -+ id: string; -+ accessId: string; -+ type: SendType; -+ name: string; -+ notes: string; -+ file: SendFileApi; -+ text: SendTextApi; -+ key: string; -+ maxAccessCount?: number; -+ accessCount: number; -+ revisionDate: string; -+ expirationDate: string; -+ deletionDate: string; -+ password: string; -+ disable: boolean; -+ hideEmail: boolean; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.accessId = this.getResponseProperty('AccessId'); -- this.type = this.getResponseProperty('Type'); -- this.name = this.getResponseProperty('Name'); -- this.notes = this.getResponseProperty('Notes'); -- this.key = this.getResponseProperty('Key'); -- this.maxAccessCount = this.getResponseProperty('MaxAccessCount'); -- this.accessCount = this.getResponseProperty('AccessCount'); -- this.revisionDate = this.getResponseProperty('RevisionDate'); -- this.expirationDate = this.getResponseProperty('ExpirationDate'); -- this.deletionDate = this.getResponseProperty('DeletionDate'); -- this.password = this.getResponseProperty('Password'); -- this.disable = this.getResponseProperty('Disabled') || false; -- this.hideEmail = this.getResponseProperty('HideEmail') || false; -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.accessId = this.getResponseProperty("AccessId"); -+ this.type = this.getResponseProperty("Type"); -+ this.name = this.getResponseProperty("Name"); -+ this.notes = this.getResponseProperty("Notes"); -+ this.key = this.getResponseProperty("Key"); -+ this.maxAccessCount = this.getResponseProperty("MaxAccessCount"); -+ this.accessCount = this.getResponseProperty("AccessCount"); -+ this.revisionDate = this.getResponseProperty("RevisionDate"); -+ this.expirationDate = this.getResponseProperty("ExpirationDate"); -+ this.deletionDate = this.getResponseProperty("DeletionDate"); -+ this.password = this.getResponseProperty("Password"); -+ this.disable = this.getResponseProperty("Disabled") || false; -+ this.hideEmail = this.getResponseProperty("HideEmail") || false; - -- const text = this.getResponseProperty('Text'); -- if (text != null) { -- this.text = new SendTextApi(text); -- } -+ const text = this.getResponseProperty("Text"); -+ if (text != null) { -+ this.text = new SendTextApi(text); -+ } - -- const file = this.getResponseProperty('File'); -- if (file != null) { -- this.file = new SendFileApi(file); -- } -+ const file = this.getResponseProperty("File"); -+ if (file != null) { -+ this.file = new SendFileApi(file); - } -+ } - } -diff --git a/jslib/common/src/models/response/subscriptionResponse.ts b/jslib/common/src/models/response/subscriptionResponse.ts -index 616d5a8b..fc5570c3 100644 ---- a/jslib/common/src/models/response/subscriptionResponse.ts -+++ b/jslib/common/src/models/response/subscriptionResponse.ts -@@ -1,83 +1,85 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class SubscriptionResponse extends BaseResponse { -- storageName: string; -- storageGb: number; -- maxStorageGb: number; -- subscription: BillingSubscriptionResponse; -- upcomingInvoice: BillingSubscriptionUpcomingInvoiceResponse; -- license: any; -- expiration: string; -- usingInAppPurchase: boolean; -+ storageName: string; -+ storageGb: number; -+ maxStorageGb: number; -+ subscription: BillingSubscriptionResponse; -+ upcomingInvoice: BillingSubscriptionUpcomingInvoiceResponse; -+ license: any; -+ expiration: string; -+ usingInAppPurchase: boolean; - -- constructor(response: any) { -- super(response); -- this.storageName = this.getResponseProperty('StorageName'); -- this.storageGb = this.getResponseProperty('StorageGb'); -- this.maxStorageGb = this.getResponseProperty('MaxStorageGb'); -- this.license = this.getResponseProperty('License'); -- this.expiration = this.getResponseProperty('Expiration'); -- this.usingInAppPurchase = this.getResponseProperty('UsingInAppPurchase'); -- const subscription = this.getResponseProperty('Subscription'); -- const upcomingInvoice = this.getResponseProperty('UpcomingInvoice'); -- this.subscription = subscription == null ? null : new BillingSubscriptionResponse(subscription); -- this.upcomingInvoice = upcomingInvoice == null ? null : -- new BillingSubscriptionUpcomingInvoiceResponse(upcomingInvoice); -- } -+ constructor(response: any) { -+ super(response); -+ this.storageName = this.getResponseProperty("StorageName"); -+ this.storageGb = this.getResponseProperty("StorageGb"); -+ this.maxStorageGb = this.getResponseProperty("MaxStorageGb"); -+ this.license = this.getResponseProperty("License"); -+ this.expiration = this.getResponseProperty("Expiration"); -+ this.usingInAppPurchase = this.getResponseProperty("UsingInAppPurchase"); -+ const subscription = this.getResponseProperty("Subscription"); -+ const upcomingInvoice = this.getResponseProperty("UpcomingInvoice"); -+ this.subscription = subscription == null ? null : new BillingSubscriptionResponse(subscription); -+ this.upcomingInvoice = -+ upcomingInvoice == null -+ ? null -+ : new BillingSubscriptionUpcomingInvoiceResponse(upcomingInvoice); -+ } - } - - export class BillingSubscriptionResponse extends BaseResponse { -- trialStartDate: string; -- trialEndDate: string; -- periodStartDate: string; -- periodEndDate: string; -- cancelledDate: string; -- cancelAtEndDate: boolean; -- status: string; -- cancelled: boolean; -- items: BillingSubscriptionItemResponse[] = []; -+ trialStartDate: string; -+ trialEndDate: string; -+ periodStartDate: string; -+ periodEndDate: string; -+ cancelledDate: string; -+ cancelAtEndDate: boolean; -+ status: string; -+ cancelled: boolean; -+ items: BillingSubscriptionItemResponse[] = []; - -- constructor(response: any) { -- super(response); -- this.trialEndDate = this.getResponseProperty('TrialStartDate'); -- this.trialEndDate = this.getResponseProperty('TrialEndDate'); -- this.periodStartDate = this.getResponseProperty('PeriodStartDate'); -- this.periodEndDate = this.getResponseProperty('PeriodEndDate'); -- this.cancelledDate = this.getResponseProperty('CancelledDate'); -- this.cancelAtEndDate = this.getResponseProperty('CancelAtEndDate'); -- this.status = this.getResponseProperty('Status'); -- this.cancelled = this.getResponseProperty('Cancelled'); -- const items = this.getResponseProperty('Items'); -- if (items != null) { -- this.items = items.map((i: any) => new BillingSubscriptionItemResponse(i)); -- } -+ constructor(response: any) { -+ super(response); -+ this.trialEndDate = this.getResponseProperty("TrialStartDate"); -+ this.trialEndDate = this.getResponseProperty("TrialEndDate"); -+ this.periodStartDate = this.getResponseProperty("PeriodStartDate"); -+ this.periodEndDate = this.getResponseProperty("PeriodEndDate"); -+ this.cancelledDate = this.getResponseProperty("CancelledDate"); -+ this.cancelAtEndDate = this.getResponseProperty("CancelAtEndDate"); -+ this.status = this.getResponseProperty("Status"); -+ this.cancelled = this.getResponseProperty("Cancelled"); -+ const items = this.getResponseProperty("Items"); -+ if (items != null) { -+ this.items = items.map((i: any) => new BillingSubscriptionItemResponse(i)); - } -+ } - } - - export class BillingSubscriptionItemResponse extends BaseResponse { -- name: string; -- amount: number; -- quantity: number; -- interval: string; -- sponsoredSubscriptionItem: boolean; -+ name: string; -+ amount: number; -+ quantity: number; -+ interval: string; -+ sponsoredSubscriptionItem: boolean; - -- constructor(response: any) { -- super(response); -- this.name = this.getResponseProperty('Name'); -- this.amount = this.getResponseProperty('Amount'); -- this.quantity = this.getResponseProperty('Quantity'); -- this.interval = this.getResponseProperty('Interval'); -- this.sponsoredSubscriptionItem = this.getResponseProperty('SponsoredSubscriptionItem'); -- } -+ constructor(response: any) { -+ super(response); -+ this.name = this.getResponseProperty("Name"); -+ this.amount = this.getResponseProperty("Amount"); -+ this.quantity = this.getResponseProperty("Quantity"); -+ this.interval = this.getResponseProperty("Interval"); -+ this.sponsoredSubscriptionItem = this.getResponseProperty("SponsoredSubscriptionItem"); -+ } - } - - export class BillingSubscriptionUpcomingInvoiceResponse extends BaseResponse { -- date: string; -- amount: number; -+ date: string; -+ amount: number; - -- constructor(response: any) { -- super(response); -- this.date = this.getResponseProperty('Date'); -- this.amount = this.getResponseProperty('Amount'); -- } -+ constructor(response: any) { -+ super(response); -+ this.date = this.getResponseProperty("Date"); -+ this.amount = this.getResponseProperty("Amount"); -+ } - } -diff --git a/jslib/common/src/models/response/syncResponse.ts b/jslib/common/src/models/response/syncResponse.ts -index 1fb655a3..cab3ff2e 100644 ---- a/jslib/common/src/models/response/syncResponse.ts -+++ b/jslib/common/src/models/response/syncResponse.ts -@@ -1,57 +1,57 @@ --import { BaseResponse } from './baseResponse'; --import { CipherResponse } from './cipherResponse'; --import { CollectionDetailsResponse } from './collectionResponse'; --import { DomainsResponse } from './domainsResponse'; --import { FolderResponse } from './folderResponse'; --import { PolicyResponse } from './policyResponse'; --import { ProfileResponse } from './profileResponse'; --import { SendResponse } from './sendResponse'; -+import { BaseResponse } from "./baseResponse"; -+import { CipherResponse } from "./cipherResponse"; -+import { CollectionDetailsResponse } from "./collectionResponse"; -+import { DomainsResponse } from "./domainsResponse"; -+import { FolderResponse } from "./folderResponse"; -+import { PolicyResponse } from "./policyResponse"; -+import { ProfileResponse } from "./profileResponse"; -+import { SendResponse } from "./sendResponse"; - - export class SyncResponse extends BaseResponse { -- profile?: ProfileResponse; -- folders: FolderResponse[] = []; -- collections: CollectionDetailsResponse[] = []; -- ciphers: CipherResponse[] = []; -- domains?: DomainsResponse; -- policies?: PolicyResponse[] = []; -- sends: SendResponse[] = []; -- -- constructor(response: any) { -- super(response); -- -- const profile = this.getResponseProperty('Profile'); -- if (profile != null) { -- this.profile = new ProfileResponse(profile); -- } -- -- const folders = this.getResponseProperty('Folders'); -- if (folders != null) { -- this.folders = folders.map((f: any) => new FolderResponse(f)); -- } -- -- const collections = this.getResponseProperty('Collections'); -- if (collections != null) { -- this.collections = collections.map((c: any) => new CollectionDetailsResponse(c)); -- } -- -- const ciphers = this.getResponseProperty('Ciphers'); -- if (ciphers != null) { -- this.ciphers = ciphers.map((c: any) => new CipherResponse(c)); -- } -- -- const domains = this.getResponseProperty('Domains'); -- if (domains != null) { -- this.domains = new DomainsResponse(domains); -- } -- -- const policies = this.getResponseProperty('Policies'); -- if (policies != null) { -- this.policies = policies.map((p: any) => new PolicyResponse(p)); -- } -- -- const sends = this.getResponseProperty('Sends'); -- if (sends != null) { -- this.sends = sends.map((s: any) => new SendResponse(s)); -- } -+ profile?: ProfileResponse; -+ folders: FolderResponse[] = []; -+ collections: CollectionDetailsResponse[] = []; -+ ciphers: CipherResponse[] = []; -+ domains?: DomainsResponse; -+ policies?: PolicyResponse[] = []; -+ sends: SendResponse[] = []; -+ -+ constructor(response: any) { -+ super(response); -+ -+ const profile = this.getResponseProperty("Profile"); -+ if (profile != null) { -+ this.profile = new ProfileResponse(profile); - } -+ -+ const folders = this.getResponseProperty("Folders"); -+ if (folders != null) { -+ this.folders = folders.map((f: any) => new FolderResponse(f)); -+ } -+ -+ const collections = this.getResponseProperty("Collections"); -+ if (collections != null) { -+ this.collections = collections.map((c: any) => new CollectionDetailsResponse(c)); -+ } -+ -+ const ciphers = this.getResponseProperty("Ciphers"); -+ if (ciphers != null) { -+ this.ciphers = ciphers.map((c: any) => new CipherResponse(c)); -+ } -+ -+ const domains = this.getResponseProperty("Domains"); -+ if (domains != null) { -+ this.domains = new DomainsResponse(domains); -+ } -+ -+ const policies = this.getResponseProperty("Policies"); -+ if (policies != null) { -+ this.policies = policies.map((p: any) => new PolicyResponse(p)); -+ } -+ -+ const sends = this.getResponseProperty("Sends"); -+ if (sends != null) { -+ this.sends = sends.map((s: any) => new SendResponse(s)); -+ } -+ } - } -diff --git a/jslib/common/src/models/response/taxInfoResponse.ts b/jslib/common/src/models/response/taxInfoResponse.ts -index 9759bbe3..bb4693ca 100644 ---- a/jslib/common/src/models/response/taxInfoResponse.ts -+++ b/jslib/common/src/models/response/taxInfoResponse.ts -@@ -1,24 +1,24 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class TaxInfoResponse extends BaseResponse { -- taxId: string; -- taxIdType: string; -- line1: string; -- line2: string; -- city: string; -- state: string; -- country: string; -- postalCode: string; -+ taxId: string; -+ taxIdType: string; -+ line1: string; -+ line2: string; -+ city: string; -+ state: string; -+ country: string; -+ postalCode: string; - -- constructor(response: any) { -- super(response); -- this.taxId = this.getResponseProperty('TaxIdNumber'); -- this.taxIdType = this.getResponseProperty('TaxIdType'); -- this.line1 = this.getResponseProperty('Line1'); -- this.line2 = this.getResponseProperty('Line2'); -- this.city = this.getResponseProperty('City'); -- this.state = this.getResponseProperty('State'); -- this.postalCode = this.getResponseProperty('PostalCode'); -- this.country = this.getResponseProperty('Country'); -- } -+ constructor(response: any) { -+ super(response); -+ this.taxId = this.getResponseProperty("TaxIdNumber"); -+ this.taxIdType = this.getResponseProperty("TaxIdType"); -+ this.line1 = this.getResponseProperty("Line1"); -+ this.line2 = this.getResponseProperty("Line2"); -+ this.city = this.getResponseProperty("City"); -+ this.state = this.getResponseProperty("State"); -+ this.postalCode = this.getResponseProperty("PostalCode"); -+ this.country = this.getResponseProperty("Country"); -+ } - } -diff --git a/jslib/common/src/models/response/taxRateResponse.ts b/jslib/common/src/models/response/taxRateResponse.ts -index 83970afa..28274a50 100644 ---- a/jslib/common/src/models/response/taxRateResponse.ts -+++ b/jslib/common/src/models/response/taxRateResponse.ts -@@ -1,18 +1,18 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class TaxRateResponse extends BaseResponse { -- id: string; -- country: string; -- state: string; -- postalCode: string; -- rate: number; -+ id: string; -+ country: string; -+ state: string; -+ postalCode: string; -+ rate: number; - -- constructor(response: any) { -- super(response); -- this.id = this.getResponseProperty('Id'); -- this.country = this.getResponseProperty('Country'); -- this.state = this.getResponseProperty('State'); -- this.postalCode = this.getResponseProperty('PostalCode'); -- this.rate = this.getResponseProperty('Rate'); -- } -+ constructor(response: any) { -+ super(response); -+ this.id = this.getResponseProperty("Id"); -+ this.country = this.getResponseProperty("Country"); -+ this.state = this.getResponseProperty("State"); -+ this.postalCode = this.getResponseProperty("PostalCode"); -+ this.rate = this.getResponseProperty("Rate"); -+ } - } -diff --git a/jslib/common/src/models/response/twoFactorAuthenticatorResponse.ts b/jslib/common/src/models/response/twoFactorAuthenticatorResponse.ts -index b08ecb19..8eb73eaa 100644 ---- a/jslib/common/src/models/response/twoFactorAuthenticatorResponse.ts -+++ b/jslib/common/src/models/response/twoFactorAuthenticatorResponse.ts -@@ -1,12 +1,12 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class TwoFactorAuthenticatorResponse extends BaseResponse { -- enabled: boolean; -- key: string; -+ enabled: boolean; -+ key: string; - -- constructor(response: any) { -- super(response); -- this.enabled = this.getResponseProperty('Enabled'); -- this.key = this.getResponseProperty('Key'); -- } -+ constructor(response: any) { -+ super(response); -+ this.enabled = this.getResponseProperty("Enabled"); -+ this.key = this.getResponseProperty("Key"); -+ } - } -diff --git a/jslib/common/src/models/response/twoFactorDuoResponse.ts b/jslib/common/src/models/response/twoFactorDuoResponse.ts -index 087fce48..401de796 100644 ---- a/jslib/common/src/models/response/twoFactorDuoResponse.ts -+++ b/jslib/common/src/models/response/twoFactorDuoResponse.ts -@@ -1,16 +1,16 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class TwoFactorDuoResponse extends BaseResponse { -- enabled: boolean; -- host: string; -- secretKey: string; -- integrationKey: string; -+ enabled: boolean; -+ host: string; -+ secretKey: string; -+ integrationKey: string; - -- constructor(response: any) { -- super(response); -- this.enabled = this.getResponseProperty('Enabled'); -- this.host = this.getResponseProperty('Host'); -- this.secretKey = this.getResponseProperty('SecretKey'); -- this.integrationKey = this.getResponseProperty('IntegrationKey'); -- } -+ constructor(response: any) { -+ super(response); -+ this.enabled = this.getResponseProperty("Enabled"); -+ this.host = this.getResponseProperty("Host"); -+ this.secretKey = this.getResponseProperty("SecretKey"); -+ this.integrationKey = this.getResponseProperty("IntegrationKey"); -+ } - } -diff --git a/jslib/common/src/models/response/twoFactorEmailResponse.ts b/jslib/common/src/models/response/twoFactorEmailResponse.ts -index 54f4a551..ad1936d3 100644 ---- a/jslib/common/src/models/response/twoFactorEmailResponse.ts -+++ b/jslib/common/src/models/response/twoFactorEmailResponse.ts -@@ -1,12 +1,12 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class TwoFactorEmailResponse extends BaseResponse { -- enabled: boolean; -- email: string; -+ enabled: boolean; -+ email: string; - -- constructor(response: any) { -- super(response); -- this.enabled = this.getResponseProperty('Enabled'); -- this.email = this.getResponseProperty('Email'); -- } -+ constructor(response: any) { -+ super(response); -+ this.enabled = this.getResponseProperty("Enabled"); -+ this.email = this.getResponseProperty("Email"); -+ } - } -diff --git a/jslib/common/src/models/response/twoFactorProviderResponse.ts b/jslib/common/src/models/response/twoFactorProviderResponse.ts -index 570b1d6e..6f149136 100644 ---- a/jslib/common/src/models/response/twoFactorProviderResponse.ts -+++ b/jslib/common/src/models/response/twoFactorProviderResponse.ts -@@ -1,14 +1,14 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - --import { TwoFactorProviderType } from '../../enums/twoFactorProviderType'; -+import { TwoFactorProviderType } from "../../enums/twoFactorProviderType"; - - export class TwoFactorProviderResponse extends BaseResponse { -- enabled: boolean; -- type: TwoFactorProviderType; -+ enabled: boolean; -+ type: TwoFactorProviderType; - -- constructor(response: any) { -- super(response); -- this.enabled = this.getResponseProperty('Enabled'); -- this.type = this.getResponseProperty('Type'); -- } -+ constructor(response: any) { -+ super(response); -+ this.enabled = this.getResponseProperty("Enabled"); -+ this.type = this.getResponseProperty("Type"); -+ } - } -diff --git a/jslib/common/src/models/response/twoFactorRescoverResponse.ts b/jslib/common/src/models/response/twoFactorRescoverResponse.ts -index 62bfdfe6..0e26db9b 100644 ---- a/jslib/common/src/models/response/twoFactorRescoverResponse.ts -+++ b/jslib/common/src/models/response/twoFactorRescoverResponse.ts -@@ -1,10 +1,10 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class TwoFactorRecoverResponse extends BaseResponse { -- code: string; -+ code: string; - -- constructor(response: any) { -- super(response); -- this.code = this.getResponseProperty('Code'); -- } -+ constructor(response: any) { -+ super(response); -+ this.code = this.getResponseProperty("Code"); -+ } - } -diff --git a/jslib/common/src/models/response/twoFactorWebAuthnResponse.ts b/jslib/common/src/models/response/twoFactorWebAuthnResponse.ts -index 499bdca5..9c855b76 100644 ---- a/jslib/common/src/models/response/twoFactorWebAuthnResponse.ts -+++ b/jslib/common/src/models/response/twoFactorWebAuthnResponse.ts -@@ -1,59 +1,59 @@ --import { Utils } from '../../misc/utils'; --import { BaseResponse } from './baseResponse'; -+import { Utils } from "../../misc/utils"; -+import { BaseResponse } from "./baseResponse"; - - export class TwoFactorWebAuthnResponse extends BaseResponse { -- enabled: boolean; -- keys: KeyResponse[]; -- -- constructor(response: any) { -- super(response); -- this.enabled = this.getResponseProperty('Enabled'); -- const keys = this.getResponseProperty('Keys'); -- this.keys = keys == null ? null : keys.map((k: any) => new KeyResponse(k)); -- } -+ enabled: boolean; -+ keys: KeyResponse[]; -+ -+ constructor(response: any) { -+ super(response); -+ this.enabled = this.getResponseProperty("Enabled"); -+ const keys = this.getResponseProperty("Keys"); -+ this.keys = keys == null ? null : keys.map((k: any) => new KeyResponse(k)); -+ } - } - - export class KeyResponse extends BaseResponse { -- name: string; -- id: number; -- migrated: boolean; -- -- constructor(response: any) { -- super(response); -- this.name = this.getResponseProperty('Name'); -- this.id = this.getResponseProperty('Id'); -- this.migrated = this.getResponseProperty('Migrated'); -- } -+ name: string; -+ id: number; -+ migrated: boolean; -+ -+ constructor(response: any) { -+ super(response); -+ this.name = this.getResponseProperty("Name"); -+ this.id = this.getResponseProperty("Id"); -+ this.migrated = this.getResponseProperty("Migrated"); -+ } - } - - export class ChallengeResponse extends BaseResponse implements PublicKeyCredentialCreationOptions { -- attestation?: AttestationConveyancePreference; -- authenticatorSelection?: AuthenticatorSelectionCriteria; -- challenge: BufferSource; -- excludeCredentials?: PublicKeyCredentialDescriptor[]; -- extensions?: AuthenticationExtensionsClientInputs; -- pubKeyCredParams: PublicKeyCredentialParameters[]; -- rp: PublicKeyCredentialRpEntity; -- timeout?: number; -- user: PublicKeyCredentialUserEntity; -- -- constructor(response: any) { -- super(response); -- this.attestation = this.getResponseProperty('attestation'); -- this.authenticatorSelection = this.getResponseProperty('authenticatorSelection'); -- this.challenge = Utils.fromUrlB64ToArray(this.getResponseProperty('challenge')); -- this.excludeCredentials = this.getResponseProperty('excludeCredentials').map((c: any) => { -- c.id = Utils.fromUrlB64ToArray(c.id).buffer; -- return c; -- }); -- this.extensions = this.getResponseProperty('extensions'); -- this.pubKeyCredParams = this.getResponseProperty('pubKeyCredParams'); -- this.rp = this.getResponseProperty('rp'); -- this.timeout = this.getResponseProperty('timeout'); -- -- const user = this.getResponseProperty('user'); -- user.id = Utils.fromUrlB64ToArray(user.id); -- -- this.user = user; -- } -+ attestation?: AttestationConveyancePreference; -+ authenticatorSelection?: AuthenticatorSelectionCriteria; -+ challenge: BufferSource; -+ excludeCredentials?: PublicKeyCredentialDescriptor[]; -+ extensions?: AuthenticationExtensionsClientInputs; -+ pubKeyCredParams: PublicKeyCredentialParameters[]; -+ rp: PublicKeyCredentialRpEntity; -+ timeout?: number; -+ user: PublicKeyCredentialUserEntity; -+ -+ constructor(response: any) { -+ super(response); -+ this.attestation = this.getResponseProperty("attestation"); -+ this.authenticatorSelection = this.getResponseProperty("authenticatorSelection"); -+ this.challenge = Utils.fromUrlB64ToArray(this.getResponseProperty("challenge")); -+ this.excludeCredentials = this.getResponseProperty("excludeCredentials").map((c: any) => { -+ c.id = Utils.fromUrlB64ToArray(c.id).buffer; -+ return c; -+ }); -+ this.extensions = this.getResponseProperty("extensions"); -+ this.pubKeyCredParams = this.getResponseProperty("pubKeyCredParams"); -+ this.rp = this.getResponseProperty("rp"); -+ this.timeout = this.getResponseProperty("timeout"); -+ -+ const user = this.getResponseProperty("user"); -+ user.id = Utils.fromUrlB64ToArray(user.id); -+ -+ this.user = user; -+ } - } -diff --git a/jslib/common/src/models/response/twoFactorYubiKeyResponse.ts b/jslib/common/src/models/response/twoFactorYubiKeyResponse.ts -index 92d8cffb..ee75074b 100644 ---- a/jslib/common/src/models/response/twoFactorYubiKeyResponse.ts -+++ b/jslib/common/src/models/response/twoFactorYubiKeyResponse.ts -@@ -1,22 +1,22 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class TwoFactorYubiKeyResponse extends BaseResponse { -- enabled: boolean; -- key1: string; -- key2: string; -- key3: string; -- key4: string; -- key5: string; -- nfc: boolean; -+ enabled: boolean; -+ key1: string; -+ key2: string; -+ key3: string; -+ key4: string; -+ key5: string; -+ nfc: boolean; - -- constructor(response: any) { -- super(response); -- this.enabled = this.getResponseProperty('Enabled'); -- this.key1 = this.getResponseProperty('Key1'); -- this.key2 = this.getResponseProperty('Key2'); -- this.key3 = this.getResponseProperty('Key3'); -- this.key4 = this.getResponseProperty('Key4'); -- this.key5 = this.getResponseProperty('Key5'); -- this.nfc = this.getResponseProperty('Nfc'); -- } -+ constructor(response: any) { -+ super(response); -+ this.enabled = this.getResponseProperty("Enabled"); -+ this.key1 = this.getResponseProperty("Key1"); -+ this.key2 = this.getResponseProperty("Key2"); -+ this.key3 = this.getResponseProperty("Key3"); -+ this.key4 = this.getResponseProperty("Key4"); -+ this.key5 = this.getResponseProperty("Key5"); -+ this.nfc = this.getResponseProperty("Nfc"); -+ } - } -diff --git a/jslib/common/src/models/response/userKeyResponse.ts b/jslib/common/src/models/response/userKeyResponse.ts -index 66af3639..5550abdf 100644 ---- a/jslib/common/src/models/response/userKeyResponse.ts -+++ b/jslib/common/src/models/response/userKeyResponse.ts -@@ -1,12 +1,12 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class UserKeyResponse extends BaseResponse { -- userId: string; -- publicKey: string; -+ userId: string; -+ publicKey: string; - -- constructor(response: any) { -- super(response); -- this.userId = this.getResponseProperty('UserId'); -- this.publicKey = this.getResponseProperty('PublicKey'); -- } -+ constructor(response: any) { -+ super(response); -+ this.userId = this.getResponseProperty("UserId"); -+ this.publicKey = this.getResponseProperty("PublicKey"); -+ } - } -diff --git a/jslib/common/src/models/view/attachmentView.ts b/jslib/common/src/models/view/attachmentView.ts -index 45c04f48..7bf8186f 100644 ---- a/jslib/common/src/models/view/attachmentView.ts -+++ b/jslib/common/src/models/view/attachmentView.ts -@@ -1,35 +1,35 @@ --import { View } from './view'; -+import { View } from "./view"; - --import { Attachment } from '../domain/attachment'; --import { SymmetricCryptoKey } from '../domain/symmetricCryptoKey'; -+import { Attachment } from "../domain/attachment"; -+import { SymmetricCryptoKey } from "../domain/symmetricCryptoKey"; - - export class AttachmentView implements View { -- id: string = null; -- url: string = null; -- size: string = null; -- sizeName: string = null; -- fileName: string = null; -- key: SymmetricCryptoKey = null; -+ id: string = null; -+ url: string = null; -+ size: string = null; -+ sizeName: string = null; -+ fileName: string = null; -+ key: SymmetricCryptoKey = null; - -- constructor(a?: Attachment) { -- if (!a) { -- return; -- } -- -- this.id = a.id; -- this.url = a.url; -- this.size = a.size; -- this.sizeName = a.sizeName; -+ constructor(a?: Attachment) { -+ if (!a) { -+ return; - } - -- get fileSize(): number { -- try { -- if (this.size != null) { -- return parseInt(this.size, null); -- } -- } catch { -- // Invalid file size. -- } -- return 0; -+ this.id = a.id; -+ this.url = a.url; -+ this.size = a.size; -+ this.sizeName = a.sizeName; -+ } -+ -+ get fileSize(): number { -+ try { -+ if (this.size != null) { -+ return parseInt(this.size, null); -+ } -+ } catch { -+ // Invalid file size. - } -+ return 0; -+ } - } -diff --git a/jslib/common/src/models/view/cardView.ts b/jslib/common/src/models/view/cardView.ts -index 94e7ae49..4aa3a999 100644 ---- a/jslib/common/src/models/view/cardView.ts -+++ b/jslib/common/src/models/view/cardView.ts -@@ -1,86 +1,87 @@ --import { ItemView } from './itemView'; -+import { ItemView } from "./itemView"; - --import { Card } from '../domain/card'; -+import { Card } from "../domain/card"; - --import { CardLinkedId as LinkedId } from '../../enums/linkedIdType'; -+import { CardLinkedId as LinkedId } from "../../enums/linkedIdType"; - --import { linkedFieldOption } from '../../misc/linkedFieldOption.decorator'; -+import { linkedFieldOption } from "../../misc/linkedFieldOption.decorator"; - - export class CardView extends ItemView { -- @linkedFieldOption(LinkedId.CardholderName) -- cardholderName: string = null; -- @linkedFieldOption(LinkedId.ExpMonth, 'expirationMonth') -- expMonth: string = null; -- @linkedFieldOption(LinkedId.ExpYear, 'expirationYear') -- expYear: string = null; -- @linkedFieldOption(LinkedId.Code, 'securityCode') -- code: string = null; -+ @linkedFieldOption(LinkedId.CardholderName) -+ cardholderName: string = null; -+ @linkedFieldOption(LinkedId.ExpMonth, "expirationMonth") -+ expMonth: string = null; -+ @linkedFieldOption(LinkedId.ExpYear, "expirationYear") -+ expYear: string = null; -+ @linkedFieldOption(LinkedId.Code, "securityCode") -+ code: string = null; - -- // tslint:disable -- private _brand: string = null; -- private _number: string = null; -- private _subTitle: string = null; -- // tslint:enable -+ // tslint:disable -+ private _brand: string = null; -+ private _number: string = null; -+ private _subTitle: string = null; -+ // tslint:enable - -- constructor(c?: Card) { -- super(); -- } -+ constructor(c?: Card) { -+ super(); -+ } - -- get maskedCode(): string { -- return this.code != null ? '•'.repeat(this.code.length) : null; -- } -+ get maskedCode(): string { -+ return this.code != null ? "•".repeat(this.code.length) : null; -+ } - -- get maskedNumber(): string { -- return this.number != null ? '•'.repeat(this.number.length) : null; -- } -+ get maskedNumber(): string { -+ return this.number != null ? "•".repeat(this.number.length) : null; -+ } - -- @linkedFieldOption(LinkedId.Brand) -- get brand(): string { -- return this._brand; -- } -- set brand(value: string) { -- this._brand = value; -- this._subTitle = null; -- } -+ @linkedFieldOption(LinkedId.Brand) -+ get brand(): string { -+ return this._brand; -+ } -+ set brand(value: string) { -+ this._brand = value; -+ this._subTitle = null; -+ } - -- @linkedFieldOption(LinkedId.Number) -- get number(): string { -- return this._number; -- } -- set number(value: string) { -- this._number = value; -- this._subTitle = null; -- } -+ @linkedFieldOption(LinkedId.Number) -+ get number(): string { -+ return this._number; -+ } -+ set number(value: string) { -+ this._number = value; -+ this._subTitle = null; -+ } - -- get subTitle(): string { -- if (this._subTitle == null) { -- this._subTitle = this.brand; -- if (this.number != null && this.number.length >= 4) { -- if (this._subTitle != null && this._subTitle !== '') { -- this._subTitle += ', '; -- } else { -- this._subTitle = ''; -- } -- -- // Show last 5 on amex, last 4 for all others -- const count = this.number.length >= 5 && this.number.match(new RegExp('^3[47]')) != null ? 5 : 4; -- this._subTitle += ('*' + this.number.substr(this.number.length - count)); -- } -- } -- return this._subTitle; -- } -- -- get expiration(): string { -- if (!this.expMonth && !this.expYear) { -- return null; -+ get subTitle(): string { -+ if (this._subTitle == null) { -+ this._subTitle = this.brand; -+ if (this.number != null && this.number.length >= 4) { -+ if (this._subTitle != null && this._subTitle !== "") { -+ this._subTitle += ", "; -+ } else { -+ this._subTitle = ""; - } - -- let exp = this.expMonth != null ? ('0' + this.expMonth).slice(-2) : '__'; -- exp += (' / ' + (this.expYear != null ? this.formatYear(this.expYear) : '____')); -- return exp; -+ // Show last 5 on amex, last 4 for all others -+ const count = -+ this.number.length >= 5 && this.number.match(new RegExp("^3[47]")) != null ? 5 : 4; -+ this._subTitle += "*" + this.number.substr(this.number.length - count); -+ } - } -+ return this._subTitle; -+ } - -- private formatYear(year: string): string { -- return year.length === 2 ? '20' + year : year; -+ get expiration(): string { -+ if (!this.expMonth && !this.expYear) { -+ return null; - } -+ -+ let exp = this.expMonth != null ? ("0" + this.expMonth).slice(-2) : "__"; -+ exp += " / " + (this.expYear != null ? this.formatYear(this.expYear) : "____"); -+ return exp; -+ } -+ -+ private formatYear(year: string): string { -+ return year.length === 2 ? "20" + year : year; -+ } - } -diff --git a/jslib/common/src/models/view/cipherView.ts b/jslib/common/src/models/view/cipherView.ts -index e4e5c3f1..073b2a2c 100644 ---- a/jslib/common/src/models/view/cipherView.ts -+++ b/jslib/common/src/models/view/cipherView.ts -@@ -1,136 +1,136 @@ --import { CipherRepromptType } from '../../enums/cipherRepromptType'; --import { CipherType } from '../../enums/cipherType'; --import { LinkedIdType } from '../../enums/linkedIdType'; -- --import { Cipher } from '../domain/cipher'; -- --import { AttachmentView } from './attachmentView'; --import { CardView } from './cardView'; --import { FieldView } from './fieldView'; --import { IdentityView } from './identityView'; --import { ItemView } from './itemView'; --import { LoginView } from './loginView'; --import { PasswordHistoryView } from './passwordHistoryView'; --import { SecureNoteView } from './secureNoteView'; --import { View } from './view'; -+import { CipherRepromptType } from "../../enums/cipherRepromptType"; -+import { CipherType } from "../../enums/cipherType"; -+import { LinkedIdType } from "../../enums/linkedIdType"; -+ -+import { Cipher } from "../domain/cipher"; -+ -+import { AttachmentView } from "./attachmentView"; -+import { CardView } from "./cardView"; -+import { FieldView } from "./fieldView"; -+import { IdentityView } from "./identityView"; -+import { ItemView } from "./itemView"; -+import { LoginView } from "./loginView"; -+import { PasswordHistoryView } from "./passwordHistoryView"; -+import { SecureNoteView } from "./secureNoteView"; -+import { View } from "./view"; - - export class CipherView implements View { -- id: string = null; -- organizationId: string = null; -- folderId: string = null; -- name: string = null; -- notes: string = null; -- type: CipherType = null; -- favorite = false; -- organizationUseTotp = false; -- edit = false; -- viewPassword = true; -- localData: any; -- login = new LoginView(); -- identity = new IdentityView(); -- card = new CardView(); -- secureNote = new SecureNoteView(); -- attachments: AttachmentView[] = null; -- fields: FieldView[] = null; -- passwordHistory: PasswordHistoryView[] = null; -- collectionIds: string[] = null; -- revisionDate: Date = null; -- deletedDate: Date = null; -- reprompt: CipherRepromptType = CipherRepromptType.None; -- -- constructor(c?: Cipher) { -- if (!c) { -- return; -- } -- -- this.id = c.id; -- this.organizationId = c.organizationId; -- this.folderId = c.folderId; -- this.favorite = c.favorite; -- this.organizationUseTotp = c.organizationUseTotp; -- this.edit = c.edit; -- this.viewPassword = c.viewPassword; -- this.type = c.type; -- this.localData = c.localData; -- this.collectionIds = c.collectionIds; -- this.revisionDate = c.revisionDate; -- this.deletedDate = c.deletedDate; -- // Old locally stored ciphers might have reprompt == null. If so set it to None. -- this.reprompt = c.reprompt ?? CipherRepromptType.None; -- } -- -- private get item() { -- switch (this.type) { -- case CipherType.Login: -- return this.login; -- case CipherType.SecureNote: -- return this.secureNote; -- case CipherType.Card: -- return this.card; -- case CipherType.Identity: -- return this.identity; -- default: -- break; -- } -- -- return null; -+ id: string = null; -+ organizationId: string = null; -+ folderId: string = null; -+ name: string = null; -+ notes: string = null; -+ type: CipherType = null; -+ favorite = false; -+ organizationUseTotp = false; -+ edit = false; -+ viewPassword = true; -+ localData: any; -+ login = new LoginView(); -+ identity = new IdentityView(); -+ card = new CardView(); -+ secureNote = new SecureNoteView(); -+ attachments: AttachmentView[] = null; -+ fields: FieldView[] = null; -+ passwordHistory: PasswordHistoryView[] = null; -+ collectionIds: string[] = null; -+ revisionDate: Date = null; -+ deletedDate: Date = null; -+ reprompt: CipherRepromptType = CipherRepromptType.None; -+ -+ constructor(c?: Cipher) { -+ if (!c) { -+ return; - } - -- get subTitle(): string { -- return this.item.subTitle; -+ this.id = c.id; -+ this.organizationId = c.organizationId; -+ this.folderId = c.folderId; -+ this.favorite = c.favorite; -+ this.organizationUseTotp = c.organizationUseTotp; -+ this.edit = c.edit; -+ this.viewPassword = c.viewPassword; -+ this.type = c.type; -+ this.localData = c.localData; -+ this.collectionIds = c.collectionIds; -+ this.revisionDate = c.revisionDate; -+ this.deletedDate = c.deletedDate; -+ // Old locally stored ciphers might have reprompt == null. If so set it to None. -+ this.reprompt = c.reprompt ?? CipherRepromptType.None; -+ } -+ -+ private get item() { -+ switch (this.type) { -+ case CipherType.Login: -+ return this.login; -+ case CipherType.SecureNote: -+ return this.secureNote; -+ case CipherType.Card: -+ return this.card; -+ case CipherType.Identity: -+ return this.identity; -+ default: -+ break; - } - -- get hasPasswordHistory(): boolean { -- return this.passwordHistory && this.passwordHistory.length > 0; -- } -+ return null; -+ } - -- get hasAttachments(): boolean { -- return this.attachments && this.attachments.length > 0; -- } -+ get subTitle(): string { -+ return this.item.subTitle; -+ } - -- get hasOldAttachments(): boolean { -- if (this.hasAttachments) { -- for (let i = 0; i < this.attachments.length; i++) { -- if (this.attachments[i].key == null) { -- return true; -- } -- } -- } -- return false; -- } -+ get hasPasswordHistory(): boolean { -+ return this.passwordHistory && this.passwordHistory.length > 0; -+ } - -- get hasFields(): boolean { -- return this.fields && this.fields.length > 0; -- } -+ get hasAttachments(): boolean { -+ return this.attachments && this.attachments.length > 0; -+ } - -- get passwordRevisionDisplayDate(): Date { -- if (this.type !== CipherType.Login || this.login == null) { -- return null; -- } else if (this.login.password == null || this.login.password === '') { -- return null; -+ get hasOldAttachments(): boolean { -+ if (this.hasAttachments) { -+ for (let i = 0; i < this.attachments.length; i++) { -+ if (this.attachments[i].key == null) { -+ return true; - } -- return this.login.passwordRevisionDate; -+ } - } -- -- get isDeleted(): boolean { -- return this.deletedDate != null; -+ return false; -+ } -+ -+ get hasFields(): boolean { -+ return this.fields && this.fields.length > 0; -+ } -+ -+ get passwordRevisionDisplayDate(): Date { -+ if (this.type !== CipherType.Login || this.login == null) { -+ return null; -+ } else if (this.login.password == null || this.login.password === "") { -+ return null; - } -+ return this.login.passwordRevisionDate; -+ } - -- get linkedFieldOptions() { -- return this.item.linkedFieldOptions; -- } -+ get isDeleted(): boolean { -+ return this.deletedDate != null; -+ } - -- linkedFieldValue(id: LinkedIdType) { -- const linkedFieldOption = this.linkedFieldOptions?.get(id); -- if (linkedFieldOption == null) { -- return null; -- } -+ get linkedFieldOptions() { -+ return this.item.linkedFieldOptions; -+ } - -- const item = this.item; -- return this.item[linkedFieldOption.propertyKey as keyof typeof item]; -+ linkedFieldValue(id: LinkedIdType) { -+ const linkedFieldOption = this.linkedFieldOptions?.get(id); -+ if (linkedFieldOption == null) { -+ return null; - } - -- linkedFieldI18nKey(id: LinkedIdType): string { -- return this.linkedFieldOptions.get(id)?.i18nKey; -- } -+ const item = this.item; -+ return this.item[linkedFieldOption.propertyKey as keyof typeof item]; -+ } -+ -+ linkedFieldI18nKey(id: LinkedIdType): string { -+ return this.linkedFieldOptions.get(id)?.i18nKey; -+ } - } -diff --git a/jslib/common/src/models/view/collectionView.ts b/jslib/common/src/models/view/collectionView.ts -index 9c27c9fb..0f580837 100644 ---- a/jslib/common/src/models/view/collectionView.ts -+++ b/jslib/common/src/models/view/collectionView.ts -@@ -1,29 +1,29 @@ --import { View } from './view'; -+import { View } from "./view"; - --import { Collection } from '../domain/collection'; --import { ITreeNodeObject } from '../domain/treeNode'; -+import { Collection } from "../domain/collection"; -+import { ITreeNodeObject } from "../domain/treeNode"; - --import { CollectionGroupDetailsResponse } from '../response/collectionResponse'; -+import { CollectionGroupDetailsResponse } from "../response/collectionResponse"; - - export class CollectionView implements View, ITreeNodeObject { -- id: string = null; -- organizationId: string = null; -- name: string = null; -- externalId: string = null; -- readOnly: boolean = null; -- hidePasswords: boolean = null; -+ id: string = null; -+ organizationId: string = null; -+ name: string = null; -+ externalId: string = null; -+ readOnly: boolean = null; -+ hidePasswords: boolean = null; - -- constructor(c?: Collection | CollectionGroupDetailsResponse) { -- if (!c) { -- return; -- } -+ constructor(c?: Collection | CollectionGroupDetailsResponse) { -+ if (!c) { -+ return; -+ } - -- this.id = c.id; -- this.organizationId = c.organizationId; -- this.externalId = c.externalId; -- if (c instanceof Collection) { -- this.readOnly = c.readOnly; -- this.hidePasswords = c.hidePasswords; -- } -+ this.id = c.id; -+ this.organizationId = c.organizationId; -+ this.externalId = c.externalId; -+ if (c instanceof Collection) { -+ this.readOnly = c.readOnly; -+ this.hidePasswords = c.hidePasswords; - } -+ } - } -diff --git a/jslib/common/src/models/view/eventView.ts b/jslib/common/src/models/view/eventView.ts -index 39e40c4d..17339ecb 100644 ---- a/jslib/common/src/models/view/eventView.ts -+++ b/jslib/common/src/models/view/eventView.ts -@@ -1,27 +1,27 @@ --import { EventType } from '../../enums/eventType'; -+import { EventType } from "../../enums/eventType"; - - export class EventView { -- message: string; -- humanReadableMessage: string; -- appIcon: string; -- appName: string; -- userId: string; -- userName: string; -- userEmail: string; -- date: string; -- ip: string; -- type: EventType; -+ message: string; -+ humanReadableMessage: string; -+ appIcon: string; -+ appName: string; -+ userId: string; -+ userName: string; -+ userEmail: string; -+ date: string; -+ ip: string; -+ type: EventType; - -- constructor(data: Required) { -- this.message = data.message; -- this.humanReadableMessage = data.humanReadableMessage; -- this.appIcon = data.appIcon; -- this.appName = data.appName; -- this.userId = data.userId; -- this.userName = data.userName; -- this.userEmail = data.userEmail; -- this.date = data.date; -- this.ip = data.ip; -- this.type = data.type; -- } -+ constructor(data: Required) { -+ this.message = data.message; -+ this.humanReadableMessage = data.humanReadableMessage; -+ this.appIcon = data.appIcon; -+ this.appName = data.appName; -+ this.userId = data.userId; -+ this.userName = data.userName; -+ this.userEmail = data.userEmail; -+ this.date = data.date; -+ this.ip = data.ip; -+ this.type = data.type; -+ } - } -diff --git a/jslib/common/src/models/view/fieldView.ts b/jslib/common/src/models/view/fieldView.ts -index 3202d4b1..c1c1b1e4 100644 ---- a/jslib/common/src/models/view/fieldView.ts -+++ b/jslib/common/src/models/view/fieldView.ts -@@ -1,28 +1,28 @@ --import { FieldType } from '../../enums/fieldType'; --import { LinkedIdType } from '../../enums/linkedIdType'; -+import { FieldType } from "../../enums/fieldType"; -+import { LinkedIdType } from "../../enums/linkedIdType"; - --import { View } from './view'; -+import { View } from "./view"; - --import { Field } from '../domain/field'; -+import { Field } from "../domain/field"; - - export class FieldView implements View { -- name: string = null; -- value: string = null; -- type: FieldType = null; -- newField: boolean = false; // Marks if the field is new and hasn't been saved -- showValue: boolean = false; -- linkedId: LinkedIdType = null; -+ name: string = null; -+ value: string = null; -+ type: FieldType = null; -+ newField: boolean = false; // Marks if the field is new and hasn't been saved -+ showValue: boolean = false; -+ linkedId: LinkedIdType = null; - -- constructor(f?: Field) { -- if (!f) { -- return; -- } -- -- this.type = f.type; -- this.linkedId = f.linkedId; -+ constructor(f?: Field) { -+ if (!f) { -+ return; - } - -- get maskedValue(): string { -- return this.value != null ? '••••••••' : null; -- } -+ this.type = f.type; -+ this.linkedId = f.linkedId; -+ } -+ -+ get maskedValue(): string { -+ return this.value != null ? "••••••••" : null; -+ } - } -diff --git a/jslib/common/src/models/view/folderView.ts b/jslib/common/src/models/view/folderView.ts -index 1446a972..5ecd8822 100644 ---- a/jslib/common/src/models/view/folderView.ts -+++ b/jslib/common/src/models/view/folderView.ts -@@ -1,19 +1,19 @@ --import { View } from './view'; -+import { View } from "./view"; - --import { Folder } from '../domain/folder'; --import { ITreeNodeObject } from '../domain/treeNode'; -+import { Folder } from "../domain/folder"; -+import { ITreeNodeObject } from "../domain/treeNode"; - - export class FolderView implements View, ITreeNodeObject { -- id: string = null; -- name: string = null; -- revisionDate: Date = null; -+ id: string = null; -+ name: string = null; -+ revisionDate: Date = null; - -- constructor(f?: Folder) { -- if (!f) { -- return; -- } -- -- this.id = f.id; -- this.revisionDate = f.revisionDate; -+ constructor(f?: Folder) { -+ if (!f) { -+ return; - } -+ -+ this.id = f.id; -+ this.revisionDate = f.revisionDate; -+ } - } -diff --git a/jslib/common/src/models/view/identityView.ts b/jslib/common/src/models/view/identityView.ts -index 844a8acd..10e6616e 100644 ---- a/jslib/common/src/models/view/identityView.ts -+++ b/jslib/common/src/models/view/identityView.ts -@@ -1,143 +1,148 @@ --import { ItemView } from './itemView'; -+import { ItemView } from "./itemView"; - --import { Identity } from '../domain/identity'; -+import { Identity } from "../domain/identity"; - --import { Utils } from '../../misc/utils'; -+import { Utils } from "../../misc/utils"; - --import { IdentityLinkedId as LinkedId } from '../../enums/linkedIdType'; -+import { IdentityLinkedId as LinkedId } from "../../enums/linkedIdType"; - --import { linkedFieldOption } from '../../misc/linkedFieldOption.decorator'; -+import { linkedFieldOption } from "../../misc/linkedFieldOption.decorator"; - - export class IdentityView extends ItemView { -- @linkedFieldOption(LinkedId.Title) -- title: string = null; -- @linkedFieldOption(LinkedId.MiddleName) -- middleName: string = null; -- @linkedFieldOption(LinkedId.Address1) -- address1: string = null; -- @linkedFieldOption(LinkedId.Address2) -- address2: string = null; -- @linkedFieldOption(LinkedId.Address3) -- address3: string = null; -- @linkedFieldOption(LinkedId.City, 'cityTown') -- city: string = null; -- @linkedFieldOption(LinkedId.State, 'stateProvince') -- state: string = null; -- @linkedFieldOption(LinkedId.PostalCode, 'zipPostalCode') -- postalCode: string = null; -- @linkedFieldOption(LinkedId.Country) -- country: string = null; -- @linkedFieldOption(LinkedId.Company) -- company: string = null; -- @linkedFieldOption(LinkedId.Email) -- email: string = null; -- @linkedFieldOption(LinkedId.Phone) -- phone: string = null; -- @linkedFieldOption(LinkedId.Ssn) -- ssn: string = null; -- @linkedFieldOption(LinkedId.Username) -- username: string = null; -- @linkedFieldOption(LinkedId.PassportNumber) -- passportNumber: string = null; -- @linkedFieldOption(LinkedId.LicenseNumber) -- licenseNumber: string = null; -+ @linkedFieldOption(LinkedId.Title) -+ title: string = null; -+ @linkedFieldOption(LinkedId.MiddleName) -+ middleName: string = null; -+ @linkedFieldOption(LinkedId.Address1) -+ address1: string = null; -+ @linkedFieldOption(LinkedId.Address2) -+ address2: string = null; -+ @linkedFieldOption(LinkedId.Address3) -+ address3: string = null; -+ @linkedFieldOption(LinkedId.City, "cityTown") -+ city: string = null; -+ @linkedFieldOption(LinkedId.State, "stateProvince") -+ state: string = null; -+ @linkedFieldOption(LinkedId.PostalCode, "zipPostalCode") -+ postalCode: string = null; -+ @linkedFieldOption(LinkedId.Country) -+ country: string = null; -+ @linkedFieldOption(LinkedId.Company) -+ company: string = null; -+ @linkedFieldOption(LinkedId.Email) -+ email: string = null; -+ @linkedFieldOption(LinkedId.Phone) -+ phone: string = null; -+ @linkedFieldOption(LinkedId.Ssn) -+ ssn: string = null; -+ @linkedFieldOption(LinkedId.Username) -+ username: string = null; -+ @linkedFieldOption(LinkedId.PassportNumber) -+ passportNumber: string = null; -+ @linkedFieldOption(LinkedId.LicenseNumber) -+ licenseNumber: string = null; - -- // tslint:disable -- private _firstName: string = null; -- private _lastName: string = null; -- private _subTitle: string = null; -- // tslint:enable -+ // tslint:disable -+ private _firstName: string = null; -+ private _lastName: string = null; -+ private _subTitle: string = null; -+ // tslint:enable - -- constructor(i?: Identity) { -- super(); -- } -+ constructor(i?: Identity) { -+ super(); -+ } - -- @linkedFieldOption(LinkedId.FirstName) -- get firstName(): string { -- return this._firstName; -- } -- set firstName(value: string) { -- this._firstName = value; -- this._subTitle = null; -- } -+ @linkedFieldOption(LinkedId.FirstName) -+ get firstName(): string { -+ return this._firstName; -+ } -+ set firstName(value: string) { -+ this._firstName = value; -+ this._subTitle = null; -+ } - -- @linkedFieldOption(LinkedId.LastName) -- get lastName(): string { -- return this._lastName; -- } -- set lastName(value: string) { -- this._lastName = value; -- this._subTitle = null; -- } -+ @linkedFieldOption(LinkedId.LastName) -+ get lastName(): string { -+ return this._lastName; -+ } -+ set lastName(value: string) { -+ this._lastName = value; -+ this._subTitle = null; -+ } - -- get subTitle(): string { -- if (this._subTitle == null && (this.firstName != null || this.lastName != null)) { -- this._subTitle = ''; -- if (this.firstName != null) { -- this._subTitle = this.firstName; -- } -- if (this.lastName != null) { -- if (this._subTitle !== '') { -- this._subTitle += ' '; -- } -- this._subTitle += this.lastName; -- } -+ get subTitle(): string { -+ if (this._subTitle == null && (this.firstName != null || this.lastName != null)) { -+ this._subTitle = ""; -+ if (this.firstName != null) { -+ this._subTitle = this.firstName; -+ } -+ if (this.lastName != null) { -+ if (this._subTitle !== "") { -+ this._subTitle += " "; - } -- -- return this._subTitle; -+ this._subTitle += this.lastName; -+ } - } - -- @linkedFieldOption(LinkedId.FullName) -- get fullName(): string { -- if (this.title != null || this.firstName != null || this.middleName != null || this.lastName != null) { -- let name = ''; -- if (this.title != null) { -- name += (this.title + ' '); -- } -- if (this.firstName != null) { -- name += (this.firstName + ' '); -- } -- if (this.middleName != null) { -- name += (this.middleName + ' '); -- } -- if (this.lastName != null) { -- name += this.lastName; -- } -- return name.trim(); -- } -+ return this._subTitle; -+ } - -- return null; -+ @linkedFieldOption(LinkedId.FullName) -+ get fullName(): string { -+ if ( -+ this.title != null || -+ this.firstName != null || -+ this.middleName != null || -+ this.lastName != null -+ ) { -+ let name = ""; -+ if (this.title != null) { -+ name += this.title + " "; -+ } -+ if (this.firstName != null) { -+ name += this.firstName + " "; -+ } -+ if (this.middleName != null) { -+ name += this.middleName + " "; -+ } -+ if (this.lastName != null) { -+ name += this.lastName; -+ } -+ return name.trim(); - } - -- get fullAddress(): string { -- let address = this.address1; -- if (!Utils.isNullOrWhitespace(this.address2)) { -- if (!Utils.isNullOrWhitespace(address)) { -- address += ', '; -- } -- address += this.address2; -- } -- if (!Utils.isNullOrWhitespace(this.address3)) { -- if (!Utils.isNullOrWhitespace(address)) { -- address += ', '; -- } -- address += this.address3; -- } -- return address; -+ return null; -+ } -+ -+ get fullAddress(): string { -+ let address = this.address1; -+ if (!Utils.isNullOrWhitespace(this.address2)) { -+ if (!Utils.isNullOrWhitespace(address)) { -+ address += ", "; -+ } -+ address += this.address2; -+ } -+ if (!Utils.isNullOrWhitespace(this.address3)) { -+ if (!Utils.isNullOrWhitespace(address)) { -+ address += ", "; -+ } -+ address += this.address3; - } -+ return address; -+ } - -- get fullAddressPart2(): string { -- if (this.city == null && this.state == null && this.postalCode == null) { -- return null; -- } -- const city = this.city || '-'; -- const state = this.state; -- const postalCode = this.postalCode || '-'; -- let addressPart2 = city; -- if (!Utils.isNullOrWhitespace(state)) { -- addressPart2 += ', ' + state; -- } -- addressPart2 += ', ' + postalCode; -- return addressPart2; -+ get fullAddressPart2(): string { -+ if (this.city == null && this.state == null && this.postalCode == null) { -+ return null; -+ } -+ const city = this.city || "-"; -+ const state = this.state; -+ const postalCode = this.postalCode || "-"; -+ let addressPart2 = city; -+ if (!Utils.isNullOrWhitespace(state)) { -+ addressPart2 += ", " + state; - } -+ addressPart2 += ", " + postalCode; -+ return addressPart2; -+ } - } -diff --git a/jslib/common/src/models/view/itemView.ts b/jslib/common/src/models/view/itemView.ts -index aaeac473..edd85b7d 100644 ---- a/jslib/common/src/models/view/itemView.ts -+++ b/jslib/common/src/models/view/itemView.ts -@@ -1,8 +1,8 @@ --import { View } from './view'; -+import { View } from "./view"; - --import { LinkedMetadata } from '../../misc/linkedFieldOption.decorator'; -+import { LinkedMetadata } from "../../misc/linkedFieldOption.decorator"; - - export abstract class ItemView implements View { -- linkedFieldOptions: Map; -- abstract get subTitle(): string; -+ linkedFieldOptions: Map; -+ abstract get subTitle(): string; - } -diff --git a/jslib/common/src/models/view/loginUriView.ts b/jslib/common/src/models/view/loginUriView.ts -index eab4806b..cd5dc2b8 100644 ---- a/jslib/common/src/models/view/loginUriView.ts -+++ b/jslib/common/src/models/view/loginUriView.ts -@@ -1,125 +1,131 @@ --import { UriMatchType } from '../../enums/uriMatchType'; -+import { UriMatchType } from "../../enums/uriMatchType"; - --import { View } from './view'; -+import { View } from "./view"; - --import { LoginUri } from '../domain/loginUri'; -+import { LoginUri } from "../domain/loginUri"; - --import { Utils } from '../../misc/utils'; -+import { Utils } from "../../misc/utils"; - - const CanLaunchWhitelist = [ -- 'https://', -- 'http://', -- 'ssh://', -- 'ftp://', -- 'sftp://', -- 'irc://', -- 'vnc://', -- // https://docs.microsoft.com/en-us/windows-server/remote/remote-desktop-services/clients/remote-desktop-uri -- 'rdp://', // Legacy RDP URI scheme -- 'ms-rd:', // Preferred RDP URI scheme -- 'chrome://', -- 'iosapp://', -- 'androidapp://', -+ "https://", -+ "http://", -+ "ssh://", -+ "ftp://", -+ "sftp://", -+ "irc://", -+ "vnc://", -+ // https://docs.microsoft.com/en-us/windows-server/remote/remote-desktop-services/clients/remote-desktop-uri -+ "rdp://", // Legacy RDP URI scheme -+ "ms-rd:", // Preferred RDP URI scheme -+ "chrome://", -+ "iosapp://", -+ "androidapp://", - ]; - - export class LoginUriView implements View { -- match: UriMatchType = null; -- -- // tslint:disable -- private _uri: string = null; -- private _domain: string = null; -- private _hostname: string = null; -- private _host: string = null; -- private _canLaunch: boolean = null; -- // tslint:enable -- -- constructor(u?: LoginUri) { -- if (!u) { -- return; -- } -- -- this.match = u.match; -+ match: UriMatchType = null; -+ -+ // tslint:disable -+ private _uri: string = null; -+ private _domain: string = null; -+ private _hostname: string = null; -+ private _host: string = null; -+ private _canLaunch: boolean = null; -+ // tslint:enable -+ -+ constructor(u?: LoginUri) { -+ if (!u) { -+ return; - } - -- get uri(): string { -- return this._uri; -- } -- set uri(value: string) { -- this._uri = value; -+ this.match = u.match; -+ } -+ -+ get uri(): string { -+ return this._uri; -+ } -+ set uri(value: string) { -+ this._uri = value; -+ this._domain = null; -+ this._canLaunch = null; -+ } -+ -+ get domain(): string { -+ if (this._domain == null && this.uri != null) { -+ this._domain = Utils.getDomain(this.uri); -+ if (this._domain === "") { - this._domain = null; -- this._canLaunch = null; -+ } - } - -- get domain(): string { -- if (this._domain == null && this.uri != null) { -- this._domain = Utils.getDomain(this.uri); -- if (this._domain === '') { -- this._domain = null; -- } -- } -+ return this._domain; -+ } - -- return this._domain; -+ get hostname(): string { -+ if (this.match === UriMatchType.RegularExpression) { -+ return null; - } -- -- get hostname(): string { -- if (this.match === UriMatchType.RegularExpression) { -- return null; -- } -- if (this._hostname == null && this.uri != null) { -- this._hostname = Utils.getHostname(this.uri); -- if (this._hostname === '') { -- this._hostname = null; -- } -- } -- -- return this._hostname; -+ if (this._hostname == null && this.uri != null) { -+ this._hostname = Utils.getHostname(this.uri); -+ if (this._hostname === "") { -+ this._hostname = null; -+ } - } - -- get host(): string { -- if (this.match === UriMatchType.RegularExpression) { -- return null; -- } -- if (this._host == null && this.uri != null) { -- this._host = Utils.getHost(this.uri); -- if (this._host === '') { -- this._host = null; -- } -- } -- -- return this._host; -- } -+ return this._hostname; -+ } - -- get hostnameOrUri(): string { -- return this.hostname != null ? this.hostname : this.uri; -+ get host(): string { -+ if (this.match === UriMatchType.RegularExpression) { -+ return null; - } -- -- get hostOrUri(): string { -- return this.host != null ? this.host : this.uri; -+ if (this._host == null && this.uri != null) { -+ this._host = Utils.getHost(this.uri); -+ if (this._host === "") { -+ this._host = null; -+ } - } - -- get isWebsite(): boolean { -- return this.uri != null && (this.uri.indexOf('http://') === 0 || this.uri.indexOf('https://') === 0 || -- (this.uri.indexOf('://') < 0 && Utils.tldEndingRegex.test(this.uri))); -+ return this._host; -+ } -+ -+ get hostnameOrUri(): string { -+ return this.hostname != null ? this.hostname : this.uri; -+ } -+ -+ get hostOrUri(): string { -+ return this.host != null ? this.host : this.uri; -+ } -+ -+ get isWebsite(): boolean { -+ return ( -+ this.uri != null && -+ (this.uri.indexOf("http://") === 0 || -+ this.uri.indexOf("https://") === 0 || -+ (this.uri.indexOf("://") < 0 && Utils.tldEndingRegex.test(this.uri))) -+ ); -+ } -+ -+ get canLaunch(): boolean { -+ if (this._canLaunch != null) { -+ return this._canLaunch; - } -- -- get canLaunch(): boolean { -- if (this._canLaunch != null) { -- return this._canLaunch; -- } -- if (this.uri != null && this.match !== UriMatchType.RegularExpression) { -- const uri = this.launchUri; -- for (let i = 0; i < CanLaunchWhitelist.length; i++) { -- if (uri.indexOf(CanLaunchWhitelist[i]) === 0) { -- this._canLaunch = true; -- return this._canLaunch; -- } -- } -+ if (this.uri != null && this.match !== UriMatchType.RegularExpression) { -+ const uri = this.launchUri; -+ for (let i = 0; i < CanLaunchWhitelist.length; i++) { -+ if (uri.indexOf(CanLaunchWhitelist[i]) === 0) { -+ this._canLaunch = true; -+ return this._canLaunch; - } -- this._canLaunch = false; -- return this._canLaunch; -- } -- -- get launchUri(): string { -- return this.uri.indexOf('://') < 0 && Utils.tldEndingRegex.test(this.uri) ? ('http://' + this.uri) : this.uri; -+ } - } -+ this._canLaunch = false; -+ return this._canLaunch; -+ } -+ -+ get launchUri(): string { -+ return this.uri.indexOf("://") < 0 && Utils.tldEndingRegex.test(this.uri) -+ ? "http://" + this.uri -+ : this.uri; -+ } - } -diff --git a/jslib/common/src/models/view/loginView.ts b/jslib/common/src/models/view/loginView.ts -index 3bd1ea58..b301f532 100644 ---- a/jslib/common/src/models/view/loginView.ts -+++ b/jslib/common/src/models/view/loginView.ts -@@ -1,66 +1,66 @@ --import { ItemView } from './itemView'; --import { LoginUriView } from './loginUriView'; -+import { ItemView } from "./itemView"; -+import { LoginUriView } from "./loginUriView"; - --import { Utils } from '../../misc/utils'; -+import { Utils } from "../../misc/utils"; - --import { Login } from '../domain/login'; -+import { Login } from "../domain/login"; - --import { LoginLinkedId as LinkedId } from '../../enums/linkedIdType'; -+import { LoginLinkedId as LinkedId } from "../../enums/linkedIdType"; - --import { linkedFieldOption } from '../../misc/linkedFieldOption.decorator'; -+import { linkedFieldOption } from "../../misc/linkedFieldOption.decorator"; - - export class LoginView extends ItemView { -- @linkedFieldOption(LinkedId.Username) -- username: string = null; -- @linkedFieldOption(LinkedId.Password) -- password: string = null; -+ @linkedFieldOption(LinkedId.Username) -+ username: string = null; -+ @linkedFieldOption(LinkedId.Password) -+ password: string = null; - -- passwordRevisionDate?: Date = null; -- totp: string = null; -- uris: LoginUriView[] = null; -- autofillOnPageLoad: boolean = null; -+ passwordRevisionDate?: Date = null; -+ totp: string = null; -+ uris: LoginUriView[] = null; -+ autofillOnPageLoad: boolean = null; - -- constructor(l?: Login) { -- super(); -- if (!l) { -- return; -- } -- -- this.passwordRevisionDate = l.passwordRevisionDate; -- this.autofillOnPageLoad = l.autofillOnPageLoad; -+ constructor(l?: Login) { -+ super(); -+ if (!l) { -+ return; - } - -- get uri(): string { -- return this.hasUris ? this.uris[0].uri : null; -- } -+ this.passwordRevisionDate = l.passwordRevisionDate; -+ this.autofillOnPageLoad = l.autofillOnPageLoad; -+ } - -- get maskedPassword(): string { -- return this.password != null ? '••••••••' : null; -- } -+ get uri(): string { -+ return this.hasUris ? this.uris[0].uri : null; -+ } - -- get subTitle(): string { -- return this.username; -- } -+ get maskedPassword(): string { -+ return this.password != null ? "••••••••" : null; -+ } - -- get canLaunch(): boolean { -- return this.hasUris && this.uris.some(u => u.canLaunch); -- } -+ get subTitle(): string { -+ return this.username; -+ } - -- get hasTotp(): boolean { -- return !Utils.isNullOrWhitespace(this.totp); -- } -+ get canLaunch(): boolean { -+ return this.hasUris && this.uris.some((u) => u.canLaunch); -+ } - -- get launchUri(): string { -- if (this.hasUris) { -- const uri = this.uris.find(u => u.canLaunch); -- if (uri != null) { -- return uri.launchUri; -- } -- } -- return null; -- } -+ get hasTotp(): boolean { -+ return !Utils.isNullOrWhitespace(this.totp); -+ } - -- get hasUris(): boolean { -- return this.uris != null && this.uris.length > 0; -+ get launchUri(): string { -+ if (this.hasUris) { -+ const uri = this.uris.find((u) => u.canLaunch); -+ if (uri != null) { -+ return uri.launchUri; -+ } - } -+ return null; -+ } -+ -+ get hasUris(): boolean { -+ return this.uris != null && this.uris.length > 0; -+ } - } -diff --git a/jslib/common/src/models/view/passwordHistoryView.ts b/jslib/common/src/models/view/passwordHistoryView.ts -index 5a0ca0e6..637b9b4f 100644 ---- a/jslib/common/src/models/view/passwordHistoryView.ts -+++ b/jslib/common/src/models/view/passwordHistoryView.ts -@@ -1,16 +1,16 @@ --import { View } from './view'; -+import { View } from "./view"; - --import { Password } from '../domain/password'; -+import { Password } from "../domain/password"; - - export class PasswordHistoryView implements View { -- password: string = null; -- lastUsedDate: Date = null; -+ password: string = null; -+ lastUsedDate: Date = null; - -- constructor(ph?: Password) { -- if (!ph) { -- return; -- } -- -- this.lastUsedDate = ph.lastUsedDate; -+ constructor(ph?: Password) { -+ if (!ph) { -+ return; - } -+ -+ this.lastUsedDate = ph.lastUsedDate; -+ } - } -diff --git a/jslib/common/src/models/view/secureNoteView.ts b/jslib/common/src/models/view/secureNoteView.ts -index 2d209025..c660ac9f 100644 ---- a/jslib/common/src/models/view/secureNoteView.ts -+++ b/jslib/common/src/models/view/secureNoteView.ts -@@ -1,22 +1,22 @@ --import { SecureNoteType } from '../../enums/secureNoteType'; -+import { SecureNoteType } from "../../enums/secureNoteType"; - --import { ItemView } from './itemView'; -+import { ItemView } from "./itemView"; - --import { SecureNote } from '../domain/secureNote'; -+import { SecureNote } from "../domain/secureNote"; - - export class SecureNoteView extends ItemView { -- type: SecureNoteType = null; -+ type: SecureNoteType = null; - -- constructor(n?: SecureNote) { -- super(); -- if (!n) { -- return; -- } -- -- this.type = n.type; -+ constructor(n?: SecureNote) { -+ super(); -+ if (!n) { -+ return; - } - -- get subTitle(): string { -- return null; -- } -+ this.type = n.type; -+ } -+ -+ get subTitle(): string { -+ return null; -+ } - } -diff --git a/jslib/common/src/models/view/sendAccessView.ts b/jslib/common/src/models/view/sendAccessView.ts -index 2aec827e..1b848169 100644 ---- a/jslib/common/src/models/view/sendAccessView.ts -+++ b/jslib/common/src/models/view/sendAccessView.ts -@@ -1,28 +1,28 @@ --import { SendType } from '../../enums/sendType'; -+import { SendType } from "../../enums/sendType"; - --import { SendAccess } from '../domain/sendAccess'; -+import { SendAccess } from "../domain/sendAccess"; - --import { SendFileView } from './sendFileView'; --import { SendTextView } from './sendTextView'; --import { View } from './view'; -+import { SendFileView } from "./sendFileView"; -+import { SendTextView } from "./sendTextView"; -+import { View } from "./view"; - - export class SendAccessView implements View { -- id: string = null; -- name: string = null; -- type: SendType = null; -- text = new SendTextView(); -- file = new SendFileView(); -- expirationDate: Date = null; -- creatorIdentifier: string = null; -+ id: string = null; -+ name: string = null; -+ type: SendType = null; -+ text = new SendTextView(); -+ file = new SendFileView(); -+ expirationDate: Date = null; -+ creatorIdentifier: string = null; - -- constructor(s?: SendAccess) { -- if (!s) { -- return; -- } -- -- this.id = s.id; -- this.type = s.type; -- this.expirationDate = s.expirationDate; -- this.creatorIdentifier = s.creatorIdentifier; -+ constructor(s?: SendAccess) { -+ if (!s) { -+ return; - } -+ -+ this.id = s.id; -+ this.type = s.type; -+ this.expirationDate = s.expirationDate; -+ this.creatorIdentifier = s.creatorIdentifier; -+ } - } -diff --git a/jslib/common/src/models/view/sendFileView.ts b/jslib/common/src/models/view/sendFileView.ts -index a58df8f3..deff8700 100644 ---- a/jslib/common/src/models/view/sendFileView.ts -+++ b/jslib/common/src/models/view/sendFileView.ts -@@ -1,31 +1,31 @@ --import { View } from './view'; -+import { View } from "./view"; - --import { SendFile } from '../domain/sendFile'; -+import { SendFile } from "../domain/sendFile"; - - export class SendFileView implements View { -- id: string = null; -- size: string = null; -- sizeName: string = null; -- fileName: string = null; -+ id: string = null; -+ size: string = null; -+ sizeName: string = null; -+ fileName: string = null; - -- constructor(f?: SendFile) { -- if (!f) { -- return; -- } -- -- this.id = f.id; -- this.size = f.size; -- this.sizeName = f.sizeName; -+ constructor(f?: SendFile) { -+ if (!f) { -+ return; - } - -- get fileSize(): number { -- try { -- if (this.size != null) { -- return parseInt(this.size, null); -- } -- } catch { -- // Invalid file size. -- } -- return 0; -+ this.id = f.id; -+ this.size = f.size; -+ this.sizeName = f.sizeName; -+ } -+ -+ get fileSize(): number { -+ try { -+ if (this.size != null) { -+ return parseInt(this.size, null); -+ } -+ } catch { -+ // Invalid file size. - } -+ return 0; -+ } - } -diff --git a/jslib/common/src/models/view/sendTextView.ts b/jslib/common/src/models/view/sendTextView.ts -index b6ec45ee..5c1f018e 100644 ---- a/jslib/common/src/models/view/sendTextView.ts -+++ b/jslib/common/src/models/view/sendTextView.ts -@@ -1,20 +1,20 @@ --import { View } from './view'; -+import { View } from "./view"; - --import { SendText } from '../domain/sendText'; -+import { SendText } from "../domain/sendText"; - - export class SendTextView implements View { -- text: string = null; -- hidden: boolean; -+ text: string = null; -+ hidden: boolean; - -- constructor(t?: SendText) { -- if (!t) { -- return; -- } -- -- this.hidden = t.hidden; -+ constructor(t?: SendText) { -+ if (!t) { -+ return; - } - -- get maskedText(): string { -- return this.text != null ? '••••••••' : null; -- } -+ this.hidden = t.hidden; -+ } -+ -+ get maskedText(): string { -+ return this.text != null ? "••••••••" : null; -+ } - } -diff --git a/jslib/common/src/models/view/sendView.ts b/jslib/common/src/models/view/sendView.ts -index 1730bce6..48aa887e 100644 ---- a/jslib/common/src/models/view/sendView.ts -+++ b/jslib/common/src/models/view/sendView.ts -@@ -1,69 +1,69 @@ --import { SendType } from '../../enums/sendType'; --import { Utils } from '../../misc/utils'; -+import { SendType } from "../../enums/sendType"; -+import { Utils } from "../../misc/utils"; - --import { Send } from '../domain/send'; --import { SymmetricCryptoKey } from '../domain/symmetricCryptoKey'; -+import { Send } from "../domain/send"; -+import { SymmetricCryptoKey } from "../domain/symmetricCryptoKey"; - --import { SendFileView } from './sendFileView'; --import { SendTextView } from './sendTextView'; --import { View } from './view'; -+import { SendFileView } from "./sendFileView"; -+import { SendTextView } from "./sendTextView"; -+import { View } from "./view"; - - export class SendView implements View { -- id: string = null; -- accessId: string = null; -- name: string = null; -- notes: string = null; -- key: ArrayBuffer; -- cryptoKey: SymmetricCryptoKey; -- type: SendType = null; -- text = new SendTextView(); -- file = new SendFileView(); -- maxAccessCount?: number = null; -- accessCount: number = 0; -- revisionDate: Date = null; -- deletionDate: Date = null; -- expirationDate: Date = null; -- password: string = null; -- disabled: boolean = false; -- hideEmail: boolean = false; -+ id: string = null; -+ accessId: string = null; -+ name: string = null; -+ notes: string = null; -+ key: ArrayBuffer; -+ cryptoKey: SymmetricCryptoKey; -+ type: SendType = null; -+ text = new SendTextView(); -+ file = new SendFileView(); -+ maxAccessCount?: number = null; -+ accessCount: number = 0; -+ revisionDate: Date = null; -+ deletionDate: Date = null; -+ expirationDate: Date = null; -+ password: string = null; -+ disabled: boolean = false; -+ hideEmail: boolean = false; - -- constructor(s?: Send) { -- if (!s) { -- return; -- } -- -- this.id = s.id; -- this.accessId = s.accessId; -- this.type = s.type; -- this.maxAccessCount = s.maxAccessCount; -- this.accessCount = s.accessCount; -- this.revisionDate = s.revisionDate; -- this.deletionDate = s.deletionDate; -- this.expirationDate = s.expirationDate; -- this.disabled = s.disabled; -- this.password = s.password; -- this.hideEmail = s.hideEmail; -+ constructor(s?: Send) { -+ if (!s) { -+ return; - } - -- get urlB64Key(): string { -- return Utils.fromBufferToUrlB64(this.key); -- } -+ this.id = s.id; -+ this.accessId = s.accessId; -+ this.type = s.type; -+ this.maxAccessCount = s.maxAccessCount; -+ this.accessCount = s.accessCount; -+ this.revisionDate = s.revisionDate; -+ this.deletionDate = s.deletionDate; -+ this.expirationDate = s.expirationDate; -+ this.disabled = s.disabled; -+ this.password = s.password; -+ this.hideEmail = s.hideEmail; -+ } - -- get maxAccessCountReached(): boolean { -- if (this.maxAccessCount == null) { -- return false; -- } -- return this.accessCount >= this.maxAccessCount; -- } -+ get urlB64Key(): string { -+ return Utils.fromBufferToUrlB64(this.key); -+ } - -- get expired(): boolean { -- if (this.expirationDate == null) { -- return false; -- } -- return this.expirationDate <= new Date(); -+ get maxAccessCountReached(): boolean { -+ if (this.maxAccessCount == null) { -+ return false; - } -+ return this.accessCount >= this.maxAccessCount; -+ } - -- get pendingDelete(): boolean { -- return this.deletionDate <= new Date(); -+ get expired(): boolean { -+ if (this.expirationDate == null) { -+ return false; - } -+ return this.expirationDate <= new Date(); -+ } -+ -+ get pendingDelete(): boolean { -+ return this.deletionDate <= new Date(); -+ } - } -diff --git a/jslib/common/src/models/view/view.ts b/jslib/common/src/models/view/view.ts -index c295888e..1f16b3d5 100644 ---- a/jslib/common/src/models/view/view.ts -+++ b/jslib/common/src/models/view/view.ts -@@ -1,2 +1 @@ --export class View { --} -+export class View {} -diff --git a/jslib/common/src/services/api.service.ts b/jslib/common/src/services/api.service.ts -index 46fdc139..72b6d222 100644 ---- a/jslib/common/src/services/api.service.ts -+++ b/jslib/common/src/services/api.service.ts -@@ -1,1776 +1,2508 @@ --import { DeviceType } from '../enums/deviceType'; --import { PolicyType } from '../enums/policyType'; -- --import { ApiService as ApiServiceAbstraction } from '../abstractions/api.service'; --import { EnvironmentService } from '../abstractions/environment.service'; --import { PlatformUtilsService } from '../abstractions/platformUtils.service'; --import { TokenService } from '../abstractions/token.service'; -- --import { AttachmentRequest } from '../models/request/attachmentRequest'; --import { BitPayInvoiceRequest } from '../models/request/bitPayInvoiceRequest'; --import { CipherBulkDeleteRequest } from '../models/request/cipherBulkDeleteRequest'; --import { CipherBulkMoveRequest } from '../models/request/cipherBulkMoveRequest'; --import { CipherBulkShareRequest } from '../models/request/cipherBulkShareRequest'; --import { CipherCollectionsRequest } from '../models/request/cipherCollectionsRequest'; --import { CipherCreateRequest } from '../models/request/cipherCreateRequest'; --import { CipherRequest } from '../models/request/cipherRequest'; --import { CipherShareRequest } from '../models/request/cipherShareRequest'; --import { CollectionRequest } from '../models/request/collectionRequest'; --import { DeleteRecoverRequest } from '../models/request/deleteRecoverRequest'; --import { EmailRequest } from '../models/request/emailRequest'; --import { EmailTokenRequest } from '../models/request/emailTokenRequest'; --import { EmergencyAccessAcceptRequest } from '../models/request/emergencyAccessAcceptRequest'; --import { EmergencyAccessConfirmRequest } from '../models/request/emergencyAccessConfirmRequest'; --import { EmergencyAccessInviteRequest } from '../models/request/emergencyAccessInviteRequest'; --import { EmergencyAccessPasswordRequest } from '../models/request/emergencyAccessPasswordRequest'; --import { EmergencyAccessUpdateRequest } from '../models/request/emergencyAccessUpdateRequest'; --import { EventRequest } from '../models/request/eventRequest'; --import { FolderRequest } from '../models/request/folderRequest'; --import { GroupRequest } from '../models/request/groupRequest'; --import { IapCheckRequest } from '../models/request/iapCheckRequest'; --import { ImportCiphersRequest } from '../models/request/importCiphersRequest'; --import { ImportDirectoryRequest } from '../models/request/importDirectoryRequest'; --import { ImportOrganizationCiphersRequest } from '../models/request/importOrganizationCiphersRequest'; --import { KdfRequest } from '../models/request/kdfRequest'; --import { KeysRequest } from '../models/request/keysRequest'; --import { OrganizationSponsorshipCreateRequest } from '../models/request/organization/organizationSponsorshipCreateRequest'; --import { OrganizationSponsorshipRedeemRequest } from '../models/request/organization/organizationSponsorshipRedeemRequest'; --import { OrganizationSsoRequest } from '../models/request/organization/organizationSsoRequest'; --import { OrganizationCreateRequest } from '../models/request/organizationCreateRequest'; --import { OrganizationImportRequest } from '../models/request/organizationImportRequest'; --import { OrganizationKeysRequest } from '../models/request/organizationKeysRequest'; --import { OrganizationSubscriptionUpdateRequest } from '../models/request/organizationSubscriptionUpdateRequest'; --import { OrganizationTaxInfoUpdateRequest } from '../models/request/organizationTaxInfoUpdateRequest'; --import { OrganizationUpdateRequest } from '../models/request/organizationUpdateRequest'; --import { OrganizationUpgradeRequest } from '../models/request/organizationUpgradeRequest'; --import { OrganizationUserAcceptRequest } from '../models/request/organizationUserAcceptRequest'; --import { OrganizationUserBulkConfirmRequest } from '../models/request/organizationUserBulkConfirmRequest'; --import { OrganizationUserBulkRequest } from '../models/request/organizationUserBulkRequest'; --import { OrganizationUserConfirmRequest } from '../models/request/organizationUserConfirmRequest'; --import { OrganizationUserInviteRequest } from '../models/request/organizationUserInviteRequest'; --import { OrganizationUserResetPasswordEnrollmentRequest } from '../models/request/organizationUserResetPasswordEnrollmentRequest'; --import { OrganizationUserResetPasswordRequest } from '../models/request/organizationUserResetPasswordRequest'; --import { OrganizationUserUpdateGroupsRequest } from '../models/request/organizationUserUpdateGroupsRequest'; --import { OrganizationUserUpdateRequest } from '../models/request/organizationUserUpdateRequest'; --import { PasswordHintRequest } from '../models/request/passwordHintRequest'; --import { PasswordRequest } from '../models/request/passwordRequest'; --import { PaymentRequest } from '../models/request/paymentRequest'; --import { PolicyRequest } from '../models/request/policyRequest'; --import { PreloginRequest } from '../models/request/preloginRequest'; --import { ProviderAddOrganizationRequest } from '../models/request/provider/providerAddOrganizationRequest'; --import { ProviderOrganizationCreateRequest } from '../models/request/provider/providerOrganizationCreateRequest'; --import { ProviderSetupRequest } from '../models/request/provider/providerSetupRequest'; --import { ProviderUpdateRequest } from '../models/request/provider/providerUpdateRequest'; --import { ProviderUserAcceptRequest } from '../models/request/provider/providerUserAcceptRequest'; --import { ProviderUserBulkConfirmRequest } from '../models/request/provider/providerUserBulkConfirmRequest'; --import { ProviderUserBulkRequest } from '../models/request/provider/providerUserBulkRequest'; --import { ProviderUserConfirmRequest } from '../models/request/provider/providerUserConfirmRequest'; --import { ProviderUserInviteRequest } from '../models/request/provider/providerUserInviteRequest'; --import { ProviderUserUpdateRequest } from '../models/request/provider/providerUserUpdateRequest'; --import { RegisterRequest } from '../models/request/registerRequest'; --import { SeatRequest } from '../models/request/seatRequest'; --import { SecretVerificationRequest } from '../models/request/secretVerificationRequest'; --import { SelectionReadOnlyRequest } from '../models/request/selectionReadOnlyRequest'; --import { SendAccessRequest } from '../models/request/sendAccessRequest'; --import { SendRequest } from '../models/request/sendRequest'; --import { SetPasswordRequest } from '../models/request/setPasswordRequest'; --import { StorageRequest } from '../models/request/storageRequest'; --import { TaxInfoUpdateRequest } from '../models/request/taxInfoUpdateRequest'; --import { TokenRequest } from '../models/request/tokenRequest'; --import { TwoFactorEmailRequest } from '../models/request/twoFactorEmailRequest'; --import { TwoFactorProviderRequest } from '../models/request/twoFactorProviderRequest'; --import { TwoFactorRecoveryRequest } from '../models/request/twoFactorRecoveryRequest'; --import { UpdateDomainsRequest } from '../models/request/updateDomainsRequest'; --import { UpdateKeyRequest } from '../models/request/updateKeyRequest'; --import { UpdateProfileRequest } from '../models/request/updateProfileRequest'; --import { UpdateTempPasswordRequest } from '../models/request/updateTempPasswordRequest'; --import { UpdateTwoFactorAuthenticatorRequest } from '../models/request/updateTwoFactorAuthenticatorRequest'; --import { UpdateTwoFactorDuoRequest } from '../models/request/updateTwoFactorDuoRequest'; --import { UpdateTwoFactorEmailRequest } from '../models/request/updateTwoFactorEmailRequest'; --import { UpdateTwoFactorWebAuthnDeleteRequest } from '../models/request/updateTwoFactorWebAuthnDeleteRequest'; --import { UpdateTwoFactorWebAuthnRequest } from '../models/request/updateTwoFactorWebAuthnRequest'; --import { UpdateTwoFactorYubioOtpRequest } from '../models/request/updateTwoFactorYubioOtpRequest'; --import { VerifyBankRequest } from '../models/request/verifyBankRequest'; --import { VerifyDeleteRecoverRequest } from '../models/request/verifyDeleteRecoverRequest'; --import { VerifyEmailRequest } from '../models/request/verifyEmailRequest'; -- --import { Utils } from '../misc/utils'; -- --import { ApiKeyResponse } from '../models/response/apiKeyResponse'; --import { AttachmentResponse } from '../models/response/attachmentResponse'; --import { AttachmentUploadDataResponse } from '../models/response/attachmentUploadDataResponse'; --import { BillingResponse } from '../models/response/billingResponse'; --import { BreachAccountResponse } from '../models/response/breachAccountResponse'; --import { CipherResponse } from '../models/response/cipherResponse'; -+import { DeviceType } from "../enums/deviceType"; -+import { PolicyType } from "../enums/policyType"; -+ -+import { ApiService as ApiServiceAbstraction } from "../abstractions/api.service"; -+import { EnvironmentService } from "../abstractions/environment.service"; -+import { PlatformUtilsService } from "../abstractions/platformUtils.service"; -+import { TokenService } from "../abstractions/token.service"; -+ -+import { AttachmentRequest } from "../models/request/attachmentRequest"; -+import { BitPayInvoiceRequest } from "../models/request/bitPayInvoiceRequest"; -+import { CipherBulkDeleteRequest } from "../models/request/cipherBulkDeleteRequest"; -+import { CipherBulkMoveRequest } from "../models/request/cipherBulkMoveRequest"; -+import { CipherBulkShareRequest } from "../models/request/cipherBulkShareRequest"; -+import { CipherCollectionsRequest } from "../models/request/cipherCollectionsRequest"; -+import { CipherCreateRequest } from "../models/request/cipherCreateRequest"; -+import { CipherRequest } from "../models/request/cipherRequest"; -+import { CipherShareRequest } from "../models/request/cipherShareRequest"; -+import { CollectionRequest } from "../models/request/collectionRequest"; -+import { DeleteRecoverRequest } from "../models/request/deleteRecoverRequest"; -+import { EmailRequest } from "../models/request/emailRequest"; -+import { EmailTokenRequest } from "../models/request/emailTokenRequest"; -+import { EmergencyAccessAcceptRequest } from "../models/request/emergencyAccessAcceptRequest"; -+import { EmergencyAccessConfirmRequest } from "../models/request/emergencyAccessConfirmRequest"; -+import { EmergencyAccessInviteRequest } from "../models/request/emergencyAccessInviteRequest"; -+import { EmergencyAccessPasswordRequest } from "../models/request/emergencyAccessPasswordRequest"; -+import { EmergencyAccessUpdateRequest } from "../models/request/emergencyAccessUpdateRequest"; -+import { EventRequest } from "../models/request/eventRequest"; -+import { FolderRequest } from "../models/request/folderRequest"; -+import { GroupRequest } from "../models/request/groupRequest"; -+import { IapCheckRequest } from "../models/request/iapCheckRequest"; -+import { ImportCiphersRequest } from "../models/request/importCiphersRequest"; -+import { ImportDirectoryRequest } from "../models/request/importDirectoryRequest"; -+import { ImportOrganizationCiphersRequest } from "../models/request/importOrganizationCiphersRequest"; -+import { KdfRequest } from "../models/request/kdfRequest"; -+import { KeysRequest } from "../models/request/keysRequest"; -+import { OrganizationSponsorshipCreateRequest } from "../models/request/organization/organizationSponsorshipCreateRequest"; -+import { OrganizationSponsorshipRedeemRequest } from "../models/request/organization/organizationSponsorshipRedeemRequest"; -+import { OrganizationSsoRequest } from "../models/request/organization/organizationSsoRequest"; -+import { OrganizationCreateRequest } from "../models/request/organizationCreateRequest"; -+import { OrganizationImportRequest } from "../models/request/organizationImportRequest"; -+import { OrganizationKeysRequest } from "../models/request/organizationKeysRequest"; -+import { OrganizationSsoUpdateRequest } from '../models/request/organizationSsoUpdateRequest'; -+import { OrganizationSubscriptionUpdateRequest } from "../models/request/organizationSubscriptionUpdateRequest"; -+import { OrganizationTaxInfoUpdateRequest } from "../models/request/organizationTaxInfoUpdateRequest"; -+import { OrganizationUpdateRequest } from "../models/request/organizationUpdateRequest"; -+import { OrganizationUpgradeRequest } from "../models/request/organizationUpgradeRequest"; -+import { OrganizationUserAcceptRequest } from "../models/request/organizationUserAcceptRequest"; -+import { OrganizationUserBulkConfirmRequest } from "../models/request/organizationUserBulkConfirmRequest"; -+import { OrganizationUserBulkRequest } from "../models/request/organizationUserBulkRequest"; -+import { OrganizationUserConfirmRequest } from "../models/request/organizationUserConfirmRequest"; -+import { OrganizationUserInviteRequest } from "../models/request/organizationUserInviteRequest"; -+import { OrganizationUserResetPasswordEnrollmentRequest } from "../models/request/organizationUserResetPasswordEnrollmentRequest"; -+import { OrganizationUserResetPasswordRequest } from "../models/request/organizationUserResetPasswordRequest"; -+import { OrganizationUserUpdateGroupsRequest } from "../models/request/organizationUserUpdateGroupsRequest"; -+import { OrganizationUserUpdateRequest } from "../models/request/organizationUserUpdateRequest"; -+import { PasswordHintRequest } from "../models/request/passwordHintRequest"; -+import { PasswordRequest } from "../models/request/passwordRequest"; -+import { PaymentRequest } from "../models/request/paymentRequest"; -+import { PolicyRequest } from "../models/request/policyRequest"; -+import { PreloginRequest } from "../models/request/preloginRequest"; -+import { ProviderAddOrganizationRequest } from "../models/request/provider/providerAddOrganizationRequest"; -+import { ProviderOrganizationCreateRequest } from "../models/request/provider/providerOrganizationCreateRequest"; -+import { ProviderSetupRequest } from "../models/request/provider/providerSetupRequest"; -+import { ProviderUpdateRequest } from "../models/request/provider/providerUpdateRequest"; -+import { ProviderUserAcceptRequest } from "../models/request/provider/providerUserAcceptRequest"; -+import { ProviderUserBulkConfirmRequest } from "../models/request/provider/providerUserBulkConfirmRequest"; -+import { ProviderUserBulkRequest } from "../models/request/provider/providerUserBulkRequest"; -+import { ProviderUserConfirmRequest } from "../models/request/provider/providerUserConfirmRequest"; -+import { ProviderUserInviteRequest } from "../models/request/provider/providerUserInviteRequest"; -+import { ProviderUserUpdateRequest } from "../models/request/provider/providerUserUpdateRequest"; -+import { RegisterRequest } from "../models/request/registerRequest"; -+import { SeatRequest } from "../models/request/seatRequest"; -+import { SecretVerificationRequest } from "../models/request/secretVerificationRequest"; -+import { SelectionReadOnlyRequest } from "../models/request/selectionReadOnlyRequest"; -+import { SendAccessRequest } from "../models/request/sendAccessRequest"; -+import { SendRequest } from "../models/request/sendRequest"; -+import { SetPasswordRequest } from "../models/request/setPasswordRequest"; -+import { StorageRequest } from "../models/request/storageRequest"; -+import { TaxInfoUpdateRequest } from "../models/request/taxInfoUpdateRequest"; -+import { TokenRequest } from "../models/request/tokenRequest"; -+import { TwoFactorEmailRequest } from "../models/request/twoFactorEmailRequest"; -+import { TwoFactorProviderRequest } from "../models/request/twoFactorProviderRequest"; -+import { TwoFactorRecoveryRequest } from "../models/request/twoFactorRecoveryRequest"; -+import { UpdateDomainsRequest } from "../models/request/updateDomainsRequest"; -+import { UpdateKeyRequest } from "../models/request/updateKeyRequest"; -+import { UpdateProfileRequest } from "../models/request/updateProfileRequest"; -+import { UpdateTempPasswordRequest } from "../models/request/updateTempPasswordRequest"; -+import { UpdateTwoFactorAuthenticatorRequest } from "../models/request/updateTwoFactorAuthenticatorRequest"; -+import { UpdateTwoFactorDuoRequest } from "../models/request/updateTwoFactorDuoRequest"; -+import { UpdateTwoFactorEmailRequest } from "../models/request/updateTwoFactorEmailRequest"; -+import { UpdateTwoFactorWebAuthnDeleteRequest } from "../models/request/updateTwoFactorWebAuthnDeleteRequest"; -+import { UpdateTwoFactorWebAuthnRequest } from "../models/request/updateTwoFactorWebAuthnRequest"; -+import { UpdateTwoFactorYubioOtpRequest } from "../models/request/updateTwoFactorYubioOtpRequest"; -+import { VerifyBankRequest } from "../models/request/verifyBankRequest"; -+import { VerifyDeleteRecoverRequest } from "../models/request/verifyDeleteRecoverRequest"; -+import { VerifyEmailRequest } from "../models/request/verifyEmailRequest"; -+ -+import { Utils } from "../misc/utils"; -+ -+import { ApiKeyResponse } from "../models/response/apiKeyResponse"; -+import { AttachmentResponse } from "../models/response/attachmentResponse"; -+import { AttachmentUploadDataResponse } from "../models/response/attachmentUploadDataResponse"; -+import { BillingResponse } from "../models/response/billingResponse"; -+import { BreachAccountResponse } from "../models/response/breachAccountResponse"; -+import { CipherResponse } from "../models/response/cipherResponse"; - import { -- CollectionGroupDetailsResponse, -- CollectionResponse, --} from '../models/response/collectionResponse'; --import { DomainsResponse } from '../models/response/domainsResponse'; -+ CollectionGroupDetailsResponse, -+ CollectionResponse, -+} from "../models/response/collectionResponse"; -+import { DomainsResponse } from "../models/response/domainsResponse"; - import { -- EmergencyAccessGranteeDetailsResponse, -- EmergencyAccessGrantorDetailsResponse, -- EmergencyAccessTakeoverResponse, -- EmergencyAccessViewResponse --} from '../models/response/emergencyAccessResponse'; --import { ErrorResponse } from '../models/response/errorResponse'; --import { EventResponse } from '../models/response/eventResponse'; --import { FolderResponse } from '../models/response/folderResponse'; -+ EmergencyAccessGranteeDetailsResponse, -+ EmergencyAccessGrantorDetailsResponse, -+ EmergencyAccessTakeoverResponse, -+ EmergencyAccessViewResponse, -+} from "../models/response/emergencyAccessResponse"; -+import { ErrorResponse } from "../models/response/errorResponse"; -+import { EventResponse } from "../models/response/eventResponse"; -+import { FolderResponse } from "../models/response/folderResponse"; -+import { GroupDetailsResponse, GroupResponse } from "../models/response/groupResponse"; -+import { IdentityCaptchaResponse } from "../models/response/identityCaptchaResponse"; -+import { IdentityTokenResponse } from "../models/response/identityTokenResponse"; -+import { IdentityTwoFactorResponse } from "../models/response/identityTwoFactorResponse"; -+import { ListResponse } from "../models/response/listResponse"; -+import { OrganizationSsoResponse } from "../models/response/organization/organizationSsoResponse"; -+import { OrganizationAutoEnrollStatusResponse } from "../models/response/organizationAutoEnrollStatusResponse"; -+import { OrganizationKeysResponse } from "../models/response/organizationKeysResponse"; -+import { OrganizationResponse } from "../models/response/organizationResponse"; -+import { OrganizationSubscriptionResponse } from "../models/response/organizationSubscriptionResponse"; -+import { OrganizationUserBulkPublicKeyResponse } from "../models/response/organizationUserBulkPublicKeyResponse"; -+import { OrganizationUserBulkResponse } from "../models/response/organizationUserBulkResponse"; - import { -- GroupDetailsResponse, -- GroupResponse, --} from '../models/response/groupResponse'; --import { IdentityCaptchaResponse } from '../models/response/identityCaptchaResponse'; --import { IdentityTokenResponse } from '../models/response/identityTokenResponse'; --import { IdentityTwoFactorResponse } from '../models/response/identityTwoFactorResponse'; --import { ListResponse } from '../models/response/listResponse'; --import { OrganizationSsoResponse } from '../models/response/organization/organizationSsoResponse'; --import { OrganizationAutoEnrollStatusResponse } from '../models/response/organizationAutoEnrollStatusResponse'; --import { OrganizationKeysResponse } from '../models/response/organizationKeysResponse'; --import { OrganizationResponse } from '../models/response/organizationResponse'; --import { OrganizationSubscriptionResponse } from '../models/response/organizationSubscriptionResponse'; --import { OrganizationUserBulkPublicKeyResponse } from '../models/response/organizationUserBulkPublicKeyResponse'; --import { OrganizationUserBulkResponse } from '../models/response/organizationUserBulkResponse'; -+ OrganizationUserDetailsResponse, -+ OrganizationUserResetPasswordDetailsReponse, -+ OrganizationUserUserDetailsResponse, -+} from "../models/response/organizationUserResponse"; -+import { PaymentResponse } from "../models/response/paymentResponse"; -+import { PlanResponse } from "../models/response/planResponse"; -+import { PolicyResponse } from "../models/response/policyResponse"; -+import { PreloginResponse } from "../models/response/preloginResponse"; -+import { ProfileResponse } from "../models/response/profileResponse"; - import { -- OrganizationUserDetailsResponse, -- OrganizationUserResetPasswordDetailsReponse, -- OrganizationUserUserDetailsResponse, --} from '../models/response/organizationUserResponse'; --import { PaymentResponse } from '../models/response/paymentResponse'; --import { PlanResponse } from '../models/response/planResponse'; --import { PolicyResponse } from '../models/response/policyResponse'; --import { PreloginResponse } from '../models/response/preloginResponse'; --import { ProfileResponse } from '../models/response/profileResponse'; --import { ProviderOrganizationOrganizationDetailsResponse, ProviderOrganizationResponse } from '../models/response/provider/providerOrganizationResponse'; --import { ProviderResponse } from '../models/response/provider/providerResponse'; --import { ProviderUserBulkPublicKeyResponse } from '../models/response/provider/providerUserBulkPublicKeyResponse'; --import { ProviderUserBulkResponse } from '../models/response/provider/providerUserBulkResponse'; -+ ProviderOrganizationOrganizationDetailsResponse, -+ ProviderOrganizationResponse, -+} from "../models/response/provider/providerOrganizationResponse"; -+import { ProviderResponse } from "../models/response/provider/providerResponse"; -+import { ProviderUserBulkPublicKeyResponse } from "../models/response/provider/providerUserBulkPublicKeyResponse"; -+import { ProviderUserBulkResponse } from "../models/response/provider/providerUserBulkResponse"; - import { -- ProviderUserResponse, -- ProviderUserUserDetailsResponse --} from '../models/response/provider/providerUserResponse'; --import { SelectionReadOnlyResponse } from '../models/response/selectionReadOnlyResponse'; --import { SendAccessResponse } from '../models/response/sendAccessResponse'; --import { SendFileDownloadDataResponse } from '../models/response/sendFileDownloadDataResponse'; --import { SendFileUploadDataResponse } from '../models/response/sendFileUploadDataResponse'; --import { SendResponse } from '../models/response/sendResponse'; --import { SubscriptionResponse } from '../models/response/subscriptionResponse'; --import { SyncResponse } from '../models/response/syncResponse'; --import { TaxInfoResponse } from '../models/response/taxInfoResponse'; --import { TaxRateResponse } from '../models/response/taxRateResponse'; --import { TwoFactorAuthenticatorResponse } from '../models/response/twoFactorAuthenticatorResponse'; --import { TwoFactorDuoResponse } from '../models/response/twoFactorDuoResponse'; --import { TwoFactorEmailResponse } from '../models/response/twoFactorEmailResponse'; --import { TwoFactorProviderResponse } from '../models/response/twoFactorProviderResponse'; --import { TwoFactorRecoverResponse } from '../models/response/twoFactorRescoverResponse'; --import { TwoFactorWebAuthnResponse } from '../models/response/twoFactorWebAuthnResponse'; --import { ChallengeResponse } from '../models/response/twoFactorWebAuthnResponse'; --import { TwoFactorYubiKeyResponse } from '../models/response/twoFactorYubiKeyResponse'; --import { UserKeyResponse } from '../models/response/userKeyResponse'; -- --import { SetKeyConnectorKeyRequest } from '../models/request/account/setKeyConnectorKeyRequest'; --import { VerifyOTPRequest } from '../models/request/account/verifyOTPRequest'; --import { KeyConnectorUserKeyRequest } from '../models/request/keyConnectorUserKeyRequest'; --import { KeyConnectorUserKeyResponse } from '../models/response/keyConnectorUserKeyResponse'; --import { SendAccessView } from '../models/view/sendAccessView'; -+ ProviderUserResponse, -+ ProviderUserUserDetailsResponse, -+} from "../models/response/provider/providerUserResponse"; -+import { SelectionReadOnlyResponse } from "../models/response/selectionReadOnlyResponse"; -+import { SendAccessResponse } from "../models/response/sendAccessResponse"; -+import { SendFileDownloadDataResponse } from "../models/response/sendFileDownloadDataResponse"; -+import { SendFileUploadDataResponse } from "../models/response/sendFileUploadDataResponse"; -+import { SendResponse } from "../models/response/sendResponse"; -+import { SsoConfigResponse } from '../models/response/ssoConfigResponse'; -+import { SubscriptionResponse } from "../models/response/subscriptionResponse"; -+import { SyncResponse } from "../models/response/syncResponse"; -+import { TaxInfoResponse } from "../models/response/taxInfoResponse"; -+import { TaxRateResponse } from "../models/response/taxRateResponse"; -+import { TwoFactorAuthenticatorResponse } from "../models/response/twoFactorAuthenticatorResponse"; -+import { TwoFactorDuoResponse } from "../models/response/twoFactorDuoResponse"; -+import { TwoFactorEmailResponse } from "../models/response/twoFactorEmailResponse"; -+import { TwoFactorProviderResponse } from "../models/response/twoFactorProviderResponse"; -+import { TwoFactorRecoverResponse } from "../models/response/twoFactorRescoverResponse"; -+import { TwoFactorWebAuthnResponse } from "../models/response/twoFactorWebAuthnResponse"; -+import { ChallengeResponse } from "../models/response/twoFactorWebAuthnResponse"; -+import { TwoFactorYubiKeyResponse } from "../models/response/twoFactorYubiKeyResponse"; -+import { UserKeyResponse } from "../models/response/userKeyResponse"; -+ -+import { SetKeyConnectorKeyRequest } from "../models/request/account/setKeyConnectorKeyRequest"; -+import { VerifyOTPRequest } from "../models/request/account/verifyOTPRequest"; -+import { KeyConnectorUserKeyRequest } from "../models/request/keyConnectorUserKeyRequest"; -+import { KeyConnectorUserKeyResponse } from "../models/response/keyConnectorUserKeyResponse"; -+import { SendAccessView } from "../models/view/sendAccessView"; - -- -- --export class ApiService implements ApiServiceAbstraction { -- protected apiKeyRefresh: (clientId: string, clientSecret: string) => Promise; -- private device: DeviceType; -- private deviceType: string; -- private isWebClient = false; -- private isDesktopClient = false; -- -- constructor(private tokenService: TokenService, private platformUtilsService: PlatformUtilsService, -- private environmentService: EnvironmentService, private logoutCallback: (expired: boolean) => Promise, -- private customUserAgent: string = null) { -- this.device = platformUtilsService.getDevice(); -- this.deviceType = this.device.toString(); -- this.isWebClient = this.device === DeviceType.IEBrowser || this.device === DeviceType.ChromeBrowser || -- this.device === DeviceType.EdgeBrowser || this.device === DeviceType.FirefoxBrowser || -- this.device === DeviceType.OperaBrowser || this.device === DeviceType.SafariBrowser || -- this.device === DeviceType.UnknownBrowser || this.device === DeviceType.VivaldiBrowser; -- this.isDesktopClient = this.device === DeviceType.WindowsDesktop || this.device === DeviceType.MacOsDesktop || -- this.device === DeviceType.LinuxDesktop; -- } -- -- // Auth APIs -- -- async postIdentityToken(request: TokenRequest): Promise { -- const headers = new Headers({ -- 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', -- 'Accept': 'application/json', -- 'Device-Type': this.deviceType, -- }); -- if (this.customUserAgent != null) { -- headers.set('User-Agent', this.customUserAgent); -- } -- request.alterIdentityTokenHeaders(headers); -- const response = await this.fetch(new Request(this.environmentService.getIdentityUrl() + '/connect/token', { -- body: this.qsStringify(request.toIdentityToken(request.clientId ?? this.platformUtilsService.identityClientId)), -- credentials: this.getCredentials(), -- cache: 'no-store', -- headers: headers, -- method: 'POST', -- })); -- -- let responseJson: any = null; -- if (this.isJsonResponse(response)) { -- responseJson = await response.json(); -- } -- -- if (responseJson != null) { -- if (response.status === 200) { -- return new IdentityTokenResponse(responseJson); -- } else if (response.status === 400 && responseJson.TwoFactorProviders2 && -- Object.keys(responseJson.TwoFactorProviders2).length) { -- await this.tokenService.clearTwoFactorToken(request.email); -- return new IdentityTwoFactorResponse(responseJson); -- } else if (response.status === 400 && responseJson.HCaptcha_SiteKey && -- Object.keys(responseJson.HCaptcha_SiteKey).length) { -- return new IdentityCaptchaResponse(responseJson); -- } -- } -- -- return Promise.reject(new ErrorResponse(responseJson, response.status, true)); -- } -- -- async refreshIdentityToken(): Promise { -- try { -- await this.doAuthRefresh(); -- } catch (e) { -- return Promise.reject(null); -- } -- } -- -- // Account APIs -- -- async getProfile(): Promise { -- const r = await this.send('GET', '/accounts/profile', null, true, true); -- return new ProfileResponse(r); -- } -- -- async getUserBilling(): Promise { -- const r = await this.send('GET', '/accounts/billing', null, true, true); -- return new BillingResponse(r); -- } -- -- async getUserSubscription(): Promise { -- const r = await this.send('GET', '/accounts/subscription', null, true, true); -- return new SubscriptionResponse(r); -- } -- -- async getTaxInfo(): Promise { -- const r = await this.send('GET', '/accounts/tax', null, true, true); -- return new TaxInfoResponse(r); -- } -- -- async putProfile(request: UpdateProfileRequest): Promise { -- const r = await this.send('PUT', '/accounts/profile', request, true, true); -- return new ProfileResponse(r); -- } -- -- putTaxInfo(request: TaxInfoUpdateRequest): Promise { -- return this.send('PUT', '/accounts/tax', request, true, false); -- } -- -- async postPrelogin(request: PreloginRequest): Promise { -- const r = await this.send('POST', '/accounts/prelogin', request, false, true); -- return new PreloginResponse(r); -- } -- -- postEmailToken(request: EmailTokenRequest): Promise { -- return this.send('POST', '/accounts/email-token', request, true, false); -- } -- -- postEmail(request: EmailRequest): Promise { -- return this.send('POST', '/accounts/email', request, true, false); -- } -- -- postPassword(request: PasswordRequest): Promise { -- return this.send('POST', '/accounts/password', request, true, false); -- } -- -- setPassword(request: SetPasswordRequest): Promise { -- return this.send('POST', '/accounts/set-password', request, true, false); -- } -- -- postSetKeyConnectorKey(request: SetKeyConnectorKeyRequest): Promise { -- return this.send('POST', '/accounts/set-key-connector-key', request, true, false); -- } -- -- postSecurityStamp(request: SecretVerificationRequest): Promise { -- return this.send('POST', '/accounts/security-stamp', request, true, false); -- } -- -- deleteAccount(request: SecretVerificationRequest): Promise { -- return this.send('DELETE', '/accounts', request, true, false); -- } -- -- async getAccountRevisionDate(): Promise { -- const r = await this.send('GET', '/accounts/revision-date', null, true, true); -- return r as number; -- } -- -- postPasswordHint(request: PasswordHintRequest): Promise { -- return this.send('POST', '/accounts/password-hint', request, false, false); -- } -- -- postRegister(request: RegisterRequest): Promise { -- return this.send('POST', '/accounts/register', request, false, false); -- } -- -- async postPremium(data: FormData): Promise { -- const r = await this.send('POST', '/accounts/premium', data, true, true); -- return new PaymentResponse(r); -- } -- -- async postIapCheck(request: IapCheckRequest): Promise { -- return this.send('POST', '/accounts/iap-check', request, true, false); -- } -- -- postReinstatePremium(): Promise { -- return this.send('POST', '/accounts/reinstate-premium', null, true, false); -- } -- -- postCancelPremium(): Promise { -- return this.send('POST', '/accounts/cancel-premium', null, true, false); -- } -- -- async postAccountStorage(request: StorageRequest): Promise { -- const r = await this.send('POST', '/accounts/storage', request, true, true); -- return new PaymentResponse(r); -- } -- -- postAccountPayment(request: PaymentRequest): Promise { -- return this.send('POST', '/accounts/payment', request, true, false); -- } -- -- postAccountLicense(data: FormData): Promise { -- return this.send('POST', '/accounts/license', data, true, false); -- } -- -- postAccountKeys(request: KeysRequest): Promise { -- return this.send('POST', '/accounts/keys', request, true, false); -- } -- -- postAccountKey(request: UpdateKeyRequest): Promise { -- return this.send('POST', '/accounts/key', request, true, false); -- } -- -- postAccountVerifyEmail(): Promise { -- return this.send('POST', '/accounts/verify-email', null, true, false); -- } -- -- postAccountVerifyEmailToken(request: VerifyEmailRequest): Promise { -- return this.send('POST', '/accounts/verify-email-token', request, false, false); -- } -- -- postAccountVerifyPassword(request: SecretVerificationRequest): Promise { -- return this.send('POST', '/accounts/verify-password', request, true, false); -- } -- -- postAccountRecoverDelete(request: DeleteRecoverRequest): Promise { -- return this.send('POST', '/accounts/delete-recover', request, false, false); -- } -- -- postAccountRecoverDeleteToken(request: VerifyDeleteRecoverRequest): Promise { -- return this.send('POST', '/accounts/delete-recover-token', request, false, false); -- } -- -- postAccountKdf(request: KdfRequest): Promise { -- return this.send('POST', '/accounts/kdf', request, true, false); -- } -- -- async deleteSsoUser(organizationId: string): Promise { -- return this.send('DELETE', '/accounts/sso/' + organizationId, null, true, false); -- } -- -- async getSsoUserIdentifier(): Promise { -- return this.send('GET', '/accounts/sso/user-identifier', null, true, true); -- } -- -- async postUserApiKey(id: string, request: SecretVerificationRequest): Promise { -- const r = await this.send('POST', '/accounts/api-key', request, true, true); -- return new ApiKeyResponse(r); -- } -- -- async postUserRotateApiKey(id: string, request: SecretVerificationRequest): Promise { -- const r = await this.send('POST', '/accounts/rotate-api-key', request, true, true); -- return new ApiKeyResponse(r); -- } -- -- putUpdateTempPassword(request: UpdateTempPasswordRequest): Promise { -- return this.send('PUT', '/accounts/update-temp-password', request, true, false); -- } -- -- postAccountRequestOTP(): Promise { -- return this.send('POST', '/accounts/request-otp', null, true, false); -- } -- -- postAccountVerifyOTP(request: VerifyOTPRequest): Promise { -- return this.send('POST', '/accounts/verify-otp', request, true, false); -- } -- -- postConvertToKeyConnector(): Promise { -- return this.send('POST', '/accounts/convert-to-key-connector', null, true, false); -- } -- -- // Folder APIs -- -- async getFolder(id: string): Promise { -- const r = await this.send('GET', '/folders/' + id, null, true, true); -- return new FolderResponse(r); -- } -- -- async postFolder(request: FolderRequest): Promise { -- const r = await this.send('POST', '/folders', request, true, true); -- return new FolderResponse(r); -- } -- -- async putFolder(id: string, request: FolderRequest): Promise { -- const r = await this.send('PUT', '/folders/' + id, request, true, true); -- return new FolderResponse(r); -- } -- -- deleteFolder(id: string): Promise { -- return this.send('DELETE', '/folders/' + id, null, true, false); -- } -- -- // Send APIs -- -- async getSend(id: string): Promise { -- const r = await this.send('GET', '/sends/' + id, null, true, true); -- return new SendResponse(r); -- } -- -- async postSendAccess(id: string, request: SendAccessRequest, apiUrl?: string): Promise { -- const addSendIdHeader = (headers: Headers) => { -- headers.set('Send-Id', id); -- }; -- const r = await this.send('POST', '/sends/access/' + id, request, false, true, apiUrl, addSendIdHeader); -- return new SendAccessResponse(r); -- } -- -- async getSendFileDownloadData(send: SendAccessView, request: SendAccessRequest, apiUrl?: string): Promise { -- const addSendIdHeader = (headers: Headers) => { -- headers.set('Send-Id', send.id); -- }; -- const r = await this.send('POST', '/sends/' + send.id + '/access/file/' + send.file.id, request, false, true, -- apiUrl, addSendIdHeader); -- return new SendFileDownloadDataResponse(r); -- } -- -- async getSends(): Promise> { -- const r = await this.send('GET', '/sends', null, true, true); -- return new ListResponse(r, SendResponse); -- } -- -- async postSend(request: SendRequest): Promise { -- const r = await this.send('POST', '/sends', request, true, true); -- return new SendResponse(r); -- } -- -- async postFileTypeSend(request: SendRequest): Promise { -- const r = await this.send('POST', '/sends/file/v2', request, true, true); -- return new SendFileUploadDataResponse(r); -- } -- -- async renewSendFileUploadUrl(sendId: string, fileId: string): Promise { -- const r = await this.send('GET', '/sends/' + sendId + '/file/' + fileId, null, true, true); -- return new SendFileUploadDataResponse(r); -- } -- -- postSendFile(sendId: string, fileId: string, data: FormData): Promise { -- return this.send('POST', '/sends/' + sendId + '/file/' + fileId, data, true, false); -- } -- -- /** -- * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. -- * This method still exists for backward compatibility with old server versions. -- */ -- async postSendFileLegacy(data: FormData): Promise { -- const r = await this.send('POST', '/sends/file', data, true, true); -- return new SendResponse(r); -- } -- -- async putSend(id: string, request: SendRequest): Promise { -- const r = await this.send('PUT', '/sends/' + id, request, true, true); -- return new SendResponse(r); -- } -- -- async putSendRemovePassword(id: string): Promise { -- const r = await this.send('PUT', '/sends/' + id + '/remove-password', null, true, true); -- return new SendResponse(r); -- } -- -- deleteSend(id: string): Promise { -- return this.send('DELETE', '/sends/' + id, null, true, false); -- } -- -- // Cipher APIs -- -- async getCipher(id: string): Promise { -- const r = await this.send('GET', '/ciphers/' + id, null, true, true); -- return new CipherResponse(r); -- } -- -- async getCipherAdmin(id: string): Promise { -- const r = await this.send('GET', '/ciphers/' + id + '/admin', null, true, true); -- return new CipherResponse(r); -- } -- -- async getCiphersOrganization(organizationId: string): Promise> { -- const r = await this.send('GET', '/ciphers/organization-details?organizationId=' + organizationId, -- null, true, true); -- return new ListResponse(r, CipherResponse); -- } -- -- async postCipher(request: CipherRequest): Promise { -- const r = await this.send('POST', '/ciphers', request, true, true); -- return new CipherResponse(r); -- } -- -- async postCipherCreate(request: CipherCreateRequest): Promise { -- const r = await this.send('POST', '/ciphers/create', request, true, true); -- return new CipherResponse(r); -- } -- -- async postCipherAdmin(request: CipherCreateRequest): Promise { -- const r = await this.send('POST', '/ciphers/admin', request, true, true); -- return new CipherResponse(r); -- } -- -- async putCipher(id: string, request: CipherRequest): Promise { -- const r = await this.send('PUT', '/ciphers/' + id, request, true, true); -- return new CipherResponse(r); -- } -- -- async putCipherAdmin(id: string, request: CipherRequest): Promise { -- const r = await this.send('PUT', '/ciphers/' + id + '/admin', request, true, true); -- return new CipherResponse(r); -- } -- -- deleteCipher(id: string): Promise { -- return this.send('DELETE', '/ciphers/' + id, null, true, false); -- } -- -- deleteCipherAdmin(id: string): Promise { -- return this.send('DELETE', '/ciphers/' + id + '/admin', null, true, false); -- } -- -- deleteManyCiphers(request: CipherBulkDeleteRequest): Promise { -- return this.send('DELETE', '/ciphers', request, true, false); -- } -- -- deleteManyCiphersAdmin(request: CipherBulkDeleteRequest): Promise { -- return this.send('DELETE', '/ciphers/admin', request, true, false); -- } -- -- putMoveCiphers(request: CipherBulkMoveRequest): Promise { -- return this.send('PUT', '/ciphers/move', request, true, false); -- } -- -- async putShareCipher(id: string, request: CipherShareRequest): Promise { -- const r = await this.send('PUT', '/ciphers/' + id + '/share', request, true, true); -- return new CipherResponse(r); -- } -- -- putShareCiphers(request: CipherBulkShareRequest): Promise { -- return this.send('PUT', '/ciphers/share', request, true, false); -- } -- -- putCipherCollections(id: string, request: CipherCollectionsRequest): Promise { -- return this.send('PUT', '/ciphers/' + id + '/collections', request, true, false); -- } -- -- putCipherCollectionsAdmin(id: string, request: CipherCollectionsRequest): Promise { -- return this.send('PUT', '/ciphers/' + id + '/collections-admin', request, true, false); -- } -- -- postPurgeCiphers(request: SecretVerificationRequest, organizationId: string = null): Promise { -- let path = '/ciphers/purge'; -- if (organizationId != null) { -- path += '?organizationId=' + organizationId; -- } -- return this.send('POST', path, request, true, false); -- } -- -- postImportCiphers(request: ImportCiphersRequest): Promise { -- return this.send('POST', '/ciphers/import', request, true, false); -- } -- -- postImportOrganizationCiphers(organizationId: string, request: ImportOrganizationCiphersRequest): Promise { -- return this.send('POST', '/ciphers/import-organization?organizationId=' + organizationId, request, true, false); -- } -- -- putDeleteCipher(id: string): Promise { -- return this.send('PUT', '/ciphers/' + id + '/delete', null, true, false); -- } -- -- putDeleteCipherAdmin(id: string): Promise { -- return this.send('PUT', '/ciphers/' + id + '/delete-admin', null, true, false); -- } -- -- putDeleteManyCiphers(request: CipherBulkDeleteRequest): Promise { -- return this.send('PUT', '/ciphers/delete', request, true, false); -- } -- -- putDeleteManyCiphersAdmin(request: CipherBulkDeleteRequest): Promise { -- return this.send('PUT', '/ciphers/delete-admin', request, true, false); -- } -- -- async putRestoreCipher(id: string): Promise { -- const r = await this.send('PUT', '/ciphers/' + id + '/restore', null, true, true); -- return new CipherResponse(r); -- } -- -- async putRestoreCipherAdmin(id: string): Promise { -- const r = await this.send('PUT', '/ciphers/' + id + '/restore-admin', null, true, true); -- return new CipherResponse(r); -- } -- -- async putRestoreManyCiphers(request: CipherBulkDeleteRequest): Promise> { -- const r = await this.send('PUT', '/ciphers/restore', request, true, true); -- return new ListResponse(r, CipherResponse); -- } -- -- // Attachments APIs -- -- async getAttachmentData(cipherId: string, attachmentId: string, emergencyAccessId?: string): Promise { -- const path = (emergencyAccessId != null ? -- '/emergency-access/' + emergencyAccessId + '/' : -- '/ciphers/') + cipherId + '/attachment/' + attachmentId; -- const r = await this.send('GET', path, null, true, true); -- return new AttachmentResponse(r); -- } -- -- async postCipherAttachment(id: string, request: AttachmentRequest): Promise { -- const r = await this.send('POST', '/ciphers/' + id + '/attachment/v2', request, true, true); -- return new AttachmentUploadDataResponse(r); -- } -- -- /** -- * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. -- * This method still exists for backward compatibility with old server versions. -- */ -- async postCipherAttachmentLegacy(id: string, data: FormData): Promise { -- const r = await this.send('POST', '/ciphers/' + id + '/attachment', data, true, true); -- return new CipherResponse(r); -- } -- -- /** -- * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. -- * This method still exists for backward compatibility with old server versions. -- */ -- async postCipherAttachmentAdminLegacy(id: string, data: FormData): Promise { -- const r = await this.send('POST', '/ciphers/' + id + '/attachment-admin', data, true, true); -- return new CipherResponse(r); -- } -- -- deleteCipherAttachment(id: string, attachmentId: string): Promise { -- return this.send('DELETE', '/ciphers/' + id + '/attachment/' + attachmentId, null, true, false); -- } -- -- deleteCipherAttachmentAdmin(id: string, attachmentId: string): Promise { -- return this.send('DELETE', '/ciphers/' + id + '/attachment/' + attachmentId + '/admin', null, true, false); -- } -- -- postShareCipherAttachment(id: string, attachmentId: string, data: FormData, -- organizationId: string): Promise { -- return this.send('POST', '/ciphers/' + id + '/attachment/' + -- attachmentId + '/share?organizationId=' + organizationId, data, true, false); -- } -- -- async renewAttachmentUploadUrl(id: string, attachmentId: string): Promise { -- const r = await this.send('GET', '/ciphers/' + id + '/attachment/' + attachmentId + '/renew', null, true, true); -- return new AttachmentUploadDataResponse(r); -- } -- -- postAttachmentFile(id: string, attachmentId: string, data: FormData): Promise { -- return this.send('POST', '/ciphers/' + id + '/attachment/' + attachmentId, data, true, false); -- } -- -- // Collections APIs -- -- async getCollectionDetails(organizationId: string, id: string): Promise { -- const r = await this.send('GET', '/organizations/' + organizationId + '/collections/' + id + '/details', -- null, true, true); -- return new CollectionGroupDetailsResponse(r); -- } -- -- async getUserCollections(): Promise> { -- const r = await this.send('GET', '/collections', null, true, true); -- return new ListResponse(r, CollectionResponse); -- } -- -- async getCollections(organizationId: string): Promise> { -- const r = await this.send('GET', '/organizations/' + organizationId + '/collections', null, true, true); -- return new ListResponse(r, CollectionResponse); -- } -- -- async getCollectionUsers(organizationId: string, id: string): Promise { -- const r = await this.send('GET', '/organizations/' + organizationId + '/collections/' + id + '/users', -- null, true, true); -- return r.map((dr: any) => new SelectionReadOnlyResponse(dr)); -- } -- -- async postCollection(organizationId: string, request: CollectionRequest): Promise { -- const r = await this.send('POST', '/organizations/' + organizationId + '/collections', request, true, true); -- return new CollectionResponse(r); -- } -- -- async putCollection(organizationId: string, id: string, request: CollectionRequest): Promise { -- const r = await this.send('PUT', '/organizations/' + organizationId + '/collections/' + id, -- request, true, true); -- return new CollectionResponse(r); -- } -- -- async putCollectionUsers(organizationId: string, id: string, request: SelectionReadOnlyRequest[]): Promise { -- await this.send('PUT', '/organizations/' + organizationId + '/collections/' + id + '/users', -- request, true, false); -- } -- -- deleteCollection(organizationId: string, id: string): Promise { -- return this.send('DELETE', '/organizations/' + organizationId + '/collections/' + id, null, true, false); -- } -- -- deleteCollectionUser(organizationId: string, id: string, organizationUserId: string): Promise { -- return this.send('DELETE', -- '/organizations/' + organizationId + '/collections/' + id + '/user/' + organizationUserId, -- null, true, false); -- } -- -- // Groups APIs -- -- async getGroupDetails(organizationId: string, id: string): Promise { -- const r = await this.send('GET', '/organizations/' + organizationId + '/groups/' + id + '/details', -- null, true, true); -- return new GroupDetailsResponse(r); -- } -- -- async getGroups(organizationId: string): Promise> { -- const r = await this.send('GET', '/organizations/' + organizationId + '/groups', null, true, true); -- return new ListResponse(r, GroupResponse); -- } -- -- async getGroupUsers(organizationId: string, id: string): Promise { -- const r = await this.send('GET', '/organizations/' + organizationId + '/groups/' + id + '/users', -- null, true, true); -- return r; -- } -- -- async postGroup(organizationId: string, request: GroupRequest): Promise { -- const r = await this.send('POST', '/organizations/' + organizationId + '/groups', request, true, true); -- return new GroupResponse(r); -- } -- -- async putGroup(organizationId: string, id: string, request: GroupRequest): Promise { -- const r = await this.send('PUT', '/organizations/' + organizationId + '/groups/' + id, request, true, true); -- return new GroupResponse(r); -- } -- -- async putGroupUsers(organizationId: string, id: string, request: string[]): Promise { -- await this.send('PUT', '/organizations/' + organizationId + '/groups/' + id + '/users', request, true, false); -- } -- -- deleteGroup(organizationId: string, id: string): Promise { -- return this.send('DELETE', '/organizations/' + organizationId + '/groups/' + id, null, true, false); -- } -- -- deleteGroupUser(organizationId: string, id: string, organizationUserId: string): Promise { -- return this.send('DELETE', -- '/organizations/' + organizationId + '/groups/' + id + '/user/' + organizationUserId, null, true, false); -- } -- -- // Policy APIs -- -- async getPolicy(organizationId: string, type: PolicyType): Promise { -- const r = await this.send('GET', '/organizations/' + organizationId + '/policies/' + type, null, true, true); -- return new PolicyResponse(r); -- } -- -- async getPolicies(organizationId: string): Promise> { -- const r = await this.send('GET', '/organizations/' + organizationId + '/policies', null, true, true); -- return new ListResponse(r, PolicyResponse); -- } -- -- async getPoliciesByToken(organizationId: string, token: string, email: string, organizationUserId: string): -- Promise> { -- const r = await this.send('GET', '/organizations/' + organizationId + '/policies/token?' + -- 'token=' + encodeURIComponent(token) + '&email=' + encodeURIComponent(email) + -- '&organizationUserId=' + organizationUserId, null, false, true); -- return new ListResponse(r, PolicyResponse); -- } -- -- async putPolicy(organizationId: string, type: PolicyType, request: PolicyRequest): Promise { -- const r = await this.send('PUT', '/organizations/' + organizationId + '/policies/' + type, request, true, true); -- return new PolicyResponse(r); -- } -- -- // Organization User APIs -- -- async getOrganizationUser(organizationId: string, id: string): Promise { -- const r = await this.send('GET', '/organizations/' + organizationId + '/users/' + id, null, true, true); -- return new OrganizationUserDetailsResponse(r); -- } -- -- async getOrganizationUserGroups(organizationId: string, id: string): Promise { -- const r = await this.send('GET', '/organizations/' + organizationId + '/users/' + id + '/groups', -- null, true, true); -- return r; -- } -- -- async getOrganizationUsers(organizationId: string): Promise> { -- const r = await this.send('GET', '/organizations/' + organizationId + '/users', null, true, true); -- return new ListResponse(r, OrganizationUserUserDetailsResponse); -- } -- -- async getOrganizationUserResetPasswordDetails(organizationId: string, id: string): -- Promise { -- const r = await this.send('GET', '/organizations/' + organizationId + '/users/' + id + -- '/reset-password-details', null, true, true); -- return new OrganizationUserResetPasswordDetailsReponse(r); -- } -- -- async getOrganizationAutoEnrollStatus(identifier: string): Promise { -- const r = await this.send('GET', '/organizations/' + identifier + '/auto-enroll-status', null, true, true); -- return new OrganizationAutoEnrollStatusResponse(r); -- } -- -- postOrganizationUserInvite(organizationId: string, request: OrganizationUserInviteRequest): Promise { -- return this.send('POST', '/organizations/' + organizationId + '/users/invite', request, true, false); -- } -- -- postOrganizationUserReinvite(organizationId: string, id: string): Promise { -- return this.send('POST', '/organizations/' + organizationId + '/users/' + id + '/reinvite', null, true, false); -- } -- -- async postManyOrganizationUserReinvite(organizationId: string, request: OrganizationUserBulkRequest): Promise> { -- const r = await this.send('POST', '/organizations/' + organizationId + '/users/reinvite', request, true, true); -- return new ListResponse(r, OrganizationUserBulkResponse); -- } -- -- postOrganizationUserAccept(organizationId: string, id: string, -- request: OrganizationUserAcceptRequest): Promise { -- return this.send('POST', '/organizations/' + organizationId + '/users/' + id + '/accept', request, true, false); -- } -- -- postOrganizationUserConfirm(organizationId: string, id: string, -- request: OrganizationUserConfirmRequest): Promise { -- return this.send('POST', '/organizations/' + organizationId + '/users/' + id + '/confirm', -- request, true, false); -- } -- -- async postOrganizationUsersPublicKey(organizationId: string, request: OrganizationUserBulkRequest): Promise> { -- const r = await this.send('POST', '/organizations/' + organizationId + '/users/public-keys', request, true, true); -- return new ListResponse(r, OrganizationUserBulkPublicKeyResponse); -- } -- -- async postOrganizationUserBulkConfirm(organizationId: string, request: OrganizationUserBulkConfirmRequest): Promise> { -- const r = await this.send('POST', '/organizations/' + organizationId + '/users/confirm', request, true, true); -- return new ListResponse(r, OrganizationUserBulkResponse); -- } -- -- putOrganizationUser(organizationId: string, id: string, request: OrganizationUserUpdateRequest): Promise { -- return this.send('PUT', '/organizations/' + organizationId + '/users/' + id, request, true, false); -- } -- -- putOrganizationUserGroups(organizationId: string, id: string, -- request: OrganizationUserUpdateGroupsRequest): Promise { -- return this.send('PUT', '/organizations/' + organizationId + '/users/' + id + '/groups', request, true, false); -- } -- -- putOrganizationUserResetPasswordEnrollment(organizationId: string, userId: string, -- request: OrganizationUserResetPasswordEnrollmentRequest): Promise { -- return this.send('PUT', '/organizations/' + organizationId + '/users/' + userId + '/reset-password-enrollment', -- request, true, false); -- } -- -- putOrganizationUserResetPassword(organizationId: string, id: string, -- request: OrganizationUserResetPasswordRequest): Promise { -- return this.send('PUT', '/organizations/' + organizationId + '/users/' + id + '/reset-password', -- request, true, false); -- } -- -- deleteOrganizationUser(organizationId: string, id: string): Promise { -- return this.send('DELETE', '/organizations/' + organizationId + '/users/' + id, null, true, false); -- } -- -- async deleteManyOrganizationUsers(organizationId: string, request: OrganizationUserBulkRequest): Promise> { -- const r = await this.send('DELETE', '/organizations/' + organizationId + '/users', request, true, true); -- return new ListResponse(r, OrganizationUserBulkResponse); -- } -- -- // Plan APIs -- -- async getPlans(): Promise> { -- const r = await this.send('GET', '/plans/', null, true, true); -- return new ListResponse(r, PlanResponse); -- } -- -- async postImportDirectory(organizationId: string, request: ImportDirectoryRequest): Promise { -- return this.send('POST', '/organizations/' + organizationId + '/import', request, true, false); -- } -- -- async postPublicImportDirectory(request: OrganizationImportRequest): Promise { -- return this.send('POST', '/public/organization/import', request, true, false); -- } -- -- async getTaxRates(): Promise> { -- const r = await this.send('GET', '/plans/sales-tax-rates/', null, true, true); -- return new ListResponse(r, TaxRateResponse); -- } -- -- // Settings APIs -- -- async getSettingsDomains(): Promise { -- const r = await this.send('GET', '/settings/domains', null, true, true); -- return new DomainsResponse(r); -- } -- -- async putSettingsDomains(request: UpdateDomainsRequest): Promise { -- const r = await this.send('PUT', '/settings/domains', request, true, true); -- return new DomainsResponse(r); -- } -- -- // Sync APIs -- -- async getSync(): Promise { -- const path = this.isDesktopClient || this.isWebClient ? '/sync?excludeDomains=true' : '/sync'; -- const r = await this.send('GET', path, null, true, true); -- return new SyncResponse(r); -- } -- -- // Two-factor APIs -- -- async getTwoFactorProviders(): Promise> { -- const r = await this.send('GET', '/two-factor', null, true, true); -- return new ListResponse(r, TwoFactorProviderResponse); -- } -- -- async getTwoFactorOrganizationProviders(organizationId: string): Promise> { -- const r = await this.send('GET', '/organizations/' + organizationId + '/two-factor', null, true, true); -- return new ListResponse(r, TwoFactorProviderResponse); -- } -- -- async getTwoFactorAuthenticator(request: SecretVerificationRequest): Promise { -- const r = await this.send('POST', '/two-factor/get-authenticator', request, true, true); -- return new TwoFactorAuthenticatorResponse(r); -- } -- -- async getTwoFactorEmail(request: SecretVerificationRequest): Promise { -- const r = await this.send('POST', '/two-factor/get-email', request, true, true); -- return new TwoFactorEmailResponse(r); -- } -- -- async getTwoFactorDuo(request: SecretVerificationRequest): Promise { -- const r = await this.send('POST', '/two-factor/get-duo', request, true, true); -- return new TwoFactorDuoResponse(r); -- } -- -- async getTwoFactorOrganizationDuo(organizationId: string, -- request: SecretVerificationRequest): Promise { -- const r = await this.send('POST', '/organizations/' + organizationId + '/two-factor/get-duo', -- request, true, true); -- return new TwoFactorDuoResponse(r); -- } -- -- async getTwoFactorYubiKey(request: SecretVerificationRequest): Promise { -- const r = await this.send('POST', '/two-factor/get-yubikey', request, true, true); -- return new TwoFactorYubiKeyResponse(r); -- } -- -- async getTwoFactorWebAuthn(request: SecretVerificationRequest): Promise { -- const r = await this.send('POST', '/two-factor/get-webauthn', request, true, true); -- return new TwoFactorWebAuthnResponse(r); -- } -- -- async getTwoFactorWebAuthnChallenge(request: SecretVerificationRequest): Promise { -- const r = await this.send('POST', '/two-factor/get-webauthn-challenge', request, true, true); -- return new ChallengeResponse(r); -- } -- -- async getTwoFactorRecover(request: SecretVerificationRequest): Promise { -- const r = await this.send('POST', '/two-factor/get-recover', request, true, true); -- return new TwoFactorRecoverResponse(r); -- } -- -- async putTwoFactorAuthenticator( -- request: UpdateTwoFactorAuthenticatorRequest): Promise { -- const r = await this.send('PUT', '/two-factor/authenticator', request, true, true); -- return new TwoFactorAuthenticatorResponse(r); -- } -- -- async putTwoFactorEmail(request: UpdateTwoFactorEmailRequest): Promise { -- const r = await this.send('PUT', '/two-factor/email', request, true, true); -- return new TwoFactorEmailResponse(r); -- } -- -- async putTwoFactorDuo(request: UpdateTwoFactorDuoRequest): Promise { -- const r = await this.send('PUT', '/two-factor/duo', request, true, true); -- return new TwoFactorDuoResponse(r); -- } -- -- async putTwoFactorOrganizationDuo(organizationId: string, -- request: UpdateTwoFactorDuoRequest): Promise { -- const r = await this.send('PUT', '/organizations/' + organizationId + '/two-factor/duo', request, true, true); -- return new TwoFactorDuoResponse(r); -- } -- -- async putTwoFactorYubiKey(request: UpdateTwoFactorYubioOtpRequest): Promise { -- const r = await this.send('PUT', '/two-factor/yubikey', request, true, true); -- return new TwoFactorYubiKeyResponse(r); -- } -- -- async putTwoFactorWebAuthn(request: UpdateTwoFactorWebAuthnRequest): Promise { -- const response = request.deviceResponse.response as AuthenticatorAttestationResponse; -- const data: any = Object.assign({}, request); -- -- data.deviceResponse = { -- id: request.deviceResponse.id, -- rawId: btoa(request.deviceResponse.id), -- type: request.deviceResponse.type, -- extensions: request.deviceResponse.getClientExtensionResults(), -- response: { -- AttestationObject: Utils.fromBufferToB64(response.attestationObject), -- clientDataJson: Utils.fromBufferToB64(response.clientDataJSON), -- }, -- }; -- -- const r = await this.send('PUT', '/two-factor/webauthn', data, true, true); -- return new TwoFactorWebAuthnResponse(r); -- } -- -- async deleteTwoFactorWebAuthn(request: UpdateTwoFactorWebAuthnDeleteRequest): Promise { -- const r = await this.send('DELETE', '/two-factor/webauthn', request, true, true); -- return new TwoFactorWebAuthnResponse(r); -- } -- -- async putTwoFactorDisable(request: TwoFactorProviderRequest): Promise { -- const r = await this.send('PUT', '/two-factor/disable', request, true, true); -- return new TwoFactorProviderResponse(r); -- } -- -- async putTwoFactorOrganizationDisable(organizationId: string, -- request: TwoFactorProviderRequest): Promise { -- const r = await this.send('PUT', '/organizations/' + organizationId + '/two-factor/disable', -- request, true, true); -- return new TwoFactorProviderResponse(r); -- } -- -- postTwoFactorRecover(request: TwoFactorRecoveryRequest): Promise { -- return this.send('POST', '/two-factor/recover', request, false, false); -- } -- -- postTwoFactorEmailSetup(request: TwoFactorEmailRequest): Promise { -- return this.send('POST', '/two-factor/send-email', request, true, false); -- } -- -- postTwoFactorEmail(request: TwoFactorEmailRequest): Promise { -- return this.send('POST', '/two-factor/send-email-login', request, false, false); -- } -- -- // Emergency Access APIs -- -- async getEmergencyAccessTrusted(): Promise> { -- const r = await this.send('GET', '/emergency-access/trusted', null, true, true); -- return new ListResponse(r, EmergencyAccessGranteeDetailsResponse); -- } -- -- async getEmergencyAccessGranted(): Promise> { -- const r = await this.send('GET', '/emergency-access/granted', null, true, true); -- return new ListResponse(r, EmergencyAccessGrantorDetailsResponse); -- } -- -- async getEmergencyAccess(id: string): Promise { -- const r = await this.send('GET', '/emergency-access/' + id, null, true, true); -- return new EmergencyAccessGranteeDetailsResponse(r); -- } -- -- async getEmergencyGrantorPolicies(id: string): Promise> { -- const r = await this.send('GET', '/emergency-access/' + id + '/policies', null, true, true); -- return new ListResponse(r, PolicyResponse); -- } -- -- putEmergencyAccess(id: string, request: EmergencyAccessUpdateRequest): Promise { -- return this.send('PUT', '/emergency-access/' + id, request, true, false); -- } -- -- deleteEmergencyAccess(id: string): Promise { -- return this.send('DELETE', '/emergency-access/' + id, null, true, false); -- } -- -- postEmergencyAccessInvite(request: EmergencyAccessInviteRequest): Promise { -- return this.send('POST', '/emergency-access/invite', request, true, false); -- } -- -- postEmergencyAccessReinvite(id: string): Promise { -- return this.send('POST', '/emergency-access/' + id + '/reinvite', null, true, false); -- } -- -- postEmergencyAccessAccept(id: string, request: EmergencyAccessAcceptRequest): Promise { -- return this.send('POST', '/emergency-access/' + id + '/accept', request, true, false); -- } -- -- postEmergencyAccessConfirm(id: string, request: EmergencyAccessConfirmRequest): Promise { -- return this.send('POST', '/emergency-access/' + id + '/confirm', request, true, false); -- } -- -- postEmergencyAccessInitiate(id: string): Promise { -- return this.send('POST', '/emergency-access/' + id + '/initiate', null, true, false); -- } -- -- postEmergencyAccessApprove(id: string): Promise { -- return this.send('POST', '/emergency-access/' + id + '/approve', null, true, false); -- } -- -- postEmergencyAccessReject(id: string): Promise { -- return this.send('POST', '/emergency-access/' + id + '/reject', null, true, false); -- } -- -- async postEmergencyAccessTakeover(id: string): Promise { -- const r = await this.send('POST', '/emergency-access/' + id + '/takeover', null, true, true); -- return new EmergencyAccessTakeoverResponse(r); -- } -- -- async postEmergencyAccessPassword(id: string, request: EmergencyAccessPasswordRequest): Promise { -- const r = await this.send('POST', '/emergency-access/' + id + '/password', request, true, true); -- } -- -- async postEmergencyAccessView(id: string): Promise { -- const r = await this.send('POST', '/emergency-access/' + id + '/view', null, true, true); -- return new EmergencyAccessViewResponse(r); -- } -- -- // Organization APIs -- -- async getOrganization(id: string): Promise { -- const r = await this.send('GET', '/organizations/' + id, null, true, true); -- return new OrganizationResponse(r); -- } -- -- async getOrganizationBilling(id: string): Promise { -- const r = await this.send('GET', '/organizations/' + id + '/billing', null, true, true); -- return new BillingResponse(r); -- } -- -- async getOrganizationSubscription(id: string): Promise { -- const r = await this.send('GET', '/organizations/' + id + '/subscription', null, true, true); -- return new OrganizationSubscriptionResponse(r); -- } -- -- async getOrganizationLicense(id: string, installationId: string): Promise { -- return this.send('GET', '/organizations/' + id + '/license?installationId=' + installationId, -- null, true, true); -- } -- -- async getOrganizationTaxInfo(id: string): Promise { -- const r = await this.send('GET', '/organizations/' + id + '/tax', null, true, true); -- return new TaxInfoResponse(r); -- } -- -- async getOrganizationSso(id: string): Promise { -- const r = await this.send('GET', '/organizations/' + id + '/sso', null, true, true); -- return new OrganizationSsoResponse(r); -- } -- -- async postOrganization(request: OrganizationCreateRequest): Promise { -- const r = await this.send('POST', '/organizations', request, true, true); -- return new OrganizationResponse(r); -- } -- -- async putOrganization(id: string, request: OrganizationUpdateRequest): Promise { -- const r = await this.send('PUT', '/organizations/' + id, request, true, true); -- return new OrganizationResponse(r); -- } -- -- async putOrganizationTaxInfo(id: string, request: OrganizationTaxInfoUpdateRequest): Promise { -- return this.send('PUT', '/organizations/' + id + '/tax', request, true, false); -- } -- -- postLeaveOrganization(id: string): Promise { -- return this.send('POST', '/organizations/' + id + '/leave', null, true, false); -- } -- -- async postOrganizationLicense(data: FormData): Promise { -- const r = await this.send('POST', '/organizations/license', data, true, true); -- return new OrganizationResponse(r); -- } -- -- async postOrganizationLicenseUpdate(id: string, data: FormData): Promise { -- return this.send('POST', '/organizations/' + id + '/license', data, true, false); -- } -- -- async postOrganizationApiKey(id: string, request: SecretVerificationRequest): Promise { -- const r = await this.send('POST', '/organizations/' + id + '/api-key', request, true, true); -- return new ApiKeyResponse(r); -- } -- -- async postOrganizationRotateApiKey(id: string, request: SecretVerificationRequest): Promise { -- const r = await this.send('POST', '/organizations/' + id + '/rotate-api-key', request, true, true); -- return new ApiKeyResponse(r); -- } -- -- async postOrganizationSso(id: string, request: OrganizationSsoRequest): Promise { -- const r = await this.send('POST', '/organizations/' + id + '/sso', request, true, true); -- return new OrganizationSsoResponse(r); -- } -- -- async postOrganizationUpgrade(id: string, request: OrganizationUpgradeRequest): Promise { -- const r = await this.send('POST', '/organizations/' + id + '/upgrade', request, true, true); -- return new PaymentResponse(r); -- } -- -- async postOrganizationUpdateSubscription(id: string, request: OrganizationSubscriptionUpdateRequest): Promise { -- return this.send('POST', '/organizations/' + id + '/subscription', request, true, false); -- } -- -- async postOrganizationSeat(id: string, request: SeatRequest): Promise { -- const r = await this.send('POST', '/organizations/' + id + '/seat', request, true, true); -- return new PaymentResponse(r); -- } -- -- async postOrganizationStorage(id: string, request: StorageRequest): Promise { -- const r = await this.send('POST', '/organizations/' + id + '/storage', request, true, true); -- return new PaymentResponse(r); -- } -- -- postOrganizationPayment(id: string, request: PaymentRequest): Promise { -- return this.send('POST', '/organizations/' + id + '/payment', request, true, false); -- } -- -- postOrganizationVerifyBank(id: string, request: VerifyBankRequest): Promise { -- return this.send('POST', '/organizations/' + id + '/verify-bank', request, true, false); -- } -- -- postOrganizationCancel(id: string): Promise { -- return this.send('POST', '/organizations/' + id + '/cancel', null, true, false); -- } -- -- postOrganizationReinstate(id: string): Promise { -- return this.send('POST', '/organizations/' + id + '/reinstate', null, true, false); -- } -- -- deleteOrganization(id: string, request: SecretVerificationRequest): Promise { -- return this.send('DELETE', '/organizations/' + id, request, true, false); -- } -- -- async getOrganizationKeys(id: string): Promise { -- const r = await this.send('GET', '/organizations/' + id + '/keys', null, true, true); -- return new OrganizationKeysResponse(r); -- } -- -- async postOrganizationKeys(id: string, request: OrganizationKeysRequest): Promise { -- const r = await this.send('POST', '/organizations/' + id + '/keys', request, true, true); -- return new OrganizationKeysResponse(r); -- } -- -- // Provider APIs -- -- async postProviderSetup(id: string, request: ProviderSetupRequest) { -- const r = await this.send('POST', '/providers/' + id + '/setup', request, true, true); -- return new ProviderResponse(r); -- } -- -- async getProvider(id: string) { -- const r = await this.send('GET', '/providers/' + id, null, true, true); -- return new ProviderResponse(r); -- } -- -- async putProvider(id: string, request: ProviderUpdateRequest) { -- const r = await this.send('PUT', '/providers/' + id, request, true, true); -- return new ProviderResponse(r); -- } -- -- // Provider User APIs -- -- async getProviderUsers(providerId: string): Promise> { -- const r = await this.send('GET', '/providers/' + providerId + '/users', null, true, true); -- return new ListResponse(r, ProviderUserUserDetailsResponse); -- } -- -- async getProviderUser(providerId: string, id: string): Promise { -- const r = await this.send('GET', '/providers/' + providerId + '/users/' + id, null, true, true); -- return new ProviderUserResponse(r); -- } -- -- postProviderUserInvite(providerId: string, request: ProviderUserInviteRequest): Promise { -- return this.send('POST', '/providers/' + providerId + '/users/invite', request, true, false); -- } -- -- postProviderUserReinvite(providerId: string, id: string): Promise { -- return this.send('POST', '/providers/' + providerId + '/users/' + id + '/reinvite', null, true, false); -- } -- -- async postManyProviderUserReinvite(providerId: string, request: ProviderUserBulkRequest): Promise> { -- const r = await this.send('POST', '/providers/' + providerId + '/users/reinvite', request, true, true); -- return new ListResponse(r, ProviderUserBulkResponse); -- } -- -- async postProviderUserBulkConfirm(providerId: string, request: ProviderUserBulkConfirmRequest): Promise> { -- const r = await this.send('POST', '/providers/' + providerId + '/users/confirm', request, true, true); -- return new ListResponse(r, ProviderUserBulkResponse); -- } -- -- async deleteManyProviderUsers(providerId: string, request: ProviderUserBulkRequest): Promise> { -- const r = await this.send('DELETE', '/providers/' + providerId + '/users', request, true, true); -- return new ListResponse(r, ProviderUserBulkResponse); -- } -- -- postProviderUserAccept(providerId: string, id: string, request: ProviderUserAcceptRequest): Promise { -- return this.send('POST', '/providers/' + providerId + '/users/' + id + '/accept', request, true, false); -- } -- -- postProviderUserConfirm(providerId: string, id: string, request: ProviderUserConfirmRequest): Promise { -- return this.send('POST', '/providers/' + providerId + '/users/' + id + '/confirm', -- request, true, false); -- } -- -- async postProviderUsersPublicKey(providerId: string, request: ProviderUserBulkRequest): Promise> { -- const r = await this.send('POST', '/providers/' + providerId + '/users/public-keys', request, true, true); -- return new ListResponse(r, ProviderUserBulkPublicKeyResponse); -- } -- -- -- putProviderUser(providerId: string, id: string, request: ProviderUserUpdateRequest): Promise { -- return this.send('PUT', '/providers/' + providerId + '/users/' + id, request, true, false); -- } -- -- deleteProviderUser(providerId: string, id: string): Promise { -- return this.send('DELETE', '/providers/' + providerId + '/users/' + id, null, true, false); -- } -- -- // Provider Organization APIs -- -- async getProviderClients(providerId: string): Promise> { -- const r = await this.send('GET', '/providers/' + providerId + '/organizations', null, true, true); -- return new ListResponse(r, ProviderOrganizationOrganizationDetailsResponse); -- } -- -- postProviderAddOrganization(providerId: string, request: ProviderAddOrganizationRequest): Promise { -- return this.send('POST', '/providers/' + providerId + '/organizations/add', request, true, false); -- } -- -- async postProviderCreateOrganization(providerId: string, request: ProviderOrganizationCreateRequest): Promise { -- const r = await this.send('POST', '/providers/' + providerId + '/organizations', request, true, true); -- return new ProviderOrganizationResponse(r); -- } -- -- deleteProviderOrganization(providerId: string, id: string): Promise { -- return this.send('DELETE', '/providers/' + providerId + '/organizations/' + id, null, true, false); -- } -- -- // Event APIs -- -- async getEvents(start: string, end: string, token: string): Promise> { -- const r = await this.send('GET', this.addEventParameters('/events', start, end, token), null, true, true); -- return new ListResponse(r, EventResponse); -- } -- -- async getEventsCipher(id: string, start: string, end: string, -- token: string): Promise> { -- const r = await this.send('GET', this.addEventParameters('/ciphers/' + id + '/events', start, end, token), -- null, true, true); -- return new ListResponse(r, EventResponse); -- } -- -- async getEventsOrganization(id: string, start: string, end: string, -- token: string): Promise> { -- const r = await this.send('GET', this.addEventParameters('/organizations/' + id + '/events', start, end, token), -- null, true, true); -- return new ListResponse(r, EventResponse); -- } -- -- async getEventsOrganizationUser(organizationId: string, id: string, -- start: string, end: string, token: string): Promise> { -- const r = await this.send('GET', -- this.addEventParameters('/organizations/' + organizationId + '/users/' + id + '/events', start, end, token), -- null, true, true); -- return new ListResponse(r, EventResponse); -- } -- -- async getEventsProvider(id: string, start: string, end: string, token: string): Promise> { -- const r = await this.send('GET', this.addEventParameters('/providers/' + id + '/events', start, end, token), null, true, true); -- return new ListResponse(r, EventResponse); -- } -- -- async getEventsProviderUser(providerId: string, id: string, -- start: string, end: string, token: string): Promise> { -- const r = await this.send('GET', -- this.addEventParameters('/providers/' + providerId + '/users/' + id + '/events', start, end, token), -- null, true, true); -- return new ListResponse(r, EventResponse); -- } -- -- async postEventsCollect(request: EventRequest[]): Promise { -- const authHeader = await this.getActiveBearerToken(); -- const headers = new Headers({ -- 'Device-Type': this.deviceType, -- 'Authorization': 'Bearer ' + authHeader, -- 'Content-Type': 'application/json; charset=utf-8', -- }); -- if (this.customUserAgent != null) { -- headers.set('User-Agent', this.customUserAgent); -- } -- const response = await this.fetch(new Request(this.environmentService.getEventsUrl() + '/collect', { -- cache: 'no-store', -- credentials: this.getCredentials(), -- method: 'POST', -- body: JSON.stringify(request), -- headers: headers, -- })); -- if (response.status !== 200) { -- return Promise.reject('Event post failed.'); -- } -- } -- -- // User APIs -- -- async getUserPublicKey(id: string): Promise { -- const r = await this.send('GET', '/users/' + id + '/public-key', null, true, true); -- return new UserKeyResponse(r); -- } -- -- // HIBP APIs -- -- async getHibpBreach(username: string): Promise { -- const r = await this.send('GET', '/hibp/breach?username=' + username, null, true, true); -- return r.map((a: any) => new BreachAccountResponse(a)); -- } -- -- // Misc -- -- async postBitPayInvoice(request: BitPayInvoiceRequest): Promise { -- const r = await this.send('POST', '/bitpay-invoice', request, true, true); -- return r as string; -- } -- -- async postSetupPayment(): Promise { -- const r = await this.send('POST', '/setup-payment', null, true, true); -- return r as string; -- } -- -- // Key Connector -- -- async getUserKeyFromKeyConnector(keyConnectorUrl: string): Promise { -- const authHeader = await this.getActiveBearerToken(); -- -- const response = await this.fetch(new Request(keyConnectorUrl + '/user-keys', { -- cache: 'no-store', -- method: 'GET', -- headers: new Headers({ -- 'Accept': 'application/json', -- 'Authorization': 'Bearer ' + authHeader, -- }), -- })); -- -- if (response.status !== 200) { -- const error = await this.handleError(response, false, true); -- return Promise.reject(error); -- } -- -- return new KeyConnectorUserKeyResponse(await response.json()); -- } -- -- async postUserKeyToKeyConnector(keyConnectorUrl: string, request: KeyConnectorUserKeyRequest): Promise { -- const authHeader = await this.getActiveBearerToken(); -- -- const response = await this.fetch(new Request(keyConnectorUrl + '/user-keys', { -- cache: 'no-store', -- method: 'POST', -- headers: new Headers({ -- 'Accept': 'application/json', -- 'Authorization': 'Bearer ' + authHeader, -- 'Content-Type': 'application/json; charset=utf-8', -- }), -- body: JSON.stringify(request), -- })); -- -- if (response.status !== 200) { -- const error = await this.handleError(response, false, true); -- return Promise.reject(error); -- } -- } -- -- async getKeyConnectorAlive(keyConnectorUrl: string) { -- const response = await this.fetch(new Request(keyConnectorUrl + '/alive', { -- cache: 'no-store', -- method: 'GET', -- headers: new Headers({ -- 'Accept': 'application/json', -- 'Content-Type': 'application/json; charset=utf-8', -- }), -- })); -- -- if (response.status !== 200) { -- const error = await this.handleError(response, false, true); -- return Promise.reject(error); -- } -- } -- -- // Helpers -- -- async getActiveBearerToken(): Promise { -- let accessToken = await this.tokenService.getToken(); -- if (this.tokenService.tokenNeedsRefresh()) { -- await this.doAuthRefresh(); -- accessToken = await this.tokenService.getToken(); -- } -- return accessToken; -- } -- -- fetch(request: Request): Promise { -- if (request.method === 'GET') { -- request.headers.set('Cache-Control', 'no-store'); -- request.headers.set('Pragma', 'no-cache'); -- } -- return this.nativeFetch(request); -- } -- -- nativeFetch(request: Request): Promise { -- return fetch(request); -- } -- -- async preValidateSso(identifier: string): Promise { -- if (identifier == null || identifier === '') { -- throw new Error('Organization Identifier was not provided.'); -- } -- const headers = new Headers({ -- 'Accept': 'application/json', -- 'Device-Type': this.deviceType, -- }); -- if (this.customUserAgent != null) { -- headers.set('User-Agent', this.customUserAgent); -- } -- -- const path = `/account/prevalidate?domainHint=${encodeURIComponent(identifier)}`; -- const response = await this.fetch(new Request(this.environmentService.getIdentityUrl() + path, { -- cache: 'no-store', -- credentials: this.getCredentials(), -- headers: headers, -- method: 'GET', -- })); -- -- if (response.status === 200) { -- return true; -- } else { -- const error = await this.handleError(response, false, true); -- return Promise.reject(error); -- } -- } -- -- async postCreateSponsorship(sponsoredOrgId: string, request: OrganizationSponsorshipCreateRequest): Promise { -- return await this.send('POST', -- '/organization/sponsorship/' + sponsoredOrgId + '/families-for-enterprise', -- request, true, false); -- } -- -- async deleteRevokeSponsorship(sponsoringOrganizationId: string): Promise { -- return await this.send('DELETE', -- '/organization/sponsorship/' + sponsoringOrganizationId, -- null, true, false); -- } -- -- async deleteRemoveSponsorship(sponsoringOrgId: string): Promise { -- return await this.send('DELETE', -- '/organization/sponsorship/sponsored/' + sponsoringOrgId, -- null, true, false); -- } -- -- async postPreValidateSponsorshipToken(sponsorshipToken: string): Promise { -- const r = await this.send('POST', '/organization/sponsorship/validate-token?sponsorshipToken=' + encodeURIComponent(sponsorshipToken), -- null, true, true); -- return r as boolean; -- } -- -- async postRedeemSponsorship(sponsorshipToken: string, request: OrganizationSponsorshipRedeemRequest): Promise { -- return await this.send('POST', '/organization/sponsorship/redeem?sponsorshipToken=' + encodeURIComponent(sponsorshipToken), -- request, true, false); -- } -- -- async postResendSponsorshipOffer(sponsoringOrgId: string): Promise { -- return await this.send('POST', -- '/organization/sponsorship/' + sponsoringOrgId + '/families-for-enterprise/resend', -- null, true, false); -- } -- -- -- protected async doAuthRefresh(): Promise { -- const refreshToken = await this.tokenService.getRefreshToken(); -- if (refreshToken != null && refreshToken !== '') { -- return this.doRefreshToken(); -- } -- -- const clientId = await this.tokenService.getClientId(); -- const clientSecret = await this.tokenService.getClientSecret(); -- if (!Utils.isNullOrWhitespace(clientId) && !Utils.isNullOrWhitespace(clientSecret)) { -- return this.doApiTokenRefresh(); -- } -- -- throw new Error('Cannot refresh token, no refresh token or api keys are stored'); -- } -- -- protected async doApiTokenRefresh(): Promise { -- const clientId = await this.tokenService.getClientId(); -- const clientSecret = await this.tokenService.getClientSecret(); -- if (Utils.isNullOrWhitespace(clientId) || Utils.isNullOrWhitespace(clientSecret) || this.apiKeyRefresh == null) { -- throw new Error(); -- } -- -- await this.apiKeyRefresh(clientId, clientSecret); -- } -- -- protected async doRefreshToken(): Promise { -- const refreshToken = await this.tokenService.getRefreshToken(); -- if (refreshToken == null || refreshToken === '') { -- throw new Error(); -- } -- const headers = new Headers({ -- 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', -- 'Accept': 'application/json', -- 'Device-Type': this.deviceType, -- }); -- if (this.customUserAgent != null) { -- headers.set('User-Agent', this.customUserAgent); -- } -- -- const decodedToken = this.tokenService.decodeToken(); -- const response = await this.fetch(new Request(this.environmentService.getIdentityUrl() + '/connect/token', { -- body: this.qsStringify({ -- grant_type: 'refresh_token', -- client_id: decodedToken.client_id, -- refresh_token: refreshToken, -- }), -- cache: 'no-store', -- credentials: this.getCredentials(), -- headers: headers, -- method: 'POST', -- })); -- -- if (response.status === 200) { -- const responseJson = await response.json(); -- const tokenResponse = new IdentityTokenResponse(responseJson); -- await this.tokenService.setTokens(tokenResponse.accessToken, tokenResponse.refreshToken, null); -+export class ApiService implements ApiServiceAbstraction { -+ protected apiKeyRefresh: (clientId: string, clientSecret: string) => Promise; -+ private device: DeviceType; -+ private deviceType: string; -+ private isWebClient = false; -+ private isDesktopClient = false; -+ -+ constructor( -+ private tokenService: TokenService, -+ private platformUtilsService: PlatformUtilsService, -+ private environmentService: EnvironmentService, -+ private logoutCallback: (expired: boolean) => Promise, -+ private customUserAgent: string = null -+ ) { -+ this.device = platformUtilsService.getDevice(); -+ this.deviceType = this.device.toString(); -+ this.isWebClient = -+ this.device === DeviceType.IEBrowser || -+ this.device === DeviceType.ChromeBrowser || -+ this.device === DeviceType.EdgeBrowser || -+ this.device === DeviceType.FirefoxBrowser || -+ this.device === DeviceType.OperaBrowser || -+ this.device === DeviceType.SafariBrowser || -+ this.device === DeviceType.UnknownBrowser || -+ this.device === DeviceType.VivaldiBrowser; -+ this.isDesktopClient = -+ this.device === DeviceType.WindowsDesktop || -+ this.device === DeviceType.MacOsDesktop || -+ this.device === DeviceType.LinuxDesktop; -+ } -+ -+ // Auth APIs -+ -+ async postIdentityToken( -+ request: TokenRequest -+ ): Promise { -+ const headers = new Headers({ -+ "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", -+ Accept: "application/json", -+ "Device-Type": this.deviceType, -+ }); -+ if (this.customUserAgent != null) { -+ headers.set("User-Agent", this.customUserAgent); -+ } -+ request.alterIdentityTokenHeaders(headers); -+ const response = await this.fetch( -+ new Request(this.environmentService.getIdentityUrl() + "/connect/token", { -+ body: this.qsStringify( -+ request.toIdentityToken(request.clientId ?? this.platformUtilsService.identityClientId) -+ ), -+ credentials: this.getCredentials(), -+ cache: "no-store", -+ headers: headers, -+ method: "POST", -+ }) -+ ); -+ -+ let responseJson: any = null; -+ if (this.isJsonResponse(response)) { -+ responseJson = await response.json(); -+ } -+ -+ if (responseJson != null) { -+ if (response.status === 200) { -+ return new IdentityTokenResponse(responseJson); -+ } else if ( -+ response.status === 400 && -+ responseJson.TwoFactorProviders2 && -+ Object.keys(responseJson.TwoFactorProviders2).length -+ ) { -+ await this.tokenService.clearTwoFactorToken(request.email); -+ return new IdentityTwoFactorResponse(responseJson); -+ } else if ( -+ response.status === 400 && -+ responseJson.HCaptcha_SiteKey && -+ Object.keys(responseJson.HCaptcha_SiteKey).length -+ ) { -+ return new IdentityCaptchaResponse(responseJson); -+ } -+ } -+ -+ return Promise.reject(new ErrorResponse(responseJson, response.status, true)); -+ } -+ -+ async refreshIdentityToken(): Promise { -+ try { -+ await this.doAuthRefresh(); -+ } catch (e) { -+ return Promise.reject(null); -+ } -+ } -+ -+ // Account APIs -+ -+ async getProfile(): Promise { -+ const r = await this.send("GET", "/accounts/profile", null, true, true); -+ return new ProfileResponse(r); -+ } -+ -+ async getUserBilling(): Promise { -+ const r = await this.send("GET", "/accounts/billing", null, true, true); -+ return new BillingResponse(r); -+ } -+ -+ async getUserSubscription(): Promise { -+ const r = await this.send("GET", "/accounts/subscription", null, true, true); -+ return new SubscriptionResponse(r); -+ } -+ -+ async getTaxInfo(): Promise { -+ const r = await this.send("GET", "/accounts/tax", null, true, true); -+ return new TaxInfoResponse(r); -+ } -+ -+ async putProfile(request: UpdateProfileRequest): Promise { -+ const r = await this.send("PUT", "/accounts/profile", request, true, true); -+ return new ProfileResponse(r); -+ } -+ -+ putTaxInfo(request: TaxInfoUpdateRequest): Promise { -+ return this.send("PUT", "/accounts/tax", request, true, false); -+ } -+ -+ async postPrelogin(request: PreloginRequest): Promise { -+ const r = await this.send("POST", "/accounts/prelogin", request, false, true); -+ return new PreloginResponse(r); -+ } -+ -+ postEmailToken(request: EmailTokenRequest): Promise { -+ return this.send("POST", "/accounts/email-token", request, true, false); -+ } -+ -+ postEmail(request: EmailRequest): Promise { -+ return this.send("POST", "/accounts/email", request, true, false); -+ } -+ -+ postPassword(request: PasswordRequest): Promise { -+ return this.send("POST", "/accounts/password", request, true, false); -+ } -+ -+ setPassword(request: SetPasswordRequest): Promise { -+ return this.send("POST", "/accounts/set-password", request, true, false); -+ } -+ -+ postSetKeyConnectorKey(request: SetKeyConnectorKeyRequest): Promise { -+ return this.send("POST", "/accounts/set-key-connector-key", request, true, false); -+ } -+ -+ postSecurityStamp(request: SecretVerificationRequest): Promise { -+ return this.send("POST", "/accounts/security-stamp", request, true, false); -+ } -+ -+ deleteAccount(request: SecretVerificationRequest): Promise { -+ return this.send("DELETE", "/accounts", request, true, false); -+ } -+ -+ async getAccountRevisionDate(): Promise { -+ const r = await this.send("GET", "/accounts/revision-date", null, true, true); -+ return r as number; -+ } -+ -+ postPasswordHint(request: PasswordHintRequest): Promise { -+ return this.send("POST", "/accounts/password-hint", request, false, false); -+ } -+ -+ postRegister(request: RegisterRequest): Promise { -+ return this.send("POST", "/accounts/register", request, false, false); -+ } -+ -+ async postPremium(data: FormData): Promise { -+ const r = await this.send("POST", "/accounts/premium", data, true, true); -+ return new PaymentResponse(r); -+ } -+ -+ async postIapCheck(request: IapCheckRequest): Promise { -+ return this.send("POST", "/accounts/iap-check", request, true, false); -+ } -+ -+ postReinstatePremium(): Promise { -+ return this.send("POST", "/accounts/reinstate-premium", null, true, false); -+ } -+ -+ postCancelPremium(): Promise { -+ return this.send("POST", "/accounts/cancel-premium", null, true, false); -+ } -+ -+ async postAccountStorage(request: StorageRequest): Promise { -+ const r = await this.send("POST", "/accounts/storage", request, true, true); -+ return new PaymentResponse(r); -+ } -+ -+ postAccountPayment(request: PaymentRequest): Promise { -+ return this.send("POST", "/accounts/payment", request, true, false); -+ } -+ -+ postAccountLicense(data: FormData): Promise { -+ return this.send("POST", "/accounts/license", data, true, false); -+ } -+ -+ postAccountKeys(request: KeysRequest): Promise { -+ return this.send("POST", "/accounts/keys", request, true, false); -+ } -+ -+ postAccountKey(request: UpdateKeyRequest): Promise { -+ return this.send("POST", "/accounts/key", request, true, false); -+ } -+ -+ postAccountVerifyEmail(): Promise { -+ return this.send("POST", "/accounts/verify-email", null, true, false); -+ } -+ -+ postAccountVerifyEmailToken(request: VerifyEmailRequest): Promise { -+ return this.send("POST", "/accounts/verify-email-token", request, false, false); -+ } -+ -+ postAccountVerifyPassword(request: SecretVerificationRequest): Promise { -+ return this.send("POST", "/accounts/verify-password", request, true, false); -+ } -+ -+ postAccountRecoverDelete(request: DeleteRecoverRequest): Promise { -+ return this.send("POST", "/accounts/delete-recover", request, false, false); -+ } -+ -+ postAccountRecoverDeleteToken(request: VerifyDeleteRecoverRequest): Promise { -+ return this.send("POST", "/accounts/delete-recover-token", request, false, false); -+ } -+ -+ postAccountKdf(request: KdfRequest): Promise { -+ return this.send("POST", "/accounts/kdf", request, true, false); -+ } -+ -+ async deleteSsoUser(organizationId: string): Promise { -+ return this.send("DELETE", "/accounts/sso/" + organizationId, null, true, false); -+ } -+ -+ async getSsoUserIdentifier(): Promise { -+ return this.send("GET", "/accounts/sso/user-identifier", null, true, true); -+ } -+ -+ async postUserApiKey(id: string, request: SecretVerificationRequest): Promise { -+ const r = await this.send("POST", "/accounts/api-key", request, true, true); -+ return new ApiKeyResponse(r); -+ } -+ -+ async postUserRotateApiKey( -+ id: string, -+ request: SecretVerificationRequest -+ ): Promise { -+ const r = await this.send("POST", "/accounts/rotate-api-key", request, true, true); -+ return new ApiKeyResponse(r); -+ } -+ -+ putUpdateTempPassword(request: UpdateTempPasswordRequest): Promise { -+ return this.send("PUT", "/accounts/update-temp-password", request, true, false); -+ } -+ -+ postAccountRequestOTP(): Promise { -+ return this.send("POST", "/accounts/request-otp", null, true, false); -+ } -+ -+ postAccountVerifyOTP(request: VerifyOTPRequest): Promise { -+ return this.send("POST", "/accounts/verify-otp", request, true, false); -+ } -+ -+ postConvertToKeyConnector(): Promise { -+ return this.send("POST", "/accounts/convert-to-key-connector", null, true, false); -+ } -+ -+ // Folder APIs -+ -+ async getFolder(id: string): Promise { -+ const r = await this.send("GET", "/folders/" + id, null, true, true); -+ return new FolderResponse(r); -+ } -+ -+ async postFolder(request: FolderRequest): Promise { -+ const r = await this.send("POST", "/folders", request, true, true); -+ return new FolderResponse(r); -+ } -+ -+ async putFolder(id: string, request: FolderRequest): Promise { -+ const r = await this.send("PUT", "/folders/" + id, request, true, true); -+ return new FolderResponse(r); -+ } -+ -+ deleteFolder(id: string): Promise { -+ return this.send("DELETE", "/folders/" + id, null, true, false); -+ } -+ -+ // Send APIs -+ -+ async getSend(id: string): Promise { -+ const r = await this.send("GET", "/sends/" + id, null, true, true); -+ return new SendResponse(r); -+ } -+ -+ async postSendAccess( -+ id: string, -+ request: SendAccessRequest, -+ apiUrl?: string -+ ): Promise { -+ const addSendIdHeader = (headers: Headers) => { -+ headers.set("Send-Id", id); -+ }; -+ const r = await this.send( -+ "POST", -+ "/sends/access/" + id, -+ request, -+ false, -+ true, -+ apiUrl, -+ addSendIdHeader -+ ); -+ return new SendAccessResponse(r); -+ } -+ -+ async getSendFileDownloadData( -+ send: SendAccessView, -+ request: SendAccessRequest, -+ apiUrl?: string -+ ): Promise { -+ const addSendIdHeader = (headers: Headers) => { -+ headers.set("Send-Id", send.id); -+ }; -+ const r = await this.send( -+ "POST", -+ "/sends/" + send.id + "/access/file/" + send.file.id, -+ request, -+ false, -+ true, -+ apiUrl, -+ addSendIdHeader -+ ); -+ return new SendFileDownloadDataResponse(r); -+ } -+ -+ async getSends(): Promise> { -+ const r = await this.send("GET", "/sends", null, true, true); -+ return new ListResponse(r, SendResponse); -+ } -+ -+ async postSend(request: SendRequest): Promise { -+ const r = await this.send("POST", "/sends", request, true, true); -+ return new SendResponse(r); -+ } -+ -+ async postFileTypeSend(request: SendRequest): Promise { -+ const r = await this.send("POST", "/sends/file/v2", request, true, true); -+ return new SendFileUploadDataResponse(r); -+ } -+ -+ async renewSendFileUploadUrl( -+ sendId: string, -+ fileId: string -+ ): Promise { -+ const r = await this.send("GET", "/sends/" + sendId + "/file/" + fileId, null, true, true); -+ return new SendFileUploadDataResponse(r); -+ } -+ -+ postSendFile(sendId: string, fileId: string, data: FormData): Promise { -+ return this.send("POST", "/sends/" + sendId + "/file/" + fileId, data, true, false); -+ } -+ -+ /** -+ * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. -+ * This method still exists for backward compatibility with old server versions. -+ */ -+ async postSendFileLegacy(data: FormData): Promise { -+ const r = await this.send("POST", "/sends/file", data, true, true); -+ return new SendResponse(r); -+ } -+ -+ async putSend(id: string, request: SendRequest): Promise { -+ const r = await this.send("PUT", "/sends/" + id, request, true, true); -+ return new SendResponse(r); -+ } -+ -+ async putSendRemovePassword(id: string): Promise { -+ const r = await this.send("PUT", "/sends/" + id + "/remove-password", null, true, true); -+ return new SendResponse(r); -+ } -+ -+ deleteSend(id: string): Promise { -+ return this.send("DELETE", "/sends/" + id, null, true, false); -+ } -+ -+ // Cipher APIs -+ -+ async getCipher(id: string): Promise { -+ const r = await this.send("GET", "/ciphers/" + id, null, true, true); -+ return new CipherResponse(r); -+ } -+ -+ async getCipherAdmin(id: string): Promise { -+ const r = await this.send("GET", "/ciphers/" + id + "/admin", null, true, true); -+ return new CipherResponse(r); -+ } -+ -+ async getCiphersOrganization(organizationId: string): Promise> { -+ const r = await this.send( -+ "GET", -+ "/ciphers/organization-details?organizationId=" + organizationId, -+ null, -+ true, -+ true -+ ); -+ return new ListResponse(r, CipherResponse); -+ } -+ -+ async postCipher(request: CipherRequest): Promise { -+ const r = await this.send("POST", "/ciphers", request, true, true); -+ return new CipherResponse(r); -+ } -+ -+ async postCipherCreate(request: CipherCreateRequest): Promise { -+ const r = await this.send("POST", "/ciphers/create", request, true, true); -+ return new CipherResponse(r); -+ } -+ -+ async postCipherAdmin(request: CipherCreateRequest): Promise { -+ const r = await this.send("POST", "/ciphers/admin", request, true, true); -+ return new CipherResponse(r); -+ } -+ -+ async putCipher(id: string, request: CipherRequest): Promise { -+ const r = await this.send("PUT", "/ciphers/" + id, request, true, true); -+ return new CipherResponse(r); -+ } -+ -+ async putCipherAdmin(id: string, request: CipherRequest): Promise { -+ const r = await this.send("PUT", "/ciphers/" + id + "/admin", request, true, true); -+ return new CipherResponse(r); -+ } -+ -+ deleteCipher(id: string): Promise { -+ return this.send("DELETE", "/ciphers/" + id, null, true, false); -+ } -+ -+ deleteCipherAdmin(id: string): Promise { -+ return this.send("DELETE", "/ciphers/" + id + "/admin", null, true, false); -+ } -+ -+ deleteManyCiphers(request: CipherBulkDeleteRequest): Promise { -+ return this.send("DELETE", "/ciphers", request, true, false); -+ } -+ -+ deleteManyCiphersAdmin(request: CipherBulkDeleteRequest): Promise { -+ return this.send("DELETE", "/ciphers/admin", request, true, false); -+ } -+ -+ putMoveCiphers(request: CipherBulkMoveRequest): Promise { -+ return this.send("PUT", "/ciphers/move", request, true, false); -+ } -+ -+ async putShareCipher(id: string, request: CipherShareRequest): Promise { -+ const r = await this.send("PUT", "/ciphers/" + id + "/share", request, true, true); -+ return new CipherResponse(r); -+ } -+ -+ putShareCiphers(request: CipherBulkShareRequest): Promise { -+ return this.send("PUT", "/ciphers/share", request, true, false); -+ } -+ -+ putCipherCollections(id: string, request: CipherCollectionsRequest): Promise { -+ return this.send("PUT", "/ciphers/" + id + "/collections", request, true, false); -+ } -+ -+ putCipherCollectionsAdmin(id: string, request: CipherCollectionsRequest): Promise { -+ return this.send("PUT", "/ciphers/" + id + "/collections-admin", request, true, false); -+ } -+ -+ postPurgeCiphers( -+ request: SecretVerificationRequest, -+ organizationId: string = null -+ ): Promise { -+ let path = "/ciphers/purge"; -+ if (organizationId != null) { -+ path += "?organizationId=" + organizationId; -+ } -+ return this.send("POST", path, request, true, false); -+ } -+ -+ postImportCiphers(request: ImportCiphersRequest): Promise { -+ return this.send("POST", "/ciphers/import", request, true, false); -+ } -+ -+ postImportOrganizationCiphers( -+ organizationId: string, -+ request: ImportOrganizationCiphersRequest -+ ): Promise { -+ return this.send( -+ "POST", -+ "/ciphers/import-organization?organizationId=" + organizationId, -+ request, -+ true, -+ false -+ ); -+ } -+ -+ putDeleteCipher(id: string): Promise { -+ return this.send("PUT", "/ciphers/" + id + "/delete", null, true, false); -+ } -+ -+ putDeleteCipherAdmin(id: string): Promise { -+ return this.send("PUT", "/ciphers/" + id + "/delete-admin", null, true, false); -+ } -+ -+ putDeleteManyCiphers(request: CipherBulkDeleteRequest): Promise { -+ return this.send("PUT", "/ciphers/delete", request, true, false); -+ } -+ -+ putDeleteManyCiphersAdmin(request: CipherBulkDeleteRequest): Promise { -+ return this.send("PUT", "/ciphers/delete-admin", request, true, false); -+ } -+ -+ async putRestoreCipher(id: string): Promise { -+ const r = await this.send("PUT", "/ciphers/" + id + "/restore", null, true, true); -+ return new CipherResponse(r); -+ } -+ -+ async putRestoreCipherAdmin(id: string): Promise { -+ const r = await this.send("PUT", "/ciphers/" + id + "/restore-admin", null, true, true); -+ return new CipherResponse(r); -+ } -+ -+ async putRestoreManyCiphers( -+ request: CipherBulkDeleteRequest -+ ): Promise> { -+ const r = await this.send("PUT", "/ciphers/restore", request, true, true); -+ return new ListResponse(r, CipherResponse); -+ } -+ -+ // Attachments APIs -+ -+ async getAttachmentData( -+ cipherId: string, -+ attachmentId: string, -+ emergencyAccessId?: string -+ ): Promise { -+ const path = -+ (emergencyAccessId != null ? "/emergency-access/" + emergencyAccessId + "/" : "/ciphers/") + -+ cipherId + -+ "/attachment/" + -+ attachmentId; -+ const r = await this.send("GET", path, null, true, true); -+ return new AttachmentResponse(r); -+ } -+ -+ async postCipherAttachment( -+ id: string, -+ request: AttachmentRequest -+ ): Promise { -+ const r = await this.send("POST", "/ciphers/" + id + "/attachment/v2", request, true, true); -+ return new AttachmentUploadDataResponse(r); -+ } -+ -+ /** -+ * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. -+ * This method still exists for backward compatibility with old server versions. -+ */ -+ async postCipherAttachmentLegacy(id: string, data: FormData): Promise { -+ const r = await this.send("POST", "/ciphers/" + id + "/attachment", data, true, true); -+ return new CipherResponse(r); -+ } -+ -+ /** -+ * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. -+ * This method still exists for backward compatibility with old server versions. -+ */ -+ async postCipherAttachmentAdminLegacy(id: string, data: FormData): Promise { -+ const r = await this.send("POST", "/ciphers/" + id + "/attachment-admin", data, true, true); -+ return new CipherResponse(r); -+ } -+ -+ deleteCipherAttachment(id: string, attachmentId: string): Promise { -+ return this.send("DELETE", "/ciphers/" + id + "/attachment/" + attachmentId, null, true, false); -+ } -+ -+ deleteCipherAttachmentAdmin(id: string, attachmentId: string): Promise { -+ return this.send( -+ "DELETE", -+ "/ciphers/" + id + "/attachment/" + attachmentId + "/admin", -+ null, -+ true, -+ false -+ ); -+ } -+ -+ postShareCipherAttachment( -+ id: string, -+ attachmentId: string, -+ data: FormData, -+ organizationId: string -+ ): Promise { -+ return this.send( -+ "POST", -+ "/ciphers/" + id + "/attachment/" + attachmentId + "/share?organizationId=" + organizationId, -+ data, -+ true, -+ false -+ ); -+ } -+ -+ async renewAttachmentUploadUrl( -+ id: string, -+ attachmentId: string -+ ): Promise { -+ const r = await this.send( -+ "GET", -+ "/ciphers/" + id + "/attachment/" + attachmentId + "/renew", -+ null, -+ true, -+ true -+ ); -+ return new AttachmentUploadDataResponse(r); -+ } -+ -+ postAttachmentFile(id: string, attachmentId: string, data: FormData): Promise { -+ return this.send("POST", "/ciphers/" + id + "/attachment/" + attachmentId, data, true, false); -+ } -+ -+ // Collections APIs -+ -+ async getCollectionDetails( -+ organizationId: string, -+ id: string -+ ): Promise { -+ const r = await this.send( -+ "GET", -+ "/organizations/" + organizationId + "/collections/" + id + "/details", -+ null, -+ true, -+ true -+ ); -+ return new CollectionGroupDetailsResponse(r); -+ } -+ -+ async getUserCollections(): Promise> { -+ const r = await this.send("GET", "/collections", null, true, true); -+ return new ListResponse(r, CollectionResponse); -+ } -+ -+ async getCollections(organizationId: string): Promise> { -+ const r = await this.send( -+ "GET", -+ "/organizations/" + organizationId + "/collections", -+ null, -+ true, -+ true -+ ); -+ return new ListResponse(r, CollectionResponse); -+ } -+ -+ async getCollectionUsers( -+ organizationId: string, -+ id: string -+ ): Promise { -+ const r = await this.send( -+ "GET", -+ "/organizations/" + organizationId + "/collections/" + id + "/users", -+ null, -+ true, -+ true -+ ); -+ return r.map((dr: any) => new SelectionReadOnlyResponse(dr)); -+ } -+ -+ async postCollection( -+ organizationId: string, -+ request: CollectionRequest -+ ): Promise { -+ const r = await this.send( -+ "POST", -+ "/organizations/" + organizationId + "/collections", -+ request, -+ true, -+ true -+ ); -+ return new CollectionResponse(r); -+ } -+ -+ async putCollection( -+ organizationId: string, -+ id: string, -+ request: CollectionRequest -+ ): Promise { -+ const r = await this.send( -+ "PUT", -+ "/organizations/" + organizationId + "/collections/" + id, -+ request, -+ true, -+ true -+ ); -+ return new CollectionResponse(r); -+ } -+ -+ async putCollectionUsers( -+ organizationId: string, -+ id: string, -+ request: SelectionReadOnlyRequest[] -+ ): Promise { -+ await this.send( -+ "PUT", -+ "/organizations/" + organizationId + "/collections/" + id + "/users", -+ request, -+ true, -+ false -+ ); -+ } -+ -+ deleteCollection(organizationId: string, id: string): Promise { -+ return this.send( -+ "DELETE", -+ "/organizations/" + organizationId + "/collections/" + id, -+ null, -+ true, -+ false -+ ); -+ } -+ -+ deleteCollectionUser( -+ organizationId: string, -+ id: string, -+ organizationUserId: string -+ ): Promise { -+ return this.send( -+ "DELETE", -+ "/organizations/" + organizationId + "/collections/" + id + "/user/" + organizationUserId, -+ null, -+ true, -+ false -+ ); -+ } -+ -+ // Groups APIs -+ -+ async getGroupDetails(organizationId: string, id: string): Promise { -+ const r = await this.send( -+ "GET", -+ "/organizations/" + organizationId + "/groups/" + id + "/details", -+ null, -+ true, -+ true -+ ); -+ return new GroupDetailsResponse(r); -+ } -+ -+ async getGroups(organizationId: string): Promise> { -+ const r = await this.send( -+ "GET", -+ "/organizations/" + organizationId + "/groups", -+ null, -+ true, -+ true -+ ); -+ return new ListResponse(r, GroupResponse); -+ } -+ -+ async getGroupUsers(organizationId: string, id: string): Promise { -+ const r = await this.send( -+ "GET", -+ "/organizations/" + organizationId + "/groups/" + id + "/users", -+ null, -+ true, -+ true -+ ); -+ return r; -+ } -+ -+ async postGroup(organizationId: string, request: GroupRequest): Promise { -+ const r = await this.send( -+ "POST", -+ "/organizations/" + organizationId + "/groups", -+ request, -+ true, -+ true -+ ); -+ return new GroupResponse(r); -+ } -+ -+ async putGroup( -+ organizationId: string, -+ id: string, -+ request: GroupRequest -+ ): Promise { -+ const r = await this.send( -+ "PUT", -+ "/organizations/" + organizationId + "/groups/" + id, -+ request, -+ true, -+ true -+ ); -+ return new GroupResponse(r); -+ } -+ -+ async putGroupUsers(organizationId: string, id: string, request: string[]): Promise { -+ await this.send( -+ "PUT", -+ "/organizations/" + organizationId + "/groups/" + id + "/users", -+ request, -+ true, -+ false -+ ); -+ } -+ -+ deleteGroup(organizationId: string, id: string): Promise { -+ return this.send( -+ "DELETE", -+ "/organizations/" + organizationId + "/groups/" + id, -+ null, -+ true, -+ false -+ ); -+ } -+ -+ deleteGroupUser(organizationId: string, id: string, organizationUserId: string): Promise { -+ return this.send( -+ "DELETE", -+ "/organizations/" + organizationId + "/groups/" + id + "/user/" + organizationUserId, -+ null, -+ true, -+ false -+ ); -+ } -+ -+ // Policy APIs -+ -+ async getPolicy(organizationId: string, type: PolicyType): Promise { -+ const r = await this.send( -+ "GET", -+ "/organizations/" + organizationId + "/policies/" + type, -+ null, -+ true, -+ true -+ ); -+ return new PolicyResponse(r); -+ } -+ -+ async getPolicies(organizationId: string): Promise> { -+ const r = await this.send( -+ "GET", -+ "/organizations/" + organizationId + "/policies", -+ null, -+ true, -+ true -+ ); -+ return new ListResponse(r, PolicyResponse); -+ } -+ -+ async getPoliciesByToken( -+ organizationId: string, -+ token: string, -+ email: string, -+ organizationUserId: string -+ ): Promise> { -+ const r = await this.send( -+ "GET", -+ "/organizations/" + -+ organizationId + -+ "/policies/token?" + -+ "token=" + -+ encodeURIComponent(token) + -+ "&email=" + -+ encodeURIComponent(email) + -+ "&organizationUserId=" + -+ organizationUserId, -+ null, -+ false, -+ true -+ ); -+ return new ListResponse(r, PolicyResponse); -+ } -+ -+ async getPoliciesByInvitedUser( -+ organizationId: string, -+ userId: string -+ ): Promise> { -+ const r = await this.send( -+ "GET", -+ "/organizations/" + organizationId + "/policies/invited-user?" + "userId=" + userId, -+ null, -+ false, -+ true -+ ); -+ return new ListResponse(r, PolicyResponse); -+ } -+ -+ async putPolicy( -+ organizationId: string, -+ type: PolicyType, -+ request: PolicyRequest -+ ): Promise { -+ const r = await this.send( -+ "PUT", -+ "/organizations/" + organizationId + "/policies/" + type, -+ request, -+ true, -+ true -+ ); -+ return new PolicyResponse(r); -+ } -+ -+ // Organization User APIs -+ -+ async getOrganizationUser( -+ organizationId: string, -+ id: string -+ ): Promise { -+ const r = await this.send( -+ "GET", -+ "/organizations/" + organizationId + "/users/" + id, -+ null, -+ true, -+ true -+ ); -+ return new OrganizationUserDetailsResponse(r); -+ } -+ -+ async getOrganizationUserGroups(organizationId: string, id: string): Promise { -+ const r = await this.send( -+ "GET", -+ "/organizations/" + organizationId + "/users/" + id + "/groups", -+ null, -+ true, -+ true -+ ); -+ return r; -+ } -+ -+ async getOrganizationUsers( -+ organizationId: string -+ ): Promise> { -+ const r = await this.send( -+ "GET", -+ "/organizations/" + organizationId + "/users", -+ null, -+ true, -+ true -+ ); -+ return new ListResponse(r, OrganizationUserUserDetailsResponse); -+ } -+ -+ async getOrganizationUserResetPasswordDetails( -+ organizationId: string, -+ id: string -+ ): Promise { -+ const r = await this.send( -+ "GET", -+ "/organizations/" + organizationId + "/users/" + id + "/reset-password-details", -+ null, -+ true, -+ true -+ ); -+ return new OrganizationUserResetPasswordDetailsReponse(r); -+ } -+ -+ async getOrganizationAutoEnrollStatus( -+ identifier: string -+ ): Promise { -+ const r = await this.send( -+ "GET", -+ "/organizations/" + identifier + "/auto-enroll-status", -+ null, -+ true, -+ true -+ ); -+ return new OrganizationAutoEnrollStatusResponse(r); -+ } -+ -+ postOrganizationUserInvite( -+ organizationId: string, -+ request: OrganizationUserInviteRequest -+ ): Promise { -+ return this.send( -+ "POST", -+ "/organizations/" + organizationId + "/users/invite", -+ request, -+ true, -+ false -+ ); -+ } -+ -+ postOrganizationUserReinvite(organizationId: string, id: string): Promise { -+ return this.send( -+ "POST", -+ "/organizations/" + organizationId + "/users/" + id + "/reinvite", -+ null, -+ true, -+ false -+ ); -+ } -+ -+ async postManyOrganizationUserReinvite( -+ organizationId: string, -+ request: OrganizationUserBulkRequest -+ ): Promise> { -+ const r = await this.send( -+ "POST", -+ "/organizations/" + organizationId + "/users/reinvite", -+ request, -+ true, -+ true -+ ); -+ return new ListResponse(r, OrganizationUserBulkResponse); -+ } -+ -+ postOrganizationUserAccept( -+ organizationId: string, -+ id: string, -+ request: OrganizationUserAcceptRequest -+ ): Promise { -+ return this.send( -+ "POST", -+ "/organizations/" + organizationId + "/users/" + id + "/accept", -+ request, -+ true, -+ false -+ ); -+ } -+ -+ postOrganizationUserConfirm( -+ organizationId: string, -+ id: string, -+ request: OrganizationUserConfirmRequest -+ ): Promise { -+ return this.send( -+ "POST", -+ "/organizations/" + organizationId + "/users/" + id + "/confirm", -+ request, -+ true, -+ false -+ ); -+ } -+ -+ async postOrganizationUsersPublicKey( -+ organizationId: string, -+ request: OrganizationUserBulkRequest -+ ): Promise> { -+ const r = await this.send( -+ "POST", -+ "/organizations/" + organizationId + "/users/public-keys", -+ request, -+ true, -+ true -+ ); -+ return new ListResponse(r, OrganizationUserBulkPublicKeyResponse); -+ } -+ -+ async postOrganizationUserBulkConfirm( -+ organizationId: string, -+ request: OrganizationUserBulkConfirmRequest -+ ): Promise> { -+ const r = await this.send( -+ "POST", -+ "/organizations/" + organizationId + "/users/confirm", -+ request, -+ true, -+ true -+ ); -+ return new ListResponse(r, OrganizationUserBulkResponse); -+ } -+ -+ putOrganizationUser( -+ organizationId: string, -+ id: string, -+ request: OrganizationUserUpdateRequest -+ ): Promise { -+ return this.send( -+ "PUT", -+ "/organizations/" + organizationId + "/users/" + id, -+ request, -+ true, -+ false -+ ); -+ } -+ -+ putOrganizationUserGroups( -+ organizationId: string, -+ id: string, -+ request: OrganizationUserUpdateGroupsRequest -+ ): Promise { -+ return this.send( -+ "PUT", -+ "/organizations/" + organizationId + "/users/" + id + "/groups", -+ request, -+ true, -+ false -+ ); -+ } -+ -+ putOrganizationUserResetPasswordEnrollment( -+ organizationId: string, -+ userId: string, -+ request: OrganizationUserResetPasswordEnrollmentRequest -+ ): Promise { -+ return this.send( -+ "PUT", -+ "/organizations/" + organizationId + "/users/" + userId + "/reset-password-enrollment", -+ request, -+ true, -+ false -+ ); -+ } -+ -+ putOrganizationUserResetPassword( -+ organizationId: string, -+ id: string, -+ request: OrganizationUserResetPasswordRequest -+ ): Promise { -+ return this.send( -+ "PUT", -+ "/organizations/" + organizationId + "/users/" + id + "/reset-password", -+ request, -+ true, -+ false -+ ); -+ } -+ -+ deleteOrganizationUser(organizationId: string, id: string): Promise { -+ return this.send( -+ "DELETE", -+ "/organizations/" + organizationId + "/users/" + id, -+ null, -+ true, -+ false -+ ); -+ } -+ -+ async deleteManyOrganizationUsers( -+ organizationId: string, -+ request: OrganizationUserBulkRequest -+ ): Promise> { -+ const r = await this.send( -+ "DELETE", -+ "/organizations/" + organizationId + "/users", -+ request, -+ true, -+ true -+ ); -+ return new ListResponse(r, OrganizationUserBulkResponse); -+ } -+ -+ // Plan APIs -+ -+ async getPlans(): Promise> { -+ const r = await this.send("GET", "/plans/", null, true, true); -+ return new ListResponse(r, PlanResponse); -+ } -+ -+ async postImportDirectory(organizationId: string, request: ImportDirectoryRequest): Promise { -+ return this.send("POST", "/organizations/" + organizationId + "/import", request, true, false); -+ } -+ -+ async postPublicImportDirectory(request: OrganizationImportRequest): Promise { -+ return this.send("POST", "/public/organization/import", request, true, false); -+ } -+ -+ async getTaxRates(): Promise> { -+ const r = await this.send("GET", "/plans/sales-tax-rates/", null, true, true); -+ return new ListResponse(r, TaxRateResponse); -+ } -+ -+ // Settings APIs -+ -+ async getSettingsDomains(): Promise { -+ const r = await this.send("GET", "/settings/domains", null, true, true); -+ return new DomainsResponse(r); -+ } -+ -+ async putSettingsDomains(request: UpdateDomainsRequest): Promise { -+ const r = await this.send("PUT", "/settings/domains", request, true, true); -+ return new DomainsResponse(r); -+ } -+ -+ // Sync APIs -+ -+ async getSync(): Promise { -+ const path = this.isDesktopClient || this.isWebClient ? "/sync?excludeDomains=true" : "/sync"; -+ const r = await this.send("GET", path, null, true, true); -+ return new SyncResponse(r); -+ } -+ -+ // Two-factor APIs -+ -+ async getTwoFactorProviders(): Promise> { -+ const r = await this.send("GET", "/two-factor", null, true, true); -+ return new ListResponse(r, TwoFactorProviderResponse); -+ } -+ -+ async getTwoFactorOrganizationProviders( -+ organizationId: string -+ ): Promise> { -+ const r = await this.send( -+ "GET", -+ "/organizations/" + organizationId + "/two-factor", -+ null, -+ true, -+ true -+ ); -+ return new ListResponse(r, TwoFactorProviderResponse); -+ } -+ -+ async getTwoFactorAuthenticator( -+ request: SecretVerificationRequest -+ ): Promise { -+ const r = await this.send("POST", "/two-factor/get-authenticator", request, true, true); -+ return new TwoFactorAuthenticatorResponse(r); -+ } -+ -+ async getTwoFactorEmail(request: SecretVerificationRequest): Promise { -+ const r = await this.send("POST", "/two-factor/get-email", request, true, true); -+ return new TwoFactorEmailResponse(r); -+ } -+ -+ async getTwoFactorDuo(request: SecretVerificationRequest): Promise { -+ const r = await this.send("POST", "/two-factor/get-duo", request, true, true); -+ return new TwoFactorDuoResponse(r); -+ } -+ -+ async getTwoFactorOrganizationDuo( -+ organizationId: string, -+ request: SecretVerificationRequest -+ ): Promise { -+ const r = await this.send( -+ "POST", -+ "/organizations/" + organizationId + "/two-factor/get-duo", -+ request, -+ true, -+ true -+ ); -+ return new TwoFactorDuoResponse(r); -+ } -+ -+ async getTwoFactorYubiKey(request: SecretVerificationRequest): Promise { -+ const r = await this.send("POST", "/two-factor/get-yubikey", request, true, true); -+ return new TwoFactorYubiKeyResponse(r); -+ } -+ -+ async getTwoFactorWebAuthn( -+ request: SecretVerificationRequest -+ ): Promise { -+ const r = await this.send("POST", "/two-factor/get-webauthn", request, true, true); -+ return new TwoFactorWebAuthnResponse(r); -+ } -+ -+ async getTwoFactorWebAuthnChallenge( -+ request: SecretVerificationRequest -+ ): Promise { -+ const r = await this.send("POST", "/two-factor/get-webauthn-challenge", request, true, true); -+ return new ChallengeResponse(r); -+ } -+ -+ async getTwoFactorRecover(request: SecretVerificationRequest): Promise { -+ const r = await this.send("POST", "/two-factor/get-recover", request, true, true); -+ return new TwoFactorRecoverResponse(r); -+ } -+ -+ async putTwoFactorAuthenticator( -+ request: UpdateTwoFactorAuthenticatorRequest -+ ): Promise { -+ const r = await this.send("PUT", "/two-factor/authenticator", request, true, true); -+ return new TwoFactorAuthenticatorResponse(r); -+ } -+ -+ async putTwoFactorEmail(request: UpdateTwoFactorEmailRequest): Promise { -+ const r = await this.send("PUT", "/two-factor/email", request, true, true); -+ return new TwoFactorEmailResponse(r); -+ } -+ -+ async putTwoFactorDuo(request: UpdateTwoFactorDuoRequest): Promise { -+ const r = await this.send("PUT", "/two-factor/duo", request, true, true); -+ return new TwoFactorDuoResponse(r); -+ } -+ -+ async putTwoFactorOrganizationDuo( -+ organizationId: string, -+ request: UpdateTwoFactorDuoRequest -+ ): Promise { -+ const r = await this.send( -+ "PUT", -+ "/organizations/" + organizationId + "/two-factor/duo", -+ request, -+ true, -+ true -+ ); -+ return new TwoFactorDuoResponse(r); -+ } -+ -+ async putTwoFactorYubiKey( -+ request: UpdateTwoFactorYubioOtpRequest -+ ): Promise { -+ const r = await this.send("PUT", "/two-factor/yubikey", request, true, true); -+ return new TwoFactorYubiKeyResponse(r); -+ } -+ -+ async putTwoFactorWebAuthn( -+ request: UpdateTwoFactorWebAuthnRequest -+ ): Promise { -+ const response = request.deviceResponse.response as AuthenticatorAttestationResponse; -+ const data: any = Object.assign({}, request); -+ -+ data.deviceResponse = { -+ id: request.deviceResponse.id, -+ rawId: btoa(request.deviceResponse.id), -+ type: request.deviceResponse.type, -+ extensions: request.deviceResponse.getClientExtensionResults(), -+ response: { -+ AttestationObject: Utils.fromBufferToB64(response.attestationObject), -+ clientDataJson: Utils.fromBufferToB64(response.clientDataJSON), -+ }, -+ }; -+ -+ const r = await this.send("PUT", "/two-factor/webauthn", data, true, true); -+ return new TwoFactorWebAuthnResponse(r); -+ } -+ -+ async deleteTwoFactorWebAuthn( -+ request: UpdateTwoFactorWebAuthnDeleteRequest -+ ): Promise { -+ const r = await this.send("DELETE", "/two-factor/webauthn", request, true, true); -+ return new TwoFactorWebAuthnResponse(r); -+ } -+ -+ async putTwoFactorDisable(request: TwoFactorProviderRequest): Promise { -+ const r = await this.send("PUT", "/two-factor/disable", request, true, true); -+ return new TwoFactorProviderResponse(r); -+ } -+ -+ async putTwoFactorOrganizationDisable( -+ organizationId: string, -+ request: TwoFactorProviderRequest -+ ): Promise { -+ const r = await this.send( -+ "PUT", -+ "/organizations/" + organizationId + "/two-factor/disable", -+ request, -+ true, -+ true -+ ); -+ return new TwoFactorProviderResponse(r); -+ } -+ -+ postTwoFactorRecover(request: TwoFactorRecoveryRequest): Promise { -+ return this.send("POST", "/two-factor/recover", request, false, false); -+ } -+ -+ postTwoFactorEmailSetup(request: TwoFactorEmailRequest): Promise { -+ return this.send("POST", "/two-factor/send-email", request, true, false); -+ } -+ -+ postTwoFactorEmail(request: TwoFactorEmailRequest): Promise { -+ return this.send("POST", "/two-factor/send-email-login", request, false, false); -+ } -+ -+ // Emergency Access APIs -+ -+ async getEmergencyAccessTrusted(): Promise> { -+ const r = await this.send("GET", "/emergency-access/trusted", null, true, true); -+ return new ListResponse(r, EmergencyAccessGranteeDetailsResponse); -+ } -+ -+ async getEmergencyAccessGranted(): Promise> { -+ const r = await this.send("GET", "/emergency-access/granted", null, true, true); -+ return new ListResponse(r, EmergencyAccessGrantorDetailsResponse); -+ } -+ -+ async getEmergencyAccess(id: string): Promise { -+ const r = await this.send("GET", "/emergency-access/" + id, null, true, true); -+ return new EmergencyAccessGranteeDetailsResponse(r); -+ } -+ -+ async getEmergencyGrantorPolicies(id: string): Promise> { -+ const r = await this.send("GET", "/emergency-access/" + id + "/policies", null, true, true); -+ return new ListResponse(r, PolicyResponse); -+ } -+ -+ putEmergencyAccess(id: string, request: EmergencyAccessUpdateRequest): Promise { -+ return this.send("PUT", "/emergency-access/" + id, request, true, false); -+ } -+ -+ deleteEmergencyAccess(id: string): Promise { -+ return this.send("DELETE", "/emergency-access/" + id, null, true, false); -+ } -+ -+ postEmergencyAccessInvite(request: EmergencyAccessInviteRequest): Promise { -+ return this.send("POST", "/emergency-access/invite", request, true, false); -+ } -+ -+ postEmergencyAccessReinvite(id: string): Promise { -+ return this.send("POST", "/emergency-access/" + id + "/reinvite", null, true, false); -+ } -+ -+ postEmergencyAccessAccept(id: string, request: EmergencyAccessAcceptRequest): Promise { -+ return this.send("POST", "/emergency-access/" + id + "/accept", request, true, false); -+ } -+ -+ postEmergencyAccessConfirm(id: string, request: EmergencyAccessConfirmRequest): Promise { -+ return this.send("POST", "/emergency-access/" + id + "/confirm", request, true, false); -+ } -+ -+ postEmergencyAccessInitiate(id: string): Promise { -+ return this.send("POST", "/emergency-access/" + id + "/initiate", null, true, false); -+ } -+ -+ postEmergencyAccessApprove(id: string): Promise { -+ return this.send("POST", "/emergency-access/" + id + "/approve", null, true, false); -+ } -+ -+ postEmergencyAccessReject(id: string): Promise { -+ return this.send("POST", "/emergency-access/" + id + "/reject", null, true, false); -+ } -+ -+ async postEmergencyAccessTakeover(id: string): Promise { -+ const r = await this.send("POST", "/emergency-access/" + id + "/takeover", null, true, true); -+ return new EmergencyAccessTakeoverResponse(r); -+ } -+ -+ async postEmergencyAccessPassword( -+ id: string, -+ request: EmergencyAccessPasswordRequest -+ ): Promise { -+ const r = await this.send("POST", "/emergency-access/" + id + "/password", request, true, true); -+ } -+ -+ async postEmergencyAccessView(id: string): Promise { -+ const r = await this.send("POST", "/emergency-access/" + id + "/view", null, true, true); -+ return new EmergencyAccessViewResponse(r); -+ } -+ -+ // Organization APIs -+ -+ async getOrganization(id: string): Promise { -+ const r = await this.send("GET", "/organizations/" + id, null, true, true); -+ return new OrganizationResponse(r); -+ } -+ -+ async getOrganizationBilling(id: string): Promise { -+ const r = await this.send("GET", "/organizations/" + id + "/billing", null, true, true); -+ return new BillingResponse(r); -+ } -+ -+ async getOrganizationSubscription(id: string): Promise { -+ const r = await this.send("GET", "/organizations/" + id + "/subscription", null, true, true); -+ return new OrganizationSubscriptionResponse(r); -+ } -+ -+ async getOrganizationLicense(id: string, installationId: string): Promise { -+ return this.send( -+ "GET", -+ "/organizations/" + id + "/license?installationId=" + installationId, -+ null, -+ true, -+ true -+ ); -+ } -+ -+ async getOrganizationTaxInfo(id: string): Promise { -+ const r = await this.send("GET", "/organizations/" + id + "/tax", null, true, true); -+ return new TaxInfoResponse(r); -+ } -+ -+ async getOrganizationSso(id: string): Promise { -+ const r = await this.send("GET", "/organizations/" + id + "/sso", null, true, true); -+ return new OrganizationSsoResponse(r); -+ } -+ -+ async postOrganization(request: OrganizationCreateRequest): Promise { -+ const r = await this.send("POST", "/organizations", request, true, true); -+ return new OrganizationResponse(r); -+ } -+ -+ async putOrganization( -+ id: string, -+ request: OrganizationUpdateRequest -+ ): Promise { -+ const r = await this.send("PUT", "/organizations/" + id, request, true, true); -+ return new OrganizationResponse(r); -+ } -+ -+ async getSsoConfig(id: string): Promise { -+ const r = await this.send('GET', '/organizations/' + id + '/sso', null, true, true); -+ return new SsoConfigResponse(r); -+ } -+ -+ async putOrganizationSso(id: string, request: OrganizationSsoUpdateRequest): Promise { -+ const r = await this.send('PUT', '/organizations/' + id + '/sso', request, true, false); -+ return new SsoConfigResponse(r); -+ } -+ -+ async putOrganizationTaxInfo( -+ id: string, -+ request: OrganizationTaxInfoUpdateRequest -+ ): Promise { -+ return this.send("PUT", "/organizations/" + id + "/tax", request, true, false); -+ } -+ -+ postLeaveOrganization(id: string): Promise { -+ return this.send("POST", "/organizations/" + id + "/leave", null, true, false); -+ } -+ -+ async postOrganizationLicense(data: FormData): Promise { -+ const r = await this.send("POST", "/organizations/license", data, true, true); -+ return new OrganizationResponse(r); -+ } -+ -+ async postOrganizationLicenseUpdate(id: string, data: FormData): Promise { -+ return this.send("POST", "/organizations/" + id + "/license", data, true, false); -+ } -+ -+ async postOrganizationApiKey( -+ id: string, -+ request: SecretVerificationRequest -+ ): Promise { -+ const r = await this.send("POST", "/organizations/" + id + "/api-key", request, true, true); -+ return new ApiKeyResponse(r); -+ } -+ -+ async postOrganizationRotateApiKey( -+ id: string, -+ request: SecretVerificationRequest -+ ): Promise { -+ const r = await this.send( -+ "POST", -+ "/organizations/" + id + "/rotate-api-key", -+ request, -+ true, -+ true -+ ); -+ return new ApiKeyResponse(r); -+ } -+ -+ async postOrganizationSso( -+ id: string, -+ request: OrganizationSsoRequest -+ ): Promise { -+ const r = await this.send("POST", "/organizations/" + id + "/sso", request, true, true); -+ return new OrganizationSsoResponse(r); -+ } -+ -+ async postOrganizationUpgrade( -+ id: string, -+ request: OrganizationUpgradeRequest -+ ): Promise { -+ const r = await this.send("POST", "/organizations/" + id + "/upgrade", request, true, true); -+ return new PaymentResponse(r); -+ } -+ -+ async postOrganizationUpdateSubscription( -+ id: string, -+ request: OrganizationSubscriptionUpdateRequest -+ ): Promise { -+ return this.send("POST", "/organizations/" + id + "/subscription", request, true, false); -+ } -+ -+ async postOrganizationSeat(id: string, request: SeatRequest): Promise { -+ const r = await this.send("POST", "/organizations/" + id + "/seat", request, true, true); -+ return new PaymentResponse(r); -+ } -+ -+ async postOrganizationStorage(id: string, request: StorageRequest): Promise { -+ const r = await this.send("POST", "/organizations/" + id + "/storage", request, true, true); -+ return new PaymentResponse(r); -+ } -+ -+ postOrganizationPayment(id: string, request: PaymentRequest): Promise { -+ return this.send("POST", "/organizations/" + id + "/payment", request, true, false); -+ } -+ -+ postOrganizationVerifyBank(id: string, request: VerifyBankRequest): Promise { -+ return this.send("POST", "/organizations/" + id + "/verify-bank", request, true, false); -+ } -+ -+ postOrganizationCancel(id: string): Promise { -+ return this.send("POST", "/organizations/" + id + "/cancel", null, true, false); -+ } -+ -+ postOrganizationReinstate(id: string): Promise { -+ return this.send("POST", "/organizations/" + id + "/reinstate", null, true, false); -+ } -+ -+ deleteOrganization(id: string, request: SecretVerificationRequest): Promise { -+ return this.send("DELETE", "/organizations/" + id, request, true, false); -+ } -+ -+ async getOrganizationKeys(id: string): Promise { -+ const r = await this.send("GET", "/organizations/" + id + "/keys", null, true, true); -+ return new OrganizationKeysResponse(r); -+ } -+ -+ async postOrganizationKeys( -+ id: string, -+ request: OrganizationKeysRequest -+ ): Promise { -+ const r = await this.send("POST", "/organizations/" + id + "/keys", request, true, true); -+ return new OrganizationKeysResponse(r); -+ } -+ -+ // Provider APIs -+ -+ async postProviderSetup(id: string, request: ProviderSetupRequest) { -+ const r = await this.send("POST", "/providers/" + id + "/setup", request, true, true); -+ return new ProviderResponse(r); -+ } -+ -+ async getProvider(id: string) { -+ const r = await this.send("GET", "/providers/" + id, null, true, true); -+ return new ProviderResponse(r); -+ } -+ -+ async putProvider(id: string, request: ProviderUpdateRequest) { -+ const r = await this.send("PUT", "/providers/" + id, request, true, true); -+ return new ProviderResponse(r); -+ } -+ -+ // Provider User APIs -+ -+ async getProviderUsers( -+ providerId: string -+ ): Promise> { -+ const r = await this.send("GET", "/providers/" + providerId + "/users", null, true, true); -+ return new ListResponse(r, ProviderUserUserDetailsResponse); -+ } -+ -+ async getProviderUser(providerId: string, id: string): Promise { -+ const r = await this.send("GET", "/providers/" + providerId + "/users/" + id, null, true, true); -+ return new ProviderUserResponse(r); -+ } -+ -+ postProviderUserInvite(providerId: string, request: ProviderUserInviteRequest): Promise { -+ return this.send("POST", "/providers/" + providerId + "/users/invite", request, true, false); -+ } -+ -+ postProviderUserReinvite(providerId: string, id: string): Promise { -+ return this.send( -+ "POST", -+ "/providers/" + providerId + "/users/" + id + "/reinvite", -+ null, -+ true, -+ false -+ ); -+ } -+ -+ async postManyProviderUserReinvite( -+ providerId: string, -+ request: ProviderUserBulkRequest -+ ): Promise> { -+ const r = await this.send( -+ "POST", -+ "/providers/" + providerId + "/users/reinvite", -+ request, -+ true, -+ true -+ ); -+ return new ListResponse(r, ProviderUserBulkResponse); -+ } -+ -+ async postProviderUserBulkConfirm( -+ providerId: string, -+ request: ProviderUserBulkConfirmRequest -+ ): Promise> { -+ const r = await this.send( -+ "POST", -+ "/providers/" + providerId + "/users/confirm", -+ request, -+ true, -+ true -+ ); -+ return new ListResponse(r, ProviderUserBulkResponse); -+ } -+ -+ async deleteManyProviderUsers( -+ providerId: string, -+ request: ProviderUserBulkRequest -+ ): Promise> { -+ const r = await this.send("DELETE", "/providers/" + providerId + "/users", request, true, true); -+ return new ListResponse(r, ProviderUserBulkResponse); -+ } -+ -+ postProviderUserAccept( -+ providerId: string, -+ id: string, -+ request: ProviderUserAcceptRequest -+ ): Promise { -+ return this.send( -+ "POST", -+ "/providers/" + providerId + "/users/" + id + "/accept", -+ request, -+ true, -+ false -+ ); -+ } -+ -+ postProviderUserConfirm( -+ providerId: string, -+ id: string, -+ request: ProviderUserConfirmRequest -+ ): Promise { -+ return this.send( -+ "POST", -+ "/providers/" + providerId + "/users/" + id + "/confirm", -+ request, -+ true, -+ false -+ ); -+ } -+ -+ async postProviderUsersPublicKey( -+ providerId: string, -+ request: ProviderUserBulkRequest -+ ): Promise> { -+ const r = await this.send( -+ "POST", -+ "/providers/" + providerId + "/users/public-keys", -+ request, -+ true, -+ true -+ ); -+ return new ListResponse(r, ProviderUserBulkPublicKeyResponse); -+ } -+ -+ putProviderUser( -+ providerId: string, -+ id: string, -+ request: ProviderUserUpdateRequest -+ ): Promise { -+ return this.send("PUT", "/providers/" + providerId + "/users/" + id, request, true, false); -+ } -+ -+ deleteProviderUser(providerId: string, id: string): Promise { -+ return this.send("DELETE", "/providers/" + providerId + "/users/" + id, null, true, false); -+ } -+ -+ // Provider Organization APIs -+ -+ async getProviderClients( -+ providerId: string -+ ): Promise> { -+ const r = await this.send( -+ "GET", -+ "/providers/" + providerId + "/organizations", -+ null, -+ true, -+ true -+ ); -+ return new ListResponse(r, ProviderOrganizationOrganizationDetailsResponse); -+ } -+ -+ postProviderAddOrganization( -+ providerId: string, -+ request: ProviderAddOrganizationRequest -+ ): Promise { -+ return this.send( -+ "POST", -+ "/providers/" + providerId + "/organizations/add", -+ request, -+ true, -+ false -+ ); -+ } -+ -+ async postProviderCreateOrganization( -+ providerId: string, -+ request: ProviderOrganizationCreateRequest -+ ): Promise { -+ const r = await this.send( -+ "POST", -+ "/providers/" + providerId + "/organizations", -+ request, -+ true, -+ true -+ ); -+ return new ProviderOrganizationResponse(r); -+ } -+ -+ deleteProviderOrganization(providerId: string, id: string): Promise { -+ return this.send( -+ "DELETE", -+ "/providers/" + providerId + "/organizations/" + id, -+ null, -+ true, -+ false -+ ); -+ } -+ -+ // Event APIs -+ -+ async getEvents(start: string, end: string, token: string): Promise> { -+ const r = await this.send( -+ "GET", -+ this.addEventParameters("/events", start, end, token), -+ null, -+ true, -+ true -+ ); -+ return new ListResponse(r, EventResponse); -+ } -+ -+ async getEventsCipher( -+ id: string, -+ start: string, -+ end: string, -+ token: string -+ ): Promise> { -+ const r = await this.send( -+ "GET", -+ this.addEventParameters("/ciphers/" + id + "/events", start, end, token), -+ null, -+ true, -+ true -+ ); -+ return new ListResponse(r, EventResponse); -+ } -+ -+ async getEventsOrganization( -+ id: string, -+ start: string, -+ end: string, -+ token: string -+ ): Promise> { -+ const r = await this.send( -+ "GET", -+ this.addEventParameters("/organizations/" + id + "/events", start, end, token), -+ null, -+ true, -+ true -+ ); -+ return new ListResponse(r, EventResponse); -+ } -+ -+ async getEventsOrganizationUser( -+ organizationId: string, -+ id: string, -+ start: string, -+ end: string, -+ token: string -+ ): Promise> { -+ const r = await this.send( -+ "GET", -+ this.addEventParameters( -+ "/organizations/" + organizationId + "/users/" + id + "/events", -+ start, -+ end, -+ token -+ ), -+ null, -+ true, -+ true -+ ); -+ return new ListResponse(r, EventResponse); -+ } -+ -+ async getEventsProvider( -+ id: string, -+ start: string, -+ end: string, -+ token: string -+ ): Promise> { -+ const r = await this.send( -+ "GET", -+ this.addEventParameters("/providers/" + id + "/events", start, end, token), -+ null, -+ true, -+ true -+ ); -+ return new ListResponse(r, EventResponse); -+ } -+ -+ async getEventsProviderUser( -+ providerId: string, -+ id: string, -+ start: string, -+ end: string, -+ token: string -+ ): Promise> { -+ const r = await this.send( -+ "GET", -+ this.addEventParameters( -+ "/providers/" + providerId + "/users/" + id + "/events", -+ start, -+ end, -+ token -+ ), -+ null, -+ true, -+ true -+ ); -+ return new ListResponse(r, EventResponse); -+ } -+ -+ async postEventsCollect(request: EventRequest[]): Promise { -+ const authHeader = await this.getActiveBearerToken(); -+ const headers = new Headers({ -+ "Device-Type": this.deviceType, -+ Authorization: "Bearer " + authHeader, -+ "Content-Type": "application/json; charset=utf-8", -+ }); -+ if (this.customUserAgent != null) { -+ headers.set("User-Agent", this.customUserAgent); -+ } -+ const response = await this.fetch( -+ new Request(this.environmentService.getEventsUrl() + "/collect", { -+ cache: "no-store", -+ credentials: this.getCredentials(), -+ method: "POST", -+ body: JSON.stringify(request), -+ headers: headers, -+ }) -+ ); -+ if (response.status !== 200) { -+ return Promise.reject("Event post failed."); -+ } -+ } -+ -+ // User APIs -+ -+ async getUserPublicKey(id: string): Promise { -+ const r = await this.send("GET", "/users/" + id + "/public-key", null, true, true); -+ return new UserKeyResponse(r); -+ } -+ -+ // HIBP APIs -+ -+ async getHibpBreach(username: string): Promise { -+ const r = await this.send("GET", "/hibp/breach?username=" + username, null, true, true); -+ return r.map((a: any) => new BreachAccountResponse(a)); -+ } -+ -+ // Misc -+ -+ async postBitPayInvoice(request: BitPayInvoiceRequest): Promise { -+ const r = await this.send("POST", "/bitpay-invoice", request, true, true); -+ return r as string; -+ } -+ -+ async postSetupPayment(): Promise { -+ const r = await this.send("POST", "/setup-payment", null, true, true); -+ return r as string; -+ } -+ -+ // Key Connector -+ -+ async getUserKeyFromKeyConnector(keyConnectorUrl: string): Promise { -+ const authHeader = await this.getActiveBearerToken(); -+ -+ const response = await this.fetch( -+ new Request(keyConnectorUrl + "/user-keys", { -+ cache: "no-store", -+ method: "GET", -+ headers: new Headers({ -+ Accept: "application/json", -+ Authorization: "Bearer " + authHeader, -+ }), -+ }) -+ ); -+ -+ if (response.status !== 200) { -+ const error = await this.handleError(response, false, true); -+ return Promise.reject(error); -+ } -+ -+ return new KeyConnectorUserKeyResponse(await response.json()); -+ } -+ -+ async postUserKeyToKeyConnector( -+ keyConnectorUrl: string, -+ request: KeyConnectorUserKeyRequest -+ ): Promise { -+ const authHeader = await this.getActiveBearerToken(); -+ -+ const response = await this.fetch( -+ new Request(keyConnectorUrl + "/user-keys", { -+ cache: "no-store", -+ method: "POST", -+ headers: new Headers({ -+ Accept: "application/json", -+ Authorization: "Bearer " + authHeader, -+ "Content-Type": "application/json; charset=utf-8", -+ }), -+ body: JSON.stringify(request), -+ }) -+ ); -+ -+ if (response.status !== 200) { -+ const error = await this.handleError(response, false, true); -+ return Promise.reject(error); -+ } -+ } -+ -+ async getKeyConnectorAlive(keyConnectorUrl: string) { -+ const response = await this.fetch( -+ new Request(keyConnectorUrl + "/alive", { -+ cache: "no-store", -+ method: "GET", -+ headers: new Headers({ -+ Accept: "application/json", -+ "Content-Type": "application/json; charset=utf-8", -+ }), -+ }) -+ ); -+ -+ if (response.status !== 200) { -+ const error = await this.handleError(response, false, true); -+ return Promise.reject(error); -+ } -+ } -+ -+ // Helpers -+ -+ async getActiveBearerToken(): Promise { -+ let accessToken = await this.tokenService.getToken(); -+ if (await this.tokenService.tokenNeedsRefresh()) { -+ await this.doAuthRefresh(); -+ accessToken = await this.tokenService.getToken(); -+ } -+ return accessToken; -+ } -+ -+ fetch(request: Request): Promise { -+ if (request.method === "GET") { -+ request.headers.set("Cache-Control", "no-store"); -+ request.headers.set("Pragma", "no-cache"); -+ } -+ return this.nativeFetch(request); -+ } -+ -+ nativeFetch(request: Request): Promise { -+ return fetch(request); -+ } -+ -+ async preValidateSso(identifier: string): Promise { -+ if (identifier == null || identifier === "") { -+ throw new Error("Organization Identifier was not provided."); -+ } -+ const headers = new Headers({ -+ Accept: "application/json", -+ "Device-Type": this.deviceType, -+ }); -+ if (this.customUserAgent != null) { -+ headers.set("User-Agent", this.customUserAgent); -+ } -+ -+ const path = `/account/prevalidate?domainHint=${encodeURIComponent(identifier)}`; -+ const response = await this.fetch( -+ new Request(this.environmentService.getIdentityUrl() + path, { -+ cache: "no-store", -+ credentials: this.getCredentials(), -+ headers: headers, -+ method: "GET", -+ }) -+ ); -+ -+ if (response.status === 200) { -+ return true; -+ } else { -+ const error = await this.handleError(response, false, true); -+ return Promise.reject(error); -+ } -+ } -+ -+ async postCreateSponsorship( -+ sponsoredOrgId: string, -+ request: OrganizationSponsorshipCreateRequest -+ ): Promise { -+ return await this.send( -+ "POST", -+ "/organization/sponsorship/" + sponsoredOrgId + "/families-for-enterprise", -+ request, -+ true, -+ false -+ ); -+ } -+ -+ async deleteRevokeSponsorship(sponsoringOrganizationId: string): Promise { -+ return await this.send( -+ "DELETE", -+ "/organization/sponsorship/" + sponsoringOrganizationId, -+ null, -+ true, -+ false -+ ); -+ } -+ -+ async deleteRemoveSponsorship(sponsoringOrgId: string): Promise { -+ return await this.send( -+ "DELETE", -+ "/organization/sponsorship/sponsored/" + sponsoringOrgId, -+ null, -+ true, -+ false -+ ); -+ } -+ -+ async postPreValidateSponsorshipToken(sponsorshipToken: string): Promise { -+ const r = await this.send( -+ "POST", -+ "/organization/sponsorship/validate-token?sponsorshipToken=" + -+ encodeURIComponent(sponsorshipToken), -+ null, -+ true, -+ true -+ ); -+ return r as boolean; -+ } -+ -+ async postRedeemSponsorship( -+ sponsorshipToken: string, -+ request: OrganizationSponsorshipRedeemRequest -+ ): Promise { -+ return await this.send( -+ "POST", -+ "/organization/sponsorship/redeem?sponsorshipToken=" + encodeURIComponent(sponsorshipToken), -+ request, -+ true, -+ false -+ ); -+ } -+ -+ async postResendSponsorshipOffer(sponsoringOrgId: string): Promise { -+ return await this.send( -+ "POST", -+ "/organization/sponsorship/" + sponsoringOrgId + "/families-for-enterprise/resend", -+ null, -+ true, -+ false -+ ); -+ } -+ -+ protected async doAuthRefresh(): Promise { -+ const refreshToken = await this.tokenService.getRefreshToken(); -+ if (refreshToken != null && refreshToken !== "") { -+ return this.doRefreshToken(); -+ } -+ -+ const clientId = await this.tokenService.getClientId(); -+ const clientSecret = await this.tokenService.getClientSecret(); -+ if (!Utils.isNullOrWhitespace(clientId) && !Utils.isNullOrWhitespace(clientSecret)) { -+ return this.doApiTokenRefresh(); -+ } -+ -+ throw new Error("Cannot refresh token, no refresh token or api keys are stored"); -+ } -+ -+ protected async doApiTokenRefresh(): Promise { -+ const clientId = await this.tokenService.getClientId(); -+ const clientSecret = await this.tokenService.getClientSecret(); -+ if ( -+ Utils.isNullOrWhitespace(clientId) || -+ Utils.isNullOrWhitespace(clientSecret) || -+ this.apiKeyRefresh == null -+ ) { -+ throw new Error(); -+ } -+ -+ await this.apiKeyRefresh(clientId, clientSecret); -+ } -+ -+ protected async doRefreshToken(): Promise { -+ const refreshToken = await this.tokenService.getRefreshToken(); -+ if (refreshToken == null || refreshToken === "") { -+ throw new Error(); -+ } -+ const headers = new Headers({ -+ "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", -+ Accept: "application/json", -+ "Device-Type": this.deviceType, -+ }); -+ if (this.customUserAgent != null) { -+ headers.set("User-Agent", this.customUserAgent); -+ } -+ -+ const decodedToken = await this.tokenService.decodeToken(); -+ const response = await this.fetch( -+ new Request(this.environmentService.getIdentityUrl() + "/connect/token", { -+ body: this.qsStringify({ -+ grant_type: "refresh_token", -+ client_id: decodedToken.client_id, -+ refresh_token: refreshToken, -+ }), -+ cache: "no-store", -+ credentials: this.getCredentials(), -+ headers: headers, -+ method: "POST", -+ }) -+ ); -+ -+ if (response.status === 200) { -+ const responseJson = await response.json(); -+ const tokenResponse = new IdentityTokenResponse(responseJson); -+ await this.tokenService.setTokens( -+ tokenResponse.accessToken, -+ tokenResponse.refreshToken, -+ null -+ ); -+ } else { -+ const error = await this.handleError(response, true, true); -+ return Promise.reject(error); -+ } -+ } -+ -+ private async send( -+ method: "GET" | "POST" | "PUT" | "DELETE", -+ path: string, -+ body: any, -+ authed: boolean, -+ hasResponse: boolean, -+ apiUrl?: string, -+ alterHeaders?: (headers: Headers) => void -+ ): Promise { -+ apiUrl = Utils.isNullOrWhitespace(apiUrl) ? this.environmentService.getApiUrl() : apiUrl; -+ -+ const requestUrl = apiUrl + path; -+ // Prevent directory traversal from malicious paths -+ if (new URL(requestUrl).href !== requestUrl) { -+ return Promise.reject("Invalid request url path."); -+ } -+ -+ const headers = new Headers({ -+ "Device-Type": this.deviceType, -+ }); -+ if (this.customUserAgent != null) { -+ headers.set("User-Agent", this.customUserAgent); -+ } -+ -+ const requestInit: RequestInit = { -+ cache: "no-store", -+ credentials: this.getCredentials(), -+ method: method, -+ }; -+ -+ if (authed) { -+ const authHeader = await this.getActiveBearerToken(); -+ headers.set("Authorization", "Bearer " + authHeader); -+ } -+ if (body != null) { -+ if (typeof body === "string") { -+ requestInit.body = body; -+ headers.set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); -+ } else if (typeof body === "object") { -+ if (body instanceof FormData) { -+ requestInit.body = body; - } else { -- const error = await this.handleError(response, true, true); -- return Promise.reject(error); -- } -- } -- -- private async send(method: 'GET' | 'POST' | 'PUT' | 'DELETE', path: string, body: any, -- authed: boolean, hasResponse: boolean, apiUrl?: string, -- alterHeaders?: (headers: Headers) => void): Promise { -- apiUrl = Utils.isNullOrWhitespace(apiUrl) ? this.environmentService.getApiUrl() : apiUrl; -- -- const requestUrl = apiUrl + path; -- // Prevent directory traversal from malicious paths -- if (new URL(requestUrl).href !== requestUrl) { -- return Promise.reject('Invalid request url path.'); -+ headers.set("Content-Type", "application/json; charset=utf-8"); -+ requestInit.body = JSON.stringify(body); - } -- -- const headers = new Headers({ -- 'Device-Type': this.deviceType, -- }); -- if (this.customUserAgent != null) { -- headers.set('User-Agent', this.customUserAgent); -- } -- -- const requestInit: RequestInit = { -- cache: 'no-store', -- credentials: this.getCredentials(), -- method: method, -- }; -- -- if (authed) { -- const authHeader = await this.getActiveBearerToken(); -- headers.set('Authorization', 'Bearer ' + authHeader); -- } -- if (body != null) { -- if (typeof body === 'string') { -- requestInit.body = body; -- headers.set('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8'); -- } else if (typeof body === 'object') { -- if (body instanceof FormData) { -- requestInit.body = body; -- } else { -- headers.set('Content-Type', 'application/json; charset=utf-8'); -- requestInit.body = JSON.stringify(body); -- } -- } -- } -- if (hasResponse) { -- headers.set('Accept', 'application/json'); -- } -- if (alterHeaders != null) { -- alterHeaders(headers); -- } -- -- requestInit.headers = headers; -- const response = await this.fetch(new Request(requestUrl, requestInit)); -- -- if (hasResponse && response.status === 200) { -- const responseJson = await response.json(); -- return responseJson; -- } else if (response.status !== 200) { -- const error = await this.handleError(response, false, authed); -- return Promise.reject(error); -- } -- } -- -- private async handleError(response: Response, tokenError: boolean, authed: boolean): Promise { -- if (authed && ((tokenError && response.status === 400) || response.status === 401 || response.status === 403)) { -- await this.logoutCallback(true); -- return null; -- } -- -- let responseJson: any = null; -- if (this.isJsonResponse(response)) { -- responseJson = await response.json(); -- } else if (this.isTextResponse(response)) { -- responseJson = { Message: await response.text() }; -- } -- -- return new ErrorResponse(responseJson, response.status, tokenError); -- } -- -- private qsStringify(params: any): string { -- return Object.keys(params).map(key => { -- return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]); -- }).join('&'); -- } -- -- private getCredentials(): RequestCredentials { -- if (!this.isWebClient || this.environmentService.hasBaseUrl()) { -- return 'include'; -- } -- return undefined; -- } -- -- private addEventParameters(base: string, start: string, end: string, token: string) { -- if (start != null) { -- base += ('?start=' + start); -- } -- if (end != null) { -- base += (base.indexOf('?') > -1 ? '&' : '?'); -- base += ('end=' + end); -- } -- if (token != null) { -- base += (base.indexOf('?') > -1 ? '&' : '?'); -- base += ('continuationToken=' + token); -- } -- return base; -- } -- -- private isJsonResponse(response: Response): boolean { -- const typeHeader = response.headers.get('content-type'); -- return typeHeader != null && typeHeader.indexOf('application/json') > -1; -- } -- -- private isTextResponse(response: Response): boolean { -- const typeHeader = response.headers.get('content-type'); -- return typeHeader != null && typeHeader.indexOf('text') > -1; -- } -+ } -+ } -+ if (hasResponse) { -+ headers.set("Accept", "application/json"); -+ } -+ if (alterHeaders != null) { -+ alterHeaders(headers); -+ } -+ -+ requestInit.headers = headers; -+ const response = await this.fetch(new Request(requestUrl, requestInit)); -+ -+ if (hasResponse && response.status === 200) { -+ const responseJson = await response.json(); -+ return responseJson; -+ } else if (response.status !== 200) { -+ const error = await this.handleError(response, false, authed); -+ return Promise.reject(error); -+ } -+ } -+ -+ private async handleError( -+ response: Response, -+ tokenError: boolean, -+ authed: boolean -+ ): Promise { -+ if ( -+ authed && -+ ((tokenError && response.status === 400) || -+ response.status === 401 || -+ response.status === 403) -+ ) { -+ await this.logoutCallback(true); -+ return null; -+ } -+ -+ let responseJson: any = null; -+ if (this.isJsonResponse(response)) { -+ responseJson = await response.json(); -+ } else if (this.isTextResponse(response)) { -+ responseJson = { Message: await response.text() }; -+ } -+ -+ return new ErrorResponse(responseJson, response.status, tokenError); -+ } -+ -+ private qsStringify(params: any): string { -+ return Object.keys(params) -+ .map((key) => { -+ return encodeURIComponent(key) + "=" + encodeURIComponent(params[key]); -+ }) -+ .join("&"); -+ } -+ -+ private getCredentials(): RequestCredentials { -+ if (!this.isWebClient || this.environmentService.hasBaseUrl()) { -+ return "include"; -+ } -+ return undefined; -+ } -+ -+ private addEventParameters(base: string, start: string, end: string, token: string) { -+ if (start != null) { -+ base += "?start=" + start; -+ } -+ if (end != null) { -+ base += base.indexOf("?") > -1 ? "&" : "?"; -+ base += "end=" + end; -+ } -+ if (token != null) { -+ base += base.indexOf("?") > -1 ? "&" : "?"; -+ base += "continuationToken=" + token; -+ } -+ return base; -+ } -+ -+ private isJsonResponse(response: Response): boolean { -+ const typeHeader = response.headers.get("content-type"); -+ return typeHeader != null && typeHeader.indexOf("application/json") > -1; -+ } -+ -+ private isTextResponse(response: Response): boolean { -+ const typeHeader = response.headers.get("content-type"); -+ return typeHeader != null && typeHeader.indexOf("text") > -1; -+ } - } -diff --git a/jslib/common/src/services/apiKey.service.ts b/jslib/common/src/services/apiKey.service.ts -deleted file mode 100644 -index 0b67781b..00000000 ---- a/jslib/common/src/services/apiKey.service.ts -+++ /dev/null -@@ -1,86 +0,0 @@ --import { ApiKeyService as ApiKeyServiceAbstraction } from '../abstractions/apiKey.service'; --import { StorageService } from '../abstractions/storage.service'; --import { TokenService } from '../abstractions/token.service'; -- --import { Utils } from '../misc/utils'; -- --const Keys = { -- clientId: 'clientId', -- clientSecret: 'clientSecret', -- entityType: 'entityType', -- entityId: 'entityId', --}; -- -- --export class ApiKeyService implements ApiKeyServiceAbstraction { -- private clientId: string; -- private clientSecret: string; -- private entityType: string; -- private entityId: string; -- -- constructor(private tokenService: TokenService, private storageService: StorageService) { } -- -- async setInformation(clientId: string, clientSecret: string) { -- this.clientId = clientId; -- this.clientSecret = clientSecret; -- const idParts = clientId.split('.'); -- -- if (idParts.length !== 2 || !Utils.isGuid(idParts[1])) { -- throw Error('Invalid clientId'); -- } -- this.entityType = idParts[0]; -- this.entityId = idParts[1]; -- -- await this.storageService.save(Keys.clientId, this.clientId); -- await this.storageService.save(Keys.entityId, this.entityId); -- await this.storageService.save(Keys.entityType, this.entityType); -- await this.storageService.save(Keys.clientSecret, this.clientSecret); -- } -- -- async getClientId(): Promise { -- if (this.clientId == null) { -- this.clientId = await this.storageService.get(Keys.clientId); -- } -- return this.clientId; -- } -- -- async getClientSecret(): Promise { -- if (this.clientSecret == null) { -- this.clientSecret = await this.storageService.get(Keys.clientSecret); -- } -- return this.clientSecret; -- } -- -- async getEntityType(): Promise { -- if (this.entityType == null) { -- this.entityType = await this.storageService.get(Keys.entityType); -- } -- return this.entityType; -- } -- -- async getEntityId(): Promise { -- if (this.entityId == null) { -- this.entityId = await this.storageService.get(Keys.entityId); -- } -- return this.entityId; -- } -- -- async clear(): Promise { -- await this.storageService.remove(Keys.clientId); -- await this.storageService.remove(Keys.clientSecret); -- await this.storageService.remove(Keys.entityId); -- await this.storageService.remove(Keys.entityType); -- -- this.clientId = this.clientSecret = this.entityId = this.entityType = null; -- } -- -- async isAuthenticated(): Promise { -- const token = await this.tokenService.getToken(); -- if (token == null) { -- return false; -- } -- -- const entityId = await this.getEntityId(); -- return entityId != null; -- } --} -diff --git a/jslib/common/src/services/appId.service.ts b/jslib/common/src/services/appId.service.ts -index c37668d1..8ffd20f9 100644 ---- a/jslib/common/src/services/appId.service.ts -+++ b/jslib/common/src/services/appId.service.ts -@@ -1,28 +1,27 @@ --import { Utils } from '../misc/utils'; -+import { Utils } from "../misc/utils"; - --import { AppIdService as AppIdServiceAbstraction } from '../abstractions/appId.service'; --import { StorageService } from '../abstractions/storage.service'; -+import { AppIdService as AppIdServiceAbstraction } from "../abstractions/appId.service"; -+import { StorageService } from "../abstractions/storage.service"; - - export class AppIdService implements AppIdServiceAbstraction { -- constructor(private storageService: StorageService) { -- } -+ constructor(private storageService: StorageService) {} - -- getAppId(): Promise { -- return this.makeAndGetAppId('appId'); -- } -+ getAppId(): Promise { -+ return this.makeAndGetAppId("appId"); -+ } - -- getAnonymousAppId(): Promise { -- return this.makeAndGetAppId('anonymousAppId'); -- } -+ getAnonymousAppId(): Promise { -+ return this.makeAndGetAppId("anonymousAppId"); -+ } - -- private async makeAndGetAppId(key: string) { -- const existingId = await this.storageService.get(key); -- if (existingId != null) { -- return existingId; -- } -- -- const guid = Utils.newGuid(); -- await this.storageService.save(key, guid); -- return guid; -+ private async makeAndGetAppId(key: string) { -+ const existingId = await this.storageService.get(key); -+ if (existingId != null) { -+ return existingId; - } -+ -+ const guid = Utils.newGuid(); -+ await this.storageService.save(key, guid); -+ return guid; -+ } - } -diff --git a/jslib/common/src/services/audit.service.ts b/jslib/common/src/services/audit.service.ts -index db9a0457..2136b442 100644 ---- a/jslib/common/src/services/audit.service.ts -+++ b/jslib/common/src/services/audit.service.ts -@@ -1,43 +1,46 @@ --import { ApiService } from '../abstractions/api.service'; --import { AuditService as AuditServiceAbstraction } from '../abstractions/audit.service'; --import { CryptoFunctionService } from '../abstractions/cryptoFunction.service'; -+import { ApiService } from "../abstractions/api.service"; -+import { AuditService as AuditServiceAbstraction } from "../abstractions/audit.service"; -+import { CryptoFunctionService } from "../abstractions/cryptoFunction.service"; - --import { throttle } from '../misc/throttle'; --import { Utils } from '../misc/utils'; -+import { throttle } from "../misc/throttle"; -+import { Utils } from "../misc/utils"; - --import { BreachAccountResponse } from '../models/response/breachAccountResponse'; --import { ErrorResponse } from '../models/response/errorResponse'; -+import { BreachAccountResponse } from "../models/response/breachAccountResponse"; -+import { ErrorResponse } from "../models/response/errorResponse"; - --const PwnedPasswordsApi = 'https://api.pwnedpasswords.com/range/'; -+const PwnedPasswordsApi = "https://api.pwnedpasswords.com/range/"; - - export class AuditService implements AuditServiceAbstraction { -- constructor(private cryptoFunctionService: CryptoFunctionService, private apiService: ApiService) { } -- -- @throttle(100, () => 'passwordLeaked') -- async passwordLeaked(password: string): Promise { -- const hashBytes = await this.cryptoFunctionService.hash(password, 'sha1'); -- const hash = Utils.fromBufferToHex(hashBytes).toUpperCase(); -- const hashStart = hash.substr(0, 5); -- const hashEnding = hash.substr(5); -- -- const response = await this.apiService.nativeFetch(new Request(PwnedPasswordsApi + hashStart)); -- const leakedHashes = await response.text(); -- const match = leakedHashes.split(/\r?\n/).find(v => { -- return v.split(':')[0] === hashEnding; -- }); -- -- return match != null ? parseInt(match.split(':')[1], 10) : 0; -- } -- -- async breachedAccounts(username: string): Promise { -- try { -- return await this.apiService.getHibpBreach(username); -- } catch (e) { -- const error = e as ErrorResponse; -- if (error.statusCode === 404) { -- return []; -- } -- throw new Error(); -- } -+ constructor( -+ private cryptoFunctionService: CryptoFunctionService, -+ private apiService: ApiService -+ ) {} -+ -+ @throttle(100, () => "passwordLeaked") -+ async passwordLeaked(password: string): Promise { -+ const hashBytes = await this.cryptoFunctionService.hash(password, "sha1"); -+ const hash = Utils.fromBufferToHex(hashBytes).toUpperCase(); -+ const hashStart = hash.substr(0, 5); -+ const hashEnding = hash.substr(5); -+ -+ const response = await this.apiService.nativeFetch(new Request(PwnedPasswordsApi + hashStart)); -+ const leakedHashes = await response.text(); -+ const match = leakedHashes.split(/\r?\n/).find((v) => { -+ return v.split(":")[0] === hashEnding; -+ }); -+ -+ return match != null ? parseInt(match.split(":")[1], 10) : 0; -+ } -+ -+ async breachedAccounts(username: string): Promise { -+ try { -+ return await this.apiService.getHibpBreach(username); -+ } catch (e) { -+ const error = e as ErrorResponse; -+ if (error.statusCode === 404) { -+ return []; -+ } -+ throw new Error(); - } -+ } - } -diff --git a/jslib/common/src/services/auth.service.ts b/jslib/common/src/services/auth.service.ts -index e4f670d7..f6559730 100644 ---- a/jslib/common/src/services/auth.service.ts -+++ b/jslib/common/src/services/auth.service.ts -@@ -1,438 +1,674 @@ --import { HashPurpose } from '../enums/hashPurpose'; --import { KdfType } from '../enums/kdfType'; --import { TwoFactorProviderType } from '../enums/twoFactorProviderType'; -- --import { AuthResult } from '../models/domain/authResult'; --import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; -- --import { SetKeyConnectorKeyRequest } from '../models/request/account/setKeyConnectorKeyRequest'; --import { DeviceRequest } from '../models/request/deviceRequest'; --import { KeyConnectorUserKeyRequest } from '../models/request/keyConnectorUserKeyRequest'; --import { KeysRequest } from '../models/request/keysRequest'; --import { PreloginRequest } from '../models/request/preloginRequest'; --import { TokenRequest } from '../models/request/tokenRequest'; -- --import { IdentityTokenResponse } from '../models/response/identityTokenResponse'; --import { IdentityTwoFactorResponse } from '../models/response/identityTwoFactorResponse'; -- --import { ApiService } from '../abstractions/api.service'; --import { AppIdService } from '../abstractions/appId.service'; --import { AuthService as AuthServiceAbstraction } from '../abstractions/auth.service'; --import { CryptoService } from '../abstractions/crypto.service'; --import { CryptoFunctionService } from '../abstractions/cryptoFunction.service'; --import { EnvironmentService } from '../abstractions/environment.service'; --import { I18nService } from '../abstractions/i18n.service'; --import { KeyConnectorService } from '../abstractions/keyConnector.service'; --import { LogService } from '../abstractions/log.service'; --import { MessagingService } from '../abstractions/messaging.service'; --import { PlatformUtilsService } from '../abstractions/platformUtils.service'; --import { TokenService } from '../abstractions/token.service'; --import { UserService } from '../abstractions/user.service'; --import { VaultTimeoutService } from '../abstractions/vaultTimeout.service'; -- --import { Utils } from '../misc/utils'; -+import { HashPurpose } from "../enums/hashPurpose"; -+import { KdfType } from "../enums/kdfType"; -+import { TwoFactorProviderType } from "../enums/twoFactorProviderType"; -+ -+import { -+ Account, -+ AccountData, -+ AccountKeys, -+ AccountProfile, -+ AccountTokens, -+} from "../models/domain/account"; -+import { AuthResult } from "../models/domain/authResult"; -+import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; -+ -+import { SetKeyConnectorKeyRequest } from "../models/request/account/setKeyConnectorKeyRequest"; -+import { DeviceRequest } from "../models/request/deviceRequest"; -+import { KeyConnectorUserKeyRequest } from "../models/request/keyConnectorUserKeyRequest"; -+import { KeysRequest } from "../models/request/keysRequest"; -+import { PreloginRequest } from "../models/request/preloginRequest"; -+import { TokenRequest } from "../models/request/tokenRequest"; -+ -+import { IdentityTokenResponse } from "../models/response/identityTokenResponse"; -+import { IdentityTwoFactorResponse } from "../models/response/identityTwoFactorResponse"; -+ -+import { ApiService } from "../abstractions/api.service"; -+import { AppIdService } from "../abstractions/appId.service"; -+import { AuthService as AuthServiceAbstraction } from "../abstractions/auth.service"; -+import { CryptoService } from "../abstractions/crypto.service"; -+import { CryptoFunctionService } from "../abstractions/cryptoFunction.service"; -+import { EnvironmentService } from "../abstractions/environment.service"; -+import { I18nService } from "../abstractions/i18n.service"; -+import { KeyConnectorService } from "../abstractions/keyConnector.service"; -+import { LogService } from "../abstractions/log.service"; -+import { MessagingService } from "../abstractions/messaging.service"; -+import { PlatformUtilsService } from "../abstractions/platformUtils.service"; -+import { StateService } from "../abstractions/state.service"; -+import { TokenService } from "../abstractions/token.service"; -+import { VaultTimeoutService } from "../abstractions/vaultTimeout.service"; -+ -+import { Utils } from "../misc/utils"; - - export const TwoFactorProviders = { -- [TwoFactorProviderType.Authenticator]: { -- type: TwoFactorProviderType.Authenticator, -- name: null as string, -- description: null as string, -- priority: 1, -- sort: 1, -- premium: false, -- }, -- [TwoFactorProviderType.Yubikey]: { -- type: TwoFactorProviderType.Yubikey, -- name: null as string, -- description: null as string, -- priority: 3, -- sort: 2, -- premium: true, -- }, -- [TwoFactorProviderType.Duo]: { -- type: TwoFactorProviderType.Duo, -- name: 'Duo', -- description: null as string, -- priority: 2, -- sort: 3, -- premium: true, -- }, -- [TwoFactorProviderType.OrganizationDuo]: { -- type: TwoFactorProviderType.OrganizationDuo, -- name: 'Duo (Organization)', -- description: null as string, -- priority: 10, -- sort: 4, -- premium: false, -- }, -- [TwoFactorProviderType.Email]: { -- type: TwoFactorProviderType.Email, -- name: null as string, -- description: null as string, -- priority: 0, -- sort: 6, -- premium: false, -- }, -- [TwoFactorProviderType.WebAuthn]: { -- type: TwoFactorProviderType.WebAuthn, -- name: null as string, -- description: null as string, -- priority: 4, -- sort: 5, -- premium: true, -- }, -+ [TwoFactorProviderType.Authenticator]: { -+ type: TwoFactorProviderType.Authenticator, -+ name: null as string, -+ description: null as string, -+ priority: 1, -+ sort: 1, -+ premium: false, -+ }, -+ [TwoFactorProviderType.Yubikey]: { -+ type: TwoFactorProviderType.Yubikey, -+ name: null as string, -+ description: null as string, -+ priority: 3, -+ sort: 2, -+ premium: true, -+ }, -+ [TwoFactorProviderType.Duo]: { -+ type: TwoFactorProviderType.Duo, -+ name: "Duo", -+ description: null as string, -+ priority: 2, -+ sort: 3, -+ premium: true, -+ }, -+ [TwoFactorProviderType.OrganizationDuo]: { -+ type: TwoFactorProviderType.OrganizationDuo, -+ name: "Duo (Organization)", -+ description: null as string, -+ priority: 10, -+ sort: 4, -+ premium: false, -+ }, -+ [TwoFactorProviderType.Email]: { -+ type: TwoFactorProviderType.Email, -+ name: null as string, -+ description: null as string, -+ priority: 0, -+ sort: 6, -+ premium: false, -+ }, -+ [TwoFactorProviderType.WebAuthn]: { -+ type: TwoFactorProviderType.WebAuthn, -+ name: null as string, -+ description: null as string, -+ priority: 4, -+ sort: 5, -+ premium: true, -+ }, - }; - - export class AuthService implements AuthServiceAbstraction { -- email: string; -- masterPasswordHash: string; -- localMasterPasswordHash: string; -- code: string; -- codeVerifier: string; -- ssoRedirectUrl: string; -- clientId: string; -- clientSecret: string; -- twoFactorProvidersData: Map; -- selectedTwoFactorProviderType: TwoFactorProviderType = null; -- captchaToken: string; -- -- private key: SymmetricCryptoKey; -- -- constructor(private cryptoService: CryptoService, protected apiService: ApiService, -- private userService: UserService, protected tokenService: TokenService, -- protected appIdService: AppIdService, private i18nService: I18nService, -- protected platformUtilsService: PlatformUtilsService, private messagingService: MessagingService, -- private vaultTimeoutService: VaultTimeoutService, private logService: LogService, -- private cryptoFunctionService: CryptoFunctionService, private environmentService: EnvironmentService, -- private keyConnectorService: KeyConnectorService, private setCryptoKeys = true) { -+ email: string; -+ masterPasswordHash: string; -+ localMasterPasswordHash: string; -+ code: string; -+ codeVerifier: string; -+ ssoRedirectUrl: string; -+ clientId: string; -+ clientSecret: string; -+ twoFactorProvidersData: Map; -+ selectedTwoFactorProviderType: TwoFactorProviderType = null; -+ captchaToken: string; -+ -+ private key: SymmetricCryptoKey; -+ -+ constructor( -+ private cryptoService: CryptoService, -+ protected apiService: ApiService, -+ protected tokenService: TokenService, -+ protected appIdService: AppIdService, -+ private i18nService: I18nService, -+ protected platformUtilsService: PlatformUtilsService, -+ private messagingService: MessagingService, -+ private vaultTimeoutService: VaultTimeoutService, -+ private logService: LogService, -+ protected cryptoFunctionService: CryptoFunctionService, -+ private keyConnectorService: KeyConnectorService, -+ protected environmentService: EnvironmentService, -+ protected stateService: StateService, -+ private setCryptoKeys = true -+ ) {} -+ -+ init() { -+ TwoFactorProviders[TwoFactorProviderType.Email].name = this.i18nService.t("emailTitle"); -+ TwoFactorProviders[TwoFactorProviderType.Email].description = this.i18nService.t("emailDesc"); -+ -+ TwoFactorProviders[TwoFactorProviderType.Authenticator].name = -+ this.i18nService.t("authenticatorAppTitle"); -+ TwoFactorProviders[TwoFactorProviderType.Authenticator].description = -+ this.i18nService.t("authenticatorAppDesc"); -+ -+ TwoFactorProviders[TwoFactorProviderType.Duo].description = this.i18nService.t("duoDesc"); -+ -+ TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].name = -+ "Duo (" + this.i18nService.t("organization") + ")"; -+ TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].description = -+ this.i18nService.t("duoOrganizationDesc"); -+ -+ TwoFactorProviders[TwoFactorProviderType.WebAuthn].name = this.i18nService.t("webAuthnTitle"); -+ TwoFactorProviders[TwoFactorProviderType.WebAuthn].description = -+ this.i18nService.t("webAuthnDesc"); -+ -+ TwoFactorProviders[TwoFactorProviderType.Yubikey].name = this.i18nService.t("yubiKeyTitle"); -+ TwoFactorProviders[TwoFactorProviderType.Yubikey].description = -+ this.i18nService.t("yubiKeyDesc"); -+ } -+ -+ async logIn(email: string, masterPassword: string, captchaToken?: string): Promise { -+ this.selectedTwoFactorProviderType = null; -+ const key = await this.makePreloginKey(masterPassword, email); -+ const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key); -+ const localHashedPassword = await this.cryptoService.hashPassword( -+ masterPassword, -+ key, -+ HashPurpose.LocalAuthorization -+ ); -+ return await this.logInHelper( -+ email, -+ hashedPassword, -+ localHashedPassword, -+ null, -+ null, -+ null, -+ null, -+ null, -+ key, -+ null, -+ null, -+ null, -+ captchaToken, -+ null -+ ); -+ } -+ -+ async logInSso( -+ code: string, -+ codeVerifier: string, -+ redirectUrl: string, -+ orgId: string -+ ): Promise { -+ this.selectedTwoFactorProviderType = null; -+ return await this.logInHelper( -+ null, -+ null, -+ null, -+ code, -+ codeVerifier, -+ redirectUrl, -+ null, -+ null, -+ null, -+ null, -+ null, -+ null, -+ null, -+ orgId -+ ); -+ } -+ -+ async logInApiKey(clientId: string, clientSecret: string): Promise { -+ this.selectedTwoFactorProviderType = null; -+ return await this.logInHelper( -+ null, -+ null, -+ null, -+ null, -+ null, -+ null, -+ clientId, -+ clientSecret, -+ null, -+ null, -+ null, -+ null, -+ null, -+ null -+ ); -+ } -+ -+ async logInTwoFactor( -+ twoFactorProvider: TwoFactorProviderType, -+ twoFactorToken: string, -+ remember?: boolean -+ ): Promise { -+ return await this.logInHelper( -+ this.email, -+ this.masterPasswordHash, -+ this.localMasterPasswordHash, -+ this.code, -+ this.codeVerifier, -+ this.ssoRedirectUrl, -+ this.clientId, -+ this.clientSecret, -+ this.key, -+ twoFactorProvider, -+ twoFactorToken, -+ remember, -+ this.captchaToken, -+ null -+ ); -+ } -+ -+ async logInComplete( -+ email: string, -+ masterPassword: string, -+ twoFactorProvider: TwoFactorProviderType, -+ twoFactorToken: string, -+ remember?: boolean, -+ captchaToken?: string -+ ): Promise { -+ this.selectedTwoFactorProviderType = null; -+ const key = await this.makePreloginKey(masterPassword, email); -+ const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key); -+ const localHashedPassword = await this.cryptoService.hashPassword( -+ masterPassword, -+ key, -+ HashPurpose.LocalAuthorization -+ ); -+ return await this.logInHelper( -+ email, -+ hashedPassword, -+ localHashedPassword, -+ null, -+ null, -+ null, -+ null, -+ null, -+ key, -+ twoFactorProvider, -+ twoFactorToken, -+ remember, -+ captchaToken, -+ null -+ ); -+ } -+ -+ async logInSsoComplete( -+ code: string, -+ codeVerifier: string, -+ redirectUrl: string, -+ twoFactorProvider: TwoFactorProviderType, -+ twoFactorToken: string, -+ remember?: boolean -+ ): Promise { -+ this.selectedTwoFactorProviderType = null; -+ return await this.logInHelper( -+ null, -+ null, -+ null, -+ code, -+ codeVerifier, -+ redirectUrl, -+ null, -+ null, -+ null, -+ twoFactorProvider, -+ twoFactorToken, -+ remember, -+ null, -+ null -+ ); -+ } -+ -+ async logInApiKeyComplete( -+ clientId: string, -+ clientSecret: string, -+ twoFactorProvider: TwoFactorProviderType, -+ twoFactorToken: string, -+ remember?: boolean -+ ): Promise { -+ this.selectedTwoFactorProviderType = null; -+ return await this.logInHelper( -+ null, -+ null, -+ null, -+ null, -+ null, -+ null, -+ clientId, -+ clientSecret, -+ null, -+ twoFactorProvider, -+ twoFactorToken, -+ remember, -+ null, -+ null -+ ); -+ } -+ -+ logOut(callback: Function) { -+ callback(); -+ this.messagingService.send("loggedOut"); -+ } -+ -+ getSupportedTwoFactorProviders(win: Window): any[] { -+ const providers: any[] = []; -+ if (this.twoFactorProvidersData == null) { -+ return providers; - } - -- init() { -- TwoFactorProviders[TwoFactorProviderType.Email].name = this.i18nService.t('emailTitle'); -- TwoFactorProviders[TwoFactorProviderType.Email].description = this.i18nService.t('emailDesc'); -- -- TwoFactorProviders[TwoFactorProviderType.Authenticator].name = this.i18nService.t('authenticatorAppTitle'); -- TwoFactorProviders[TwoFactorProviderType.Authenticator].description = -- this.i18nService.t('authenticatorAppDesc'); -- -- TwoFactorProviders[TwoFactorProviderType.Duo].description = this.i18nService.t('duoDesc'); -- -- TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].name = -- 'Duo (' + this.i18nService.t('organization') + ')'; -- TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].description = -- this.i18nService.t('duoOrganizationDesc'); -- -- TwoFactorProviders[TwoFactorProviderType.WebAuthn].name = this.i18nService.t('webAuthnTitle'); -- TwoFactorProviders[TwoFactorProviderType.WebAuthn].description = this.i18nService.t('webAuthnDesc'); -- -- TwoFactorProviders[TwoFactorProviderType.Yubikey].name = this.i18nService.t('yubiKeyTitle'); -- TwoFactorProviders[TwoFactorProviderType.Yubikey].description = this.i18nService.t('yubiKeyDesc'); -+ if ( -+ this.twoFactorProvidersData.has(TwoFactorProviderType.OrganizationDuo) && -+ this.platformUtilsService.supportsDuo() -+ ) { -+ providers.push(TwoFactorProviders[TwoFactorProviderType.OrganizationDuo]); - } - -- async logIn(email: string, masterPassword: string, captchaToken?: string): Promise { -- this.selectedTwoFactorProviderType = null; -- const key = await this.makePreloginKey(masterPassword, email); -- const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key); -- const localHashedPassword = await this.cryptoService.hashPassword(masterPassword, key, -- HashPurpose.LocalAuthorization); -- return await this.logInHelper(email, hashedPassword, localHashedPassword, null, null, null, null, null, -- key, null, null, null, captchaToken, null); -+ if (this.twoFactorProvidersData.has(TwoFactorProviderType.Authenticator)) { -+ providers.push(TwoFactorProviders[TwoFactorProviderType.Authenticator]); - } - -- async logInSso(code: string, codeVerifier: string, redirectUrl: string, orgId: string): Promise { -- this.selectedTwoFactorProviderType = null; -- return await this.logInHelper(null, null, null, code, codeVerifier, redirectUrl, null, null, -- null, null, null, null, null, orgId); -+ if (this.twoFactorProvidersData.has(TwoFactorProviderType.Yubikey)) { -+ providers.push(TwoFactorProviders[TwoFactorProviderType.Yubikey]); - } - -- async logInApiKey(clientId: string, clientSecret: string): Promise { -- this.selectedTwoFactorProviderType = null; -- return await this.logInHelper(null, null, null, null, null, null, clientId, clientSecret, -- null, null, null, null, null, null); -+ if ( -+ this.twoFactorProvidersData.has(TwoFactorProviderType.Duo) && -+ this.platformUtilsService.supportsDuo() -+ ) { -+ providers.push(TwoFactorProviders[TwoFactorProviderType.Duo]); - } - -- async logInTwoFactor(twoFactorProvider: TwoFactorProviderType, twoFactorToken: string, -- remember?: boolean): Promise { -- return await this.logInHelper(this.email, this.masterPasswordHash, this.localMasterPasswordHash, this.code, -- this.codeVerifier, this.ssoRedirectUrl, this.clientId, this.clientSecret, this.key, twoFactorProvider, -- twoFactorToken, remember, this.captchaToken, null); -+ if ( -+ this.twoFactorProvidersData.has(TwoFactorProviderType.WebAuthn) && -+ this.platformUtilsService.supportsWebAuthn(win) -+ ) { -+ providers.push(TwoFactorProviders[TwoFactorProviderType.WebAuthn]); - } - -- async logInComplete(email: string, masterPassword: string, twoFactorProvider: TwoFactorProviderType, -- twoFactorToken: string, remember?: boolean, captchaToken?: string): Promise { -- this.selectedTwoFactorProviderType = null; -- const key = await this.makePreloginKey(masterPassword, email); -- const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key); -- const localHashedPassword = await this.cryptoService.hashPassword(masterPassword, key, -- HashPurpose.LocalAuthorization); -- return await this.logInHelper(email, hashedPassword, localHashedPassword, null, null, null, null, null, key, -- twoFactorProvider, twoFactorToken, remember, captchaToken, null); -+ if (this.twoFactorProvidersData.has(TwoFactorProviderType.Email)) { -+ providers.push(TwoFactorProviders[TwoFactorProviderType.Email]); - } - -- async logInSsoComplete(code: string, codeVerifier: string, redirectUrl: string, -- twoFactorProvider: TwoFactorProviderType, twoFactorToken: string, remember?: boolean): Promise { -- this.selectedTwoFactorProviderType = null; -- return await this.logInHelper(null, null, null, code, codeVerifier, redirectUrl, null, -- null, null, twoFactorProvider, twoFactorToken, remember, null, null); -- } -+ return providers; -+ } - -- async logInApiKeyComplete(clientId: string, clientSecret: string, twoFactorProvider: TwoFactorProviderType, -- twoFactorToken: string, remember?: boolean): Promise { -- this.selectedTwoFactorProviderType = null; -- return await this.logInHelper(null, null, null, null, null, null, clientId, clientSecret, null, -- twoFactorProvider, twoFactorToken, remember, null, null); -+ getDefaultTwoFactorProvider(webAuthnSupported: boolean): TwoFactorProviderType { -+ if (this.twoFactorProvidersData == null) { -+ return null; - } - -- logOut(callback: Function) { -- callback(); -- this.messagingService.send('loggedOut'); -+ if ( -+ this.selectedTwoFactorProviderType != null && -+ this.twoFactorProvidersData.has(this.selectedTwoFactorProviderType) -+ ) { -+ return this.selectedTwoFactorProviderType; - } - -- getSupportedTwoFactorProviders(win: Window): any[] { -- const providers: any[] = []; -- if (this.twoFactorProvidersData == null) { -- return providers; -- } -- -- if (this.twoFactorProvidersData.has(TwoFactorProviderType.OrganizationDuo) && -- this.platformUtilsService.supportsDuo()) { -- providers.push(TwoFactorProviders[TwoFactorProviderType.OrganizationDuo]); -- } -- -- if (this.twoFactorProvidersData.has(TwoFactorProviderType.Authenticator)) { -- providers.push(TwoFactorProviders[TwoFactorProviderType.Authenticator]); -+ let providerType: TwoFactorProviderType = null; -+ let providerPriority = -1; -+ this.twoFactorProvidersData.forEach((_value, type) => { -+ const provider = (TwoFactorProviders as any)[type]; -+ if (provider != null && provider.priority > providerPriority) { -+ if (type === TwoFactorProviderType.WebAuthn && !webAuthnSupported) { -+ return; - } - -- if (this.twoFactorProvidersData.has(TwoFactorProviderType.Yubikey)) { -- providers.push(TwoFactorProviders[TwoFactorProviderType.Yubikey]); -- } -- -- if (this.twoFactorProvidersData.has(TwoFactorProviderType.Duo) && this.platformUtilsService.supportsDuo()) { -- providers.push(TwoFactorProviders[TwoFactorProviderType.Duo]); -- } -- -- if (this.twoFactorProvidersData.has(TwoFactorProviderType.WebAuthn) && this.platformUtilsService.supportsWebAuthn(win)) { -- providers.push(TwoFactorProviders[TwoFactorProviderType.WebAuthn]); -- } -- -- if (this.twoFactorProvidersData.has(TwoFactorProviderType.Email)) { -- providers.push(TwoFactorProviders[TwoFactorProviderType.Email]); -- } -- -- return providers; -+ providerType = type; -+ providerPriority = provider.priority; -+ } -+ }); -+ -+ return providerType; -+ } -+ -+ async makePreloginKey(masterPassword: string, email: string): Promise { -+ email = email.trim().toLowerCase(); -+ let kdf: KdfType = null; -+ let kdfIterations: number = null; -+ try { -+ const preloginResponse = await this.apiService.postPrelogin(new PreloginRequest(email)); -+ if (preloginResponse != null) { -+ kdf = preloginResponse.kdf; -+ kdfIterations = preloginResponse.kdfIterations; -+ } -+ } catch (e) { -+ if (e == null || e.statusCode !== 404) { -+ throw e; -+ } - } -- -- getDefaultTwoFactorProvider(webAuthnSupported: boolean): TwoFactorProviderType { -- if (this.twoFactorProvidersData == null) { -- return null; -- } -- -- if (this.selectedTwoFactorProviderType != null && -- this.twoFactorProvidersData.has(this.selectedTwoFactorProviderType)) { -- return this.selectedTwoFactorProviderType; -- } -- -- let providerType: TwoFactorProviderType = null; -- let providerPriority = -1; -- this.twoFactorProvidersData.forEach((value, type) => { -- const provider = (TwoFactorProviders as any)[type]; -- if (provider != null && provider.priority > providerPriority) { -- if (type === TwoFactorProviderType.WebAuthn && !webAuthnSupported) { -- return; -- } -- -- providerType = type; -- providerPriority = provider.priority; -- } -- }); -- -- return providerType; -+ return this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations); -+ } -+ -+ authingWithApiKey(): boolean { -+ return this.clientId != null && this.clientSecret != null; -+ } -+ -+ authingWithSso(): boolean { -+ return this.code != null && this.codeVerifier != null && this.ssoRedirectUrl != null; -+ } -+ -+ authingWithPassword(): boolean { -+ return this.email != null && this.masterPasswordHash != null; -+ } -+ -+ private async logInHelper( -+ email: string, -+ hashedPassword: string, -+ localHashedPassword: string, -+ code: string, -+ codeVerifier: string, -+ redirectUrl: string, -+ clientId: string, -+ clientSecret: string, -+ key: SymmetricCryptoKey, -+ twoFactorProvider?: TwoFactorProviderType, -+ twoFactorToken?: string, -+ remember?: boolean, -+ captchaToken?: string, -+ orgId?: string -+ ): Promise { -+ const storedTwoFactorToken = await this.tokenService.getTwoFactorToken(email); -+ const appId = await this.appIdService.getAppId(); -+ const deviceRequest = new DeviceRequest(appId, this.platformUtilsService); -+ -+ let emailPassword: string[] = []; -+ let codeCodeVerifier: string[] = []; -+ let clientIdClientSecret: [string, string] = [null, null]; -+ -+ if (email != null && hashedPassword != null) { -+ emailPassword = [email, hashedPassword]; -+ } else { -+ emailPassword = null; - } -- -- async makePreloginKey(masterPassword: string, email: string): Promise { -- email = email.trim().toLowerCase(); -- let kdf: KdfType = null; -- let kdfIterations: number = null; -- try { -- const preloginResponse = await this.apiService.postPrelogin(new PreloginRequest(email)); -- if (preloginResponse != null) { -- kdf = preloginResponse.kdf; -- kdfIterations = preloginResponse.kdfIterations; -- } -- } catch (e) { -- if (e == null || e.statusCode !== 404) { -- throw e; -- } -- } -- return this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations); -+ if (code != null && codeVerifier != null && redirectUrl != null) { -+ codeCodeVerifier = [code, codeVerifier, redirectUrl]; -+ } else { -+ codeCodeVerifier = null; -+ } -+ if (clientId != null && clientSecret != null) { -+ clientIdClientSecret = [clientId, clientSecret]; -+ } else { -+ clientIdClientSecret = null; - } - -- authingWithApiKey(): boolean { -- return this.clientId != null && this.clientSecret != null; -+ let request: TokenRequest; -+ if (twoFactorToken != null && twoFactorProvider != null) { -+ request = new TokenRequest( -+ emailPassword, -+ codeCodeVerifier, -+ clientIdClientSecret, -+ twoFactorProvider, -+ twoFactorToken, -+ remember, -+ captchaToken, -+ deviceRequest, -+ orgId -+ ); -+ } else if (storedTwoFactorToken != null) { -+ request = new TokenRequest( -+ emailPassword, -+ codeCodeVerifier, -+ clientIdClientSecret, -+ TwoFactorProviderType.Remember, -+ storedTwoFactorToken, -+ false, -+ captchaToken, -+ deviceRequest, -+ orgId -+ ); -+ } else { -+ request = new TokenRequest( -+ emailPassword, -+ codeCodeVerifier, -+ clientIdClientSecret, -+ null, -+ null, -+ false, -+ captchaToken, -+ deviceRequest, -+ orgId -+ ); - } - -- authingWithSso(): boolean { -- return this.code != null && this.codeVerifier != null && this.ssoRedirectUrl != null; -+ const response = await this.apiService.postIdentityToken(request); -+ -+ this.clearState(); -+ const result = new AuthResult(); -+ result.captchaSiteKey = (response as any).siteKey; -+ if (!!result.captchaSiteKey) { -+ return result; -+ } -+ result.twoFactor = !!(response as any).twoFactorProviders2; -+ -+ if (result.twoFactor) { -+ // two factor required -+ this.email = email; -+ this.masterPasswordHash = hashedPassword; -+ this.localMasterPasswordHash = localHashedPassword; -+ this.code = code; -+ this.codeVerifier = codeVerifier; -+ this.ssoRedirectUrl = redirectUrl; -+ this.clientId = clientId; -+ this.clientSecret = clientSecret; -+ this.key = this.setCryptoKeys ? key : null; -+ const twoFactorResponse = response as IdentityTwoFactorResponse; -+ this.twoFactorProvidersData = twoFactorResponse.twoFactorProviders2; -+ result.twoFactorProviders = twoFactorResponse.twoFactorProviders2; -+ this.captchaToken = twoFactorResponse.captchaToken; -+ return result; - } - -- authingWithPassword(): boolean { -- return this.email != null && this.masterPasswordHash != null; -+ const tokenResponse = response as IdentityTokenResponse; -+ result.resetMasterPassword = tokenResponse.resetMasterPassword; -+ result.forcePasswordReset = tokenResponse.forcePasswordReset; -+ -+ const accountInformation = await this.tokenService.decodeToken(tokenResponse.accessToken); -+ await this.stateService.addAccount( -+ new Account({ -+ profile: { -+ ...new AccountProfile(), -+ ...{ -+ userId: accountInformation.sub, -+ email: accountInformation.email, -+ apiKeyClientId: clientId, -+ hasPremiumPersonally: accountInformation.premium, -+ kdfIterations: tokenResponse.kdfIterations, -+ kdfType: tokenResponse.kdf, -+ }, -+ }, -+ keys: { -+ ...new AccountKeys(), -+ ...{ -+ apiKeyClientSecret: clientSecret, -+ }, -+ }, -+ tokens: { -+ ...new AccountTokens(), -+ ...{ -+ accessToken: tokenResponse.accessToken, -+ refreshToken: tokenResponse.refreshToken, -+ }, -+ }, -+ }) -+ ); -+ -+ if (tokenResponse.twoFactorToken != null) { -+ await this.tokenService.setTwoFactorToken(tokenResponse.twoFactorToken, email); - } - -- private async logInHelper(email: string, hashedPassword: string, localHashedPassword: string, code: string, -- codeVerifier: string, redirectUrl: string, clientId: string, clientSecret: string, key: SymmetricCryptoKey, -- twoFactorProvider?: TwoFactorProviderType, twoFactorToken?: string, remember?: boolean, captchaToken?: string, -- orgId?: string): Promise { -- const storedTwoFactorToken = await this.tokenService.getTwoFactorToken(email); -- const appId = await this.appIdService.getAppId(); -- const deviceRequest = new DeviceRequest(appId, this.platformUtilsService); -- -- let emailPassword: string[] = []; -- let codeCodeVerifier: string[] = []; -- let clientIdClientSecret: [string, string] = [null, null]; -- -- if (email != null && hashedPassword != null) { -- emailPassword = [email, hashedPassword]; -- } else { -- emailPassword = null; -- } -- if (code != null && codeVerifier != null && redirectUrl != null) { -- codeCodeVerifier = [code, codeVerifier, redirectUrl]; -- } else { -- codeCodeVerifier = null; -- } -- if (clientId != null && clientSecret != null) { -- clientIdClientSecret = [clientId, clientSecret]; -- } else { -- clientIdClientSecret = null; -+ if (this.setCryptoKeys) { -+ if (key != null) { -+ await this.cryptoService.setKey(key); -+ } -+ if (localHashedPassword != null) { -+ await this.cryptoService.setKeyHash(localHashedPassword); -+ } -+ -+ // Skip this step during SSO new user flow. No key is returned from server. -+ if (code == null || tokenResponse.key != null) { -+ if (tokenResponse.keyConnectorUrl != null) { -+ await this.keyConnectorService.getAndSetKey(tokenResponse.keyConnectorUrl); -+ } else if (tokenResponse.apiUseKeyConnector) { -+ const keyConnectorUrl = this.environmentService.getKeyConnectorUrl(); -+ await this.keyConnectorService.getAndSetKey(keyConnectorUrl); - } - -- let request: TokenRequest; -- if (twoFactorToken != null && twoFactorProvider != null) { -- request = new TokenRequest(emailPassword, codeCodeVerifier, clientIdClientSecret, twoFactorProvider, -- twoFactorToken, remember, captchaToken, deviceRequest); -- } else if (storedTwoFactorToken != null) { -- request = new TokenRequest(emailPassword, codeCodeVerifier, clientIdClientSecret, -- TwoFactorProviderType.Remember, storedTwoFactorToken, false, captchaToken, deviceRequest); -- } else { -- request = new TokenRequest(emailPassword, codeCodeVerifier, clientIdClientSecret, null, -- null, false, captchaToken, deviceRequest); -+ await this.cryptoService.setEncKey(tokenResponse.key); -+ -+ // User doesn't have a key pair yet (old account), let's generate one for them -+ if (tokenResponse.privateKey == null) { -+ try { -+ const keyPair = await this.cryptoService.makeKeyPair(); -+ await this.apiService.postAccountKeys( -+ new KeysRequest(keyPair[0], keyPair[1].encryptedString) -+ ); -+ tokenResponse.privateKey = keyPair[1].encryptedString; -+ } catch (e) { -+ this.logService.error(e); -+ } - } - -- const response = await this.apiService.postIdentityToken(request); -+ await this.cryptoService.setEncPrivateKey(tokenResponse.privateKey); -+ } else if (tokenResponse.keyConnectorUrl != null) { -+ const password = await this.cryptoFunctionService.randomBytes(64); - -- this.clearState(); -- const result = new AuthResult(); -- result.captchaSiteKey = (response as any).siteKey; -- if (!!result.captchaSiteKey) { -- return result; -- } -- result.twoFactor = !!(response as any).twoFactorProviders2; -- -- if (result.twoFactor) { -- // two factor required -- this.email = email; -- this.masterPasswordHash = hashedPassword; -- this.localMasterPasswordHash = localHashedPassword; -- this.code = code; -- this.codeVerifier = codeVerifier; -- this.ssoRedirectUrl = redirectUrl; -- this.clientId = clientId; -- this.clientSecret = clientSecret; -- this.key = this.setCryptoKeys ? key : null; -- const twoFactorResponse = response as IdentityTwoFactorResponse; -- this.twoFactorProvidersData = twoFactorResponse.twoFactorProviders2; -- result.twoFactorProviders = twoFactorResponse.twoFactorProviders2; -- this.captchaToken = twoFactorResponse.captchaToken; -- return result; -- } -+ const k = await this.cryptoService.makeKey( -+ Utils.fromBufferToB64(password), -+ await this.tokenService.getEmail(), -+ tokenResponse.kdf, -+ tokenResponse.kdfIterations -+ ); -+ const keyConnectorRequest = new KeyConnectorUserKeyRequest(k.encKeyB64); -+ await this.cryptoService.setKey(k); - -- const tokenResponse = response as IdentityTokenResponse; -- result.resetMasterPassword = tokenResponse.resetMasterPassword; -- result.forcePasswordReset = tokenResponse.forcePasswordReset; -- if (tokenResponse.twoFactorToken != null) { -- await this.tokenService.setTwoFactorToken(tokenResponse.twoFactorToken, email); -- } -+ const encKey = await this.cryptoService.makeEncKey(k); -+ await this.cryptoService.setEncKey(encKey[1].encryptedString); - -- await this.tokenService.setTokens(tokenResponse.accessToken, tokenResponse.refreshToken, clientIdClientSecret); -- await this.userService.setInformation(this.tokenService.getUserId(), this.tokenService.getEmail(), -- tokenResponse.kdf, tokenResponse.kdfIterations); -- if (this.setCryptoKeys) { -- if (key != null) { -- await this.cryptoService.setKey(key); -- } -- if (localHashedPassword != null) { -- await this.cryptoService.setKeyHash(localHashedPassword); -- } -- -- // Skip this step during SSO new user flow. No key is returned from server. -- if (code == null || tokenResponse.key != null) { -- -- if (tokenResponse.keyConnectorUrl != null) { -- await this.keyConnectorService.getAndSetKey(tokenResponse.keyConnectorUrl); -- } else if (tokenResponse.apiUseKeyConnector) { -- const keyConnectorUrl = this.environmentService.getKeyConnectorUrl(); -- await this.keyConnectorService.getAndSetKey(keyConnectorUrl); -- } -- -- await this.cryptoService.setEncKey(tokenResponse.key); -- -- // User doesn't have a key pair yet (old account), let's generate one for them -- if (tokenResponse.privateKey == null) { -- try { -- const keyPair = await this.cryptoService.makeKeyPair(); -- await this.apiService.postAccountKeys(new KeysRequest(keyPair[0], keyPair[1].encryptedString)); -- tokenResponse.privateKey = keyPair[1].encryptedString; -- } catch (e) { -- this.logService.error(e); -- } -- } -- -- await this.cryptoService.setEncPrivateKey(tokenResponse.privateKey); -- } else if (tokenResponse.keyConnectorUrl != null) { -- const password = await this.cryptoFunctionService.randomBytes(64); -- -- const k = await this.cryptoService.makeKey(Utils.fromBufferToB64(password), this.tokenService.getEmail(), tokenResponse.kdf, tokenResponse.kdfIterations); -- const keyConnectorRequest = new KeyConnectorUserKeyRequest(k.encKeyB64); -- await this.cryptoService.setKey(k); -- -- const encKey = await this.cryptoService.makeEncKey(k); -- await this.cryptoService.setEncKey(encKey[1].encryptedString); -- -- const [pubKey, privKey] = await this.cryptoService.makeKeyPair(); -- -- try { -- await this.apiService.postUserKeyToKeyConnector(tokenResponse.keyConnectorUrl, keyConnectorRequest); -- } catch (e) { -- throw new Error('Unable to reach key connector'); -- } -- -- const keys = new KeysRequest(pubKey, privKey.encryptedString); -- const setPasswordRequest = new SetKeyConnectorKeyRequest( -- encKey[1].encryptedString, tokenResponse.kdf, tokenResponse.kdfIterations, orgId, keys -- ); -- await this.apiService.postSetKeyConnectorKey(setPasswordRequest); -- } -- } -+ const [pubKey, privKey] = await this.cryptoService.makeKeyPair(); - -- if (this.vaultTimeoutService != null) { -- this.vaultTimeoutService.biometricLocked = false; -+ try { -+ await this.apiService.postUserKeyToKeyConnector( -+ tokenResponse.keyConnectorUrl, -+ keyConnectorRequest -+ ); -+ } catch (e) { -+ throw new Error("Unable to reach key connector"); - } -- this.messagingService.send('loggedIn'); -- return result; -+ -+ const keys = new KeysRequest(pubKey, privKey.encryptedString); -+ const setPasswordRequest = new SetKeyConnectorKeyRequest( -+ encKey[1].encryptedString, -+ tokenResponse.kdf, -+ tokenResponse.kdfIterations, -+ orgId, -+ keys -+ ); -+ await this.apiService.postSetKeyConnectorKey(setPasswordRequest); -+ } - } - -- private clearState(): void { -- this.key = null; -- this.email = null; -- this.masterPasswordHash = null; -- this.localMasterPasswordHash = null; -- this.code = null; -- this.codeVerifier = null; -- this.ssoRedirectUrl = null; -- this.clientId = null; -- this.clientSecret = null; -- this.twoFactorProvidersData = null; -- this.selectedTwoFactorProviderType = null; -+ if (this.vaultTimeoutService != null) { -+ await this.stateService.setBiometricLocked(false); - } -+ this.messagingService.send("loggedIn"); -+ return result; -+ } -+ -+ private clearState(): void { -+ this.key = null; -+ this.email = null; -+ this.masterPasswordHash = null; -+ this.localMasterPasswordHash = null; -+ this.code = null; -+ this.codeVerifier = null; -+ this.ssoRedirectUrl = null; -+ this.clientId = null; -+ this.clientSecret = null; -+ this.twoFactorProvidersData = null; -+ this.selectedTwoFactorProviderType = null; -+ } - } -diff --git a/jslib/common/src/services/azureFileUpload.service.ts b/jslib/common/src/services/azureFileUpload.service.ts -index 680e5ffc..300ee7b7 100644 ---- a/jslib/common/src/services/azureFileUpload.service.ts -+++ b/jslib/common/src/services/azureFileUpload.service.ts -@@ -1,201 +1,215 @@ --import { LogService } from '../abstractions/log.service'; -+import { LogService } from "../abstractions/log.service"; - --import { Utils } from '../misc/utils'; -+import { Utils } from "../misc/utils"; - --import { EncArrayBuffer } from '../models/domain/encArrayBuffer'; -+import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; - - const MAX_SINGLE_BLOB_UPLOAD_SIZE = 256 * 1024 * 1024; // 256 MiB - const MAX_BLOCKS_PER_BLOB = 50000; - - export class AzureFileUploadService { -- constructor(private logService: LogService) { } -+ constructor(private logService: LogService) {} - -- async upload(url: string, data: EncArrayBuffer, renewalCallback: () => Promise) { -- if (data.buffer.byteLength <= MAX_SINGLE_BLOB_UPLOAD_SIZE) { -- return await this.azureUploadBlob(url, data); -- } else { -- return await this.azureUploadBlocks(url, data, renewalCallback); -- } -+ async upload(url: string, data: EncArrayBuffer, renewalCallback: () => Promise) { -+ if (data.buffer.byteLength <= MAX_SINGLE_BLOB_UPLOAD_SIZE) { -+ return await this.azureUploadBlob(url, data); -+ } else { -+ return await this.azureUploadBlocks(url, data, renewalCallback); -+ } -+ } -+ private async azureUploadBlob(url: string, data: EncArrayBuffer) { -+ const urlObject = Utils.getUrl(url); -+ const headers = new Headers({ -+ "x-ms-date": new Date().toUTCString(), -+ "x-ms-version": urlObject.searchParams.get("sv"), -+ "Content-Length": data.buffer.byteLength.toString(), -+ "x-ms-blob-type": "BlockBlob", -+ }); -+ -+ const request = new Request(url, { -+ body: data.buffer, -+ cache: "no-store", -+ method: "PUT", -+ headers: headers, -+ }); -+ -+ const blobResponse = await fetch(request); -+ -+ if (blobResponse.status !== 201) { -+ throw new Error(`Failed to create Azure blob: ${blobResponse.status}`); -+ } -+ } -+ private async azureUploadBlocks( -+ url: string, -+ data: EncArrayBuffer, -+ renewalCallback: () => Promise -+ ) { -+ const baseUrl = Utils.getUrl(url); -+ const blockSize = this.getMaxBlockSize(baseUrl.searchParams.get("sv")); -+ let blockIndex = 0; -+ const numBlocks = Math.ceil(data.buffer.byteLength / blockSize); -+ const blocksStaged: string[] = []; -+ -+ if (numBlocks > MAX_BLOCKS_PER_BLOB) { -+ throw new Error( -+ `Cannot upload file, exceeds maximum size of ${blockSize * MAX_BLOCKS_PER_BLOB}` -+ ); - } -- private async azureUploadBlob(url: string, data: EncArrayBuffer) { -- const urlObject = Utils.getUrl(url); -- const headers = new Headers({ -- 'x-ms-date': new Date().toUTCString(), -- 'x-ms-version': urlObject.searchParams.get('sv'), -- 'Content-Length': data.buffer.byteLength.toString(), -- 'x-ms-blob-type': 'BlockBlob', -+ -+ try { -+ while (blockIndex < numBlocks) { -+ url = await this.renewUrlIfNecessary(url, renewalCallback); -+ const blockUrl = Utils.getUrl(url); -+ const blockId = this.encodedBlockId(blockIndex); -+ blockUrl.searchParams.append("comp", "block"); -+ blockUrl.searchParams.append("blockid", blockId); -+ const start = blockIndex * blockSize; -+ const blockData = data.buffer.slice(start, start + blockSize); -+ const blockHeaders = new Headers({ -+ "x-ms-date": new Date().toUTCString(), -+ "x-ms-version": blockUrl.searchParams.get("sv"), -+ "Content-Length": blockData.byteLength.toString(), - }); - -- const request = new Request(url, { -- body: data.buffer, -- cache: 'no-store', -- method: 'PUT', -- headers: headers, -+ const blockRequest = new Request(blockUrl.toString(), { -+ body: blockData, -+ cache: "no-store", -+ method: "PUT", -+ headers: blockHeaders, - }); - -- const blobResponse = await fetch(request); -+ const blockResponse = await fetch(blockRequest); - -- if (blobResponse.status !== 201) { -- throw new Error(`Failed to create Azure blob: ${blobResponse.status}`); -- } -- } -- private async azureUploadBlocks(url: string, data: EncArrayBuffer, renewalCallback: () => Promise) { -- const baseUrl = Utils.getUrl(url); -- const blockSize = this.getMaxBlockSize(baseUrl.searchParams.get('sv')); -- let blockIndex = 0; -- const numBlocks = Math.ceil(data.buffer.byteLength / blockSize); -- const blocksStaged: string[] = []; -- -- if (numBlocks > MAX_BLOCKS_PER_BLOB) { -- throw new Error(`Cannot upload file, exceeds maximum size of ${blockSize * MAX_BLOCKS_PER_BLOB}`); -+ if (blockResponse.status !== 201) { -+ const message = `Unsuccessful block PUT. Received status ${blockResponse.status}`; -+ this.logService.error(message + "\n" + (await blockResponse.json())); -+ throw new Error(message); - } - -- try { -- while (blockIndex < numBlocks) { -- url = await this.renewUrlIfNecessary(url, renewalCallback); -- const blockUrl = Utils.getUrl(url); -- const blockId = this.encodedBlockId(blockIndex); -- blockUrl.searchParams.append('comp', 'block'); -- blockUrl.searchParams.append('blockid', blockId); -- const start = blockIndex * blockSize; -- const blockData = data.buffer.slice(start, start + blockSize); -- const blockHeaders = new Headers({ -- 'x-ms-date': new Date().toUTCString(), -- 'x-ms-version': blockUrl.searchParams.get('sv'), -- 'Content-Length': blockData.byteLength.toString(), -- }); -- -- const blockRequest = new Request(blockUrl.toString(), { -- body: blockData, -- cache: 'no-store', -- method: 'PUT', -- headers: blockHeaders, -- }); -- -- const blockResponse = await fetch(blockRequest); -- -- if (blockResponse.status !== 201) { -- const message = `Unsuccessful block PUT. Received status ${blockResponse.status}`; -- this.logService.error(message + '\n' + await blockResponse.json()); -- throw new Error(message); -- } -- -- blocksStaged.push(blockId); -- blockIndex++; -- } -- -- url = await this.renewUrlIfNecessary(url, renewalCallback); -- const blockListUrl = Utils.getUrl(url); -- const blockListXml = this.blockListXml(blocksStaged); -- blockListUrl.searchParams.append('comp', 'blocklist'); -- const headers = new Headers({ -- 'x-ms-date': new Date().toUTCString(), -- 'x-ms-version': blockListUrl.searchParams.get('sv'), -- 'Content-Length': blockListXml.length.toString(), -- }); -- -- const request = new Request(blockListUrl.toString(), { -- body: blockListXml, -- cache: 'no-store', -- method: 'PUT', -- headers: headers, -- }); -- -- const response = await fetch(request); -- -- if (response.status !== 201) { -- const message = `Unsuccessful block list PUT. Received status ${response.status}`; -- this.logService.error(message + '\n' + await response.json()); -- throw new Error(message); -- } -- } catch (e) { -- throw e; -- } -+ blocksStaged.push(blockId); -+ blockIndex++; -+ } -+ -+ url = await this.renewUrlIfNecessary(url, renewalCallback); -+ const blockListUrl = Utils.getUrl(url); -+ const blockListXml = this.blockListXml(blocksStaged); -+ blockListUrl.searchParams.append("comp", "blocklist"); -+ const headers = new Headers({ -+ "x-ms-date": new Date().toUTCString(), -+ "x-ms-version": blockListUrl.searchParams.get("sv"), -+ "Content-Length": blockListXml.length.toString(), -+ }); -+ -+ const request = new Request(blockListUrl.toString(), { -+ body: blockListXml, -+ cache: "no-store", -+ method: "PUT", -+ headers: headers, -+ }); -+ -+ const response = await fetch(request); -+ -+ if (response.status !== 201) { -+ const message = `Unsuccessful block list PUT. Received status ${response.status}`; -+ this.logService.error(message + "\n" + (await response.json())); -+ throw new Error(message); -+ } -+ } catch (e) { -+ throw e; - } -+ } - -- private async renewUrlIfNecessary(url: string, renewalCallback: () => Promise): Promise { -- const urlObject = Utils.getUrl(url); -- const expiry = new Date(urlObject.searchParams.get('se') ?? ''); -- -- if (isNaN(expiry.getTime())) { -- expiry.setTime(Date.now() + 3600000); -- } -- -- if (expiry.getTime() < Date.now() + 1000) { -- return await renewalCallback(); -- } -- return url; -- } -+ private async renewUrlIfNecessary( -+ url: string, -+ renewalCallback: () => Promise -+ ): Promise { -+ const urlObject = Utils.getUrl(url); -+ const expiry = new Date(urlObject.searchParams.get("se") ?? ""); - -- private encodedBlockId(blockIndex: number) { -- // Encoded blockId max size is 64, so pre-encoding max size is 48 -- const utfBlockId = ('000000000000000000000000000000000000000000000000' + blockIndex.toString()).slice(-48); -- return Utils.fromUtf8ToB64(utfBlockId); -+ if (isNaN(expiry.getTime())) { -+ expiry.setTime(Date.now() + 3600000); - } - -- private blockListXml(blockIdList: string[]) { -- let xml = ''; -- blockIdList.forEach(blockId => { -- xml += `${blockId}`; -- }); -- xml += ''; -- return xml; -+ if (expiry.getTime() < Date.now() + 1000) { -+ return await renewalCallback(); - } -- -- private getMaxBlockSize(version: string) { -- if (Version.compare(version, '2019-12-12') >= 0) { -- return 4000 * 1024 * 1024; // 4000 MiB -- } else if (Version.compare(version, '2016-05-31') >= 0) { -- return 100 * 1024 * 1024; // 100 MiB -- } else { -- return 4 * 1024 * 1024; // 4 MiB -- } -+ return url; -+ } -+ -+ private encodedBlockId(blockIndex: number) { -+ // Encoded blockId max size is 64, so pre-encoding max size is 48 -+ const utfBlockId = ( -+ "000000000000000000000000000000000000000000000000" + blockIndex.toString() -+ ).slice(-48); -+ return Utils.fromUtf8ToB64(utfBlockId); -+ } -+ -+ private blockListXml(blockIdList: string[]) { -+ let xml = ''; -+ blockIdList.forEach((blockId) => { -+ xml += `${blockId}`; -+ }); -+ xml += ""; -+ return xml; -+ } -+ -+ private getMaxBlockSize(version: string) { -+ if (Version.compare(version, "2019-12-12") >= 0) { -+ return 4000 * 1024 * 1024; // 4000 MiB -+ } else if (Version.compare(version, "2016-05-31") >= 0) { -+ return 100 * 1024 * 1024; // 100 MiB -+ } else { -+ return 4 * 1024 * 1024; // 4 MiB - } -+ } - } - - class Version { -- /** -- * Compares two Azure Versions against each other -- * @param a Version to compare -- * @param b Version to compare -- * @returns a number less than zero if b is newer than a, 0 if equal, -- * and greater than zero if a is newer than b -- */ -- static compare(a: Required | string, b: Required | string) { -- if (typeof (a) === 'string') { -- a = new Version(a); -- } -- -- if (typeof (b) === 'string') { -- b = new Version(b); -- } -- -- return a.year !== b.year ? a.year - b.year : -- a.month !== b.month ? a.month - b.month : -- a.day !== b.day ? a.day - b.day : -- 0; -+ /** -+ * Compares two Azure Versions against each other -+ * @param a Version to compare -+ * @param b Version to compare -+ * @returns a number less than zero if b is newer than a, 0 if equal, -+ * and greater than zero if a is newer than b -+ */ -+ static compare(a: Required | string, b: Required | string) { -+ if (typeof a === "string") { -+ a = new Version(a); - } -- year = 0; -- month = 0; -- day = 0; -- -- constructor(version: string) { -- try { -- const parts = version.split('-').map(v => Number.parseInt(v, 10)); -- this.year = parts[0]; -- this.month = parts[1]; -- this.day = parts[2]; -- } catch { -- // Ignore error -- } -+ -+ if (typeof b === "string") { -+ b = new Version(b); - } -- /** -- * Compares two Azure Versions against each other -- * @param compareTo Version to compare against -- * @returns a number less than zero if compareTo is newer, 0 if equal, -- * and greater than zero if this is greater than compareTo -- */ -- compare(compareTo: Required | string) { -- return Version.compare(this, compareTo); -+ -+ return a.year !== b.year -+ ? a.year - b.year -+ : a.month !== b.month -+ ? a.month - b.month -+ : a.day !== b.day -+ ? a.day - b.day -+ : 0; -+ } -+ year = 0; -+ month = 0; -+ day = 0; -+ -+ constructor(version: string) { -+ try { -+ const parts = version.split("-").map((v) => Number.parseInt(v, 10)); -+ this.year = parts[0]; -+ this.month = parts[1]; -+ this.day = parts[2]; -+ } catch { -+ // Ignore error - } -+ } -+ /** -+ * Compares two Azure Versions against each other -+ * @param compareTo Version to compare against -+ * @returns a number less than zero if compareTo is newer, 0 if equal, -+ * and greater than zero if this is greater than compareTo -+ */ -+ compare(compareTo: Required | string) { -+ return Version.compare(this, compareTo); -+ } - } -diff --git a/jslib/common/src/services/bitwardenFileUpload.service.ts b/jslib/common/src/services/bitwardenFileUpload.service.ts -index 9c699a48..3f54f73e 100644 ---- a/jslib/common/src/services/bitwardenFileUpload.service.ts -+++ b/jslib/common/src/services/bitwardenFileUpload.service.ts -@@ -1,29 +1,36 @@ --import { ApiService } from '../abstractions/api.service'; -+import { ApiService } from "../abstractions/api.service"; - --import { EncArrayBuffer } from '../models/domain/encArrayBuffer'; -+import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; - --import { Utils } from '../misc/utils'; -+import { Utils } from "../misc/utils"; - --export class BitwardenFileUploadService --{ -- constructor(private apiService: ApiService) { } -+export class BitwardenFileUploadService { -+ constructor(private apiService: ApiService) {} - -- async upload(encryptedFileName: string, encryptedFileData: EncArrayBuffer, apiCall: (fd: FormData) => Promise) { -- const fd = new FormData(); -- try { -- const blob = new Blob([encryptedFileData.buffer], { type: 'application/octet-stream' }); -- fd.append('data', blob, encryptedFileName); -- } catch (e) { -- if (Utils.isNode && !Utils.isBrowser) { -- fd.append('data', Buffer.from(encryptedFileData.buffer) as any, { -- filepath: encryptedFileName, -- contentType: 'application/octet-stream', -- } as any); -- } else { -- throw e; -- } -- } -- -- await apiCall(fd); -+ async upload( -+ encryptedFileName: string, -+ encryptedFileData: EncArrayBuffer, -+ apiCall: (fd: FormData) => Promise -+ ) { -+ const fd = new FormData(); -+ try { -+ const blob = new Blob([encryptedFileData.buffer], { type: "application/octet-stream" }); -+ fd.append("data", blob, encryptedFileName); -+ } catch (e) { -+ if (Utils.isNode && !Utils.isBrowser) { -+ fd.append( -+ "data", -+ Buffer.from(encryptedFileData.buffer) as any, -+ { -+ filepath: encryptedFileName, -+ contentType: "application/octet-stream", -+ } as any -+ ); -+ } else { -+ throw e; -+ } - } -+ -+ await apiCall(fd); -+ } - } -diff --git a/jslib/common/src/services/broadcaster.service.ts b/jslib/common/src/services/broadcaster.service.ts -index 6dfbfa64..999fbe4b 100644 ---- a/jslib/common/src/services/broadcaster.service.ts -+++ b/jslib/common/src/services/broadcaster.service.ts -@@ -1,28 +1,28 @@ --import { BroadcasterService as BroadcasterServiceAbstraction } from '../abstractions/broadcaster.service'; -+import { BroadcasterService as BroadcasterServiceAbstraction } from "../abstractions/broadcaster.service"; - - export class BroadcasterService implements BroadcasterServiceAbstraction { -- subscribers: Map any> = new Map any>(); -+ subscribers: Map any> = new Map any>(); - -- send(message: any, id?: string) { -- if (id != null) { -- if (this.subscribers.has(id)) { -- this.subscribers.get(id)(message); -- } -- return; -- } -- -- this.subscribers.forEach(value => { -- value(message); -- }); -+ send(message: any, id?: string) { -+ if (id != null) { -+ if (this.subscribers.has(id)) { -+ this.subscribers.get(id)(message); -+ } -+ return; - } - -- subscribe(id: string, messageCallback: (message: any) => any) { -- this.subscribers.set(id, messageCallback); -- } -+ this.subscribers.forEach((value) => { -+ value(message); -+ }); -+ } -+ -+ subscribe(id: string, messageCallback: (message: any) => any) { -+ this.subscribers.set(id, messageCallback); -+ } - -- unsubscribe(id: string) { -- if (this.subscribers.has(id)) { -- this.subscribers.delete(id); -- } -+ unsubscribe(id: string) { -+ if (this.subscribers.has(id)) { -+ this.subscribers.delete(id); - } -+ } - } -diff --git a/jslib/common/src/services/cipher.service.ts b/jslib/common/src/services/cipher.service.ts -index 421f381a..e9002146 100644 ---- a/jslib/common/src/services/cipher.service.ts -+++ b/jslib/common/src/services/cipher.service.ts -@@ -1,1131 +1,1283 @@ --import { CipherType } from '../enums/cipherType'; --import { FieldType } from '../enums/fieldType'; --import { UriMatchType } from '../enums/uriMatchType'; -- --import { CipherData } from '../models/data/cipherData'; -- --import { Attachment } from '../models/domain/attachment'; --import { Card } from '../models/domain/card'; --import { Cipher } from '../models/domain/cipher'; --import Domain from '../models/domain/domainBase'; --import { EncArrayBuffer } from '../models/domain/encArrayBuffer'; --import { EncString } from '../models/domain/encString'; --import { Field } from '../models/domain/field'; --import { Identity } from '../models/domain/identity'; --import { Login } from '../models/domain/login'; --import { LoginUri } from '../models/domain/loginUri'; --import { Password } from '../models/domain/password'; --import { SecureNote } from '../models/domain/secureNote'; --import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; -- --import { AttachmentRequest } from '../models/request/attachmentRequest'; --import { CipherBulkDeleteRequest } from '../models/request/cipherBulkDeleteRequest'; --import { CipherBulkMoveRequest } from '../models/request/cipherBulkMoveRequest'; --import { CipherBulkRestoreRequest } from '../models/request/cipherBulkRestoreRequest'; --import { CipherBulkShareRequest } from '../models/request/cipherBulkShareRequest'; --import { CipherCollectionsRequest } from '../models/request/cipherCollectionsRequest'; --import { CipherCreateRequest } from '../models/request/cipherCreateRequest'; --import { CipherRequest } from '../models/request/cipherRequest'; --import { CipherShareRequest } from '../models/request/cipherShareRequest'; -- --import { CipherResponse } from '../models/response/cipherResponse'; --import { ErrorResponse } from '../models/response/errorResponse'; -- --import { AttachmentView } from '../models/view/attachmentView'; --import { CipherView } from '../models/view/cipherView'; --import { FieldView } from '../models/view/fieldView'; --import { PasswordHistoryView } from '../models/view/passwordHistoryView'; --import { View } from '../models/view/view'; -- --import { SortedCiphersCache } from '../models/domain/sortedCiphersCache'; -- --import { ApiService } from '../abstractions/api.service'; --import { CipherService as CipherServiceAbstraction } from '../abstractions/cipher.service'; --import { CryptoService } from '../abstractions/crypto.service'; --import { FileUploadService } from '../abstractions/fileUpload.service'; --import { I18nService } from '../abstractions/i18n.service'; --import { SearchService } from '../abstractions/search.service'; --import { SettingsService } from '../abstractions/settings.service'; --import { StorageService } from '../abstractions/storage.service'; --import { UserService } from '../abstractions/user.service'; -- --import { ConstantsService } from './constants.service'; -- --import { LogService } from '../abstractions/log.service'; --import { sequentialize } from '../misc/sequentialize'; --import { Utils } from '../misc/utils'; -- --const Keys = { -- ciphersPrefix: 'ciphers_', -- localData: 'sitesLocalData', -- neverDomains: 'neverDomains', --}; -+import { CipherType } from "../enums/cipherType"; -+import { FieldType } from "../enums/fieldType"; -+import { UriMatchType } from "../enums/uriMatchType"; -+ -+import { CipherData } from "../models/data/cipherData"; -+ -+import { Attachment } from "../models/domain/attachment"; -+import { Card } from "../models/domain/card"; -+import { Cipher } from "../models/domain/cipher"; -+import Domain from "../models/domain/domainBase"; -+import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; -+import { EncString } from "../models/domain/encString"; -+import { Field } from "../models/domain/field"; -+import { Identity } from "../models/domain/identity"; -+import { Login } from "../models/domain/login"; -+import { LoginUri } from "../models/domain/loginUri"; -+import { Password } from "../models/domain/password"; -+import { SecureNote } from "../models/domain/secureNote"; -+import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; -+ -+import { AttachmentRequest } from "../models/request/attachmentRequest"; -+import { CipherBulkDeleteRequest } from "../models/request/cipherBulkDeleteRequest"; -+import { CipherBulkMoveRequest } from "../models/request/cipherBulkMoveRequest"; -+import { CipherBulkRestoreRequest } from "../models/request/cipherBulkRestoreRequest"; -+import { CipherBulkShareRequest } from "../models/request/cipherBulkShareRequest"; -+import { CipherCollectionsRequest } from "../models/request/cipherCollectionsRequest"; -+import { CipherCreateRequest } from "../models/request/cipherCreateRequest"; -+import { CipherRequest } from "../models/request/cipherRequest"; -+import { CipherShareRequest } from "../models/request/cipherShareRequest"; -+ -+import { CipherResponse } from "../models/response/cipherResponse"; -+import { ErrorResponse } from "../models/response/errorResponse"; -+ -+import { AttachmentView } from "../models/view/attachmentView"; -+import { CipherView } from "../models/view/cipherView"; -+import { FieldView } from "../models/view/fieldView"; -+import { PasswordHistoryView } from "../models/view/passwordHistoryView"; -+import { View } from "../models/view/view"; -+ -+import { SortedCiphersCache } from "../models/domain/sortedCiphersCache"; -+ -+import { ApiService } from "../abstractions/api.service"; -+import { CipherService as CipherServiceAbstraction } from "../abstractions/cipher.service"; -+import { CryptoService } from "../abstractions/crypto.service"; -+import { FileUploadService } from "../abstractions/fileUpload.service"; -+import { I18nService } from "../abstractions/i18n.service"; -+import { SearchService } from "../abstractions/search.service"; -+import { SettingsService } from "../abstractions/settings.service"; -+import { StateService } from "../abstractions/state.service"; -+ -+import { LogService } from "../abstractions/log.service"; -+import { sequentialize } from "../misc/sequentialize"; -+import { Utils } from "../misc/utils"; - - const DomainMatchBlacklist = new Map>([ -- ['google.com', new Set(['script.google.com'])], -+ ["google.com", new Set(["script.google.com"])], - ]); - - export class CipherService implements CipherServiceAbstraction { -- // tslint:disable-next-line -- _decryptedCipherCache: CipherView[]; -- -- private sortedCiphersCache: SortedCiphersCache = new SortedCiphersCache(this.sortCiphersByLastUsed); -- -- constructor(private cryptoService: CryptoService, private userService: UserService, -- private settingsService: SettingsService, private apiService: ApiService, -- private fileUploadService: FileUploadService, private storageService: StorageService, -- private i18nService: I18nService, private searchService: () => SearchService, -- private logService: LogService) { -- } -- -- get decryptedCipherCache() { -- return this._decryptedCipherCache; -+ private sortedCiphersCache: SortedCiphersCache = new SortedCiphersCache( -+ this.sortCiphersByLastUsed -+ ); -+ -+ constructor( -+ private cryptoService: CryptoService, -+ private settingsService: SettingsService, -+ private apiService: ApiService, -+ private fileUploadService: FileUploadService, -+ private i18nService: I18nService, -+ private searchService: () => SearchService, -+ private logService: LogService, -+ private stateService: StateService -+ ) {} -+ -+ async getDecryptedCipherCache(): Promise { -+ const decryptedCiphers = await this.stateService.getDecryptedCiphers(); -+ return decryptedCiphers; -+ } -+ -+ async setDecryptedCipherCache(value: CipherView[]) { -+ await this.stateService.setDecryptedCiphers(value); -+ if (this.searchService != null) { -+ if (value == null) { -+ this.searchService().clearIndex(); -+ } else { -+ this.searchService().indexCiphers(); -+ } - } -- set decryptedCipherCache(value: CipherView[]) { -- this._decryptedCipherCache = value; -- if (this.searchService != null) { -- if (value == null) { -- this.searchService().clearIndex(); -- } else { -- this.searchService().indexCiphers(); -+ } -+ -+ async clearCache(userId?: string): Promise { -+ await this.clearDecryptedCiphersState(userId); -+ } -+ -+ async encrypt( -+ model: CipherView, -+ key?: SymmetricCryptoKey, -+ originalCipher: Cipher = null -+ ): Promise { -+ // Adjust password history -+ if (model.id != null) { -+ if (originalCipher == null) { -+ originalCipher = await this.get(model.id); -+ } -+ if (originalCipher != null) { -+ const existingCipher = await originalCipher.decrypt(); -+ model.passwordHistory = existingCipher.passwordHistory || []; -+ if (model.type === CipherType.Login && existingCipher.type === CipherType.Login) { -+ if ( -+ existingCipher.login.password != null && -+ existingCipher.login.password !== "" && -+ existingCipher.login.password !== model.login.password -+ ) { -+ const ph = new PasswordHistoryView(); -+ ph.password = existingCipher.login.password; -+ ph.lastUsedDate = model.login.passwordRevisionDate = new Date(); -+ model.passwordHistory.splice(0, 0, ph); -+ } else { -+ model.login.passwordRevisionDate = existingCipher.login.passwordRevisionDate; -+ } -+ } -+ if (existingCipher.hasFields) { -+ const existingHiddenFields = existingCipher.fields.filter( -+ (f) => -+ f.type === FieldType.Hidden && -+ f.name != null && -+ f.name !== "" && -+ f.value != null && -+ f.value !== "" -+ ); -+ const hiddenFields = -+ model.fields == null -+ ? [] -+ : model.fields.filter( -+ (f) => f.type === FieldType.Hidden && f.name != null && f.name !== "" -+ ); -+ existingHiddenFields.forEach((ef) => { -+ const matchedField = hiddenFields.find((f) => f.name === ef.name); -+ if (matchedField == null || matchedField.value !== ef.value) { -+ const ph = new PasswordHistoryView(); -+ ph.password = ef.name + ": " + ef.value; -+ ph.lastUsedDate = new Date(); -+ model.passwordHistory.splice(0, 0, ph); - } -+ }); - } -+ } -+ if (model.passwordHistory != null && model.passwordHistory.length === 0) { -+ model.passwordHistory = null; -+ } else if (model.passwordHistory != null && model.passwordHistory.length > 5) { -+ // only save last 5 history -+ model.passwordHistory = model.passwordHistory.slice(0, 5); -+ } - } - -- clearCache(): void { -- this.decryptedCipherCache = null; -- this.sortedCiphersCache.clear(); -+ const cipher = new Cipher(); -+ cipher.id = model.id; -+ cipher.folderId = model.folderId; -+ cipher.favorite = model.favorite; -+ cipher.organizationId = model.organizationId; -+ cipher.type = model.type; -+ cipher.collectionIds = model.collectionIds; -+ cipher.revisionDate = model.revisionDate; -+ cipher.reprompt = model.reprompt; -+ -+ if (key == null && cipher.organizationId != null) { -+ key = await this.cryptoService.getOrgKey(cipher.organizationId); -+ if (key == null) { -+ throw new Error("Cannot encrypt cipher for organization. No key."); -+ } - } -- -- async encrypt(model: CipherView, key?: SymmetricCryptoKey, originalCipher: Cipher = null): Promise { -- // Adjust password history -- if (model.id != null) { -- if (originalCipher == null) { -- originalCipher = await this.get(model.id); -- } -- if (originalCipher != null) { -- const existingCipher = await originalCipher.decrypt(); -- model.passwordHistory = existingCipher.passwordHistory || []; -- if (model.type === CipherType.Login && existingCipher.type === CipherType.Login) { -- if (existingCipher.login.password != null && existingCipher.login.password !== '' && -- existingCipher.login.password !== model.login.password) { -- const ph = new PasswordHistoryView(); -- ph.password = existingCipher.login.password; -- ph.lastUsedDate = model.login.passwordRevisionDate = new Date(); -- model.passwordHistory.splice(0, 0, ph); -- } else { -- model.login.passwordRevisionDate = existingCipher.login.passwordRevisionDate; -- } -- } -- if (existingCipher.hasFields) { -- const existingHiddenFields = existingCipher.fields.filter(f => f.type === FieldType.Hidden && -- f.name != null && f.name !== '' && f.value != null && f.value !== ''); -- const hiddenFields = model.fields == null ? [] : -- model.fields.filter(f => f.type === FieldType.Hidden && f.name != null && f.name !== ''); -- existingHiddenFields.forEach(ef => { -- const matchedField = hiddenFields.find(f => f.name === ef.name); -- if (matchedField == null || matchedField.value !== ef.value) { -- const ph = new PasswordHistoryView(); -- ph.password = ef.name + ': ' + ef.value; -- ph.lastUsedDate = new Date(); -- model.passwordHistory.splice(0, 0, ph); -- } -- }); -- } -- } -- if (model.passwordHistory != null && model.passwordHistory.length === 0) { -- model.passwordHistory = null; -- } else if (model.passwordHistory != null && model.passwordHistory.length > 5) { -- // only save last 5 history -- model.passwordHistory = model.passwordHistory.slice(0, 5); -- } -- } -- -- const cipher = new Cipher(); -- cipher.id = model.id; -- cipher.folderId = model.folderId; -- cipher.favorite = model.favorite; -- cipher.organizationId = model.organizationId; -- cipher.type = model.type; -- cipher.collectionIds = model.collectionIds; -- cipher.revisionDate = model.revisionDate; -- cipher.reprompt = model.reprompt; -- -- if (key == null && cipher.organizationId != null) { -- key = await this.cryptoService.getOrgKey(cipher.organizationId); -- if (key == null) { -- throw new Error('Cannot encrypt cipher for organization. No key.'); -- } -- } -- await Promise.all([ -- this.encryptObjProperty(model, cipher, { -- name: null, -- notes: null, -- }, key), -- this.encryptCipherData(cipher, model, key), -- this.encryptFields(model.fields, key).then(fields => { -- cipher.fields = fields; -- }), -- this.encryptPasswordHistories(model.passwordHistory, key).then(ph => { -- cipher.passwordHistory = ph; -- }), -- this.encryptAttachments(model.attachments, key).then(attachments => { -- cipher.attachments = attachments; -- }), -- ]); -- -- return cipher; -+ await Promise.all([ -+ this.encryptObjProperty( -+ model, -+ cipher, -+ { -+ name: null, -+ notes: null, -+ }, -+ key -+ ), -+ this.encryptCipherData(cipher, model, key), -+ this.encryptFields(model.fields, key).then((fields) => { -+ cipher.fields = fields; -+ }), -+ this.encryptPasswordHistories(model.passwordHistory, key).then((ph) => { -+ cipher.passwordHistory = ph; -+ }), -+ this.encryptAttachments(model.attachments, key).then((attachments) => { -+ cipher.attachments = attachments; -+ }), -+ ]); -+ -+ return cipher; -+ } -+ -+ async encryptAttachments( -+ attachmentsModel: AttachmentView[], -+ key: SymmetricCryptoKey -+ ): Promise { -+ if (attachmentsModel == null || attachmentsModel.length === 0) { -+ return null; - } - -- async encryptAttachments(attachmentsModel: AttachmentView[], key: SymmetricCryptoKey): Promise { -- if (attachmentsModel == null || attachmentsModel.length === 0) { -- return null; -+ const promises: Promise[] = []; -+ const encAttachments: Attachment[] = []; -+ attachmentsModel.forEach(async (model) => { -+ const attachment = new Attachment(); -+ attachment.id = model.id; -+ attachment.size = model.size; -+ attachment.sizeName = model.sizeName; -+ attachment.url = model.url; -+ const promise = this.encryptObjProperty( -+ model, -+ attachment, -+ { -+ fileName: null, -+ }, -+ key -+ ).then(async () => { -+ if (model.key != null) { -+ attachment.key = await this.cryptoService.encrypt(model.key.key, key); - } -- -- const promises: Promise[] = []; -- const encAttachments: Attachment[] = []; -- attachmentsModel.forEach(async model => { -- const attachment = new Attachment(); -- attachment.id = model.id; -- attachment.size = model.size; -- attachment.sizeName = model.sizeName; -- attachment.url = model.url; -- const promise = this.encryptObjProperty(model, attachment, { -- fileName: null, -- }, key).then(async () => { -- if (model.key != null) { -- attachment.key = await this.cryptoService.encrypt(model.key.key, key); -- } -- encAttachments.push(attachment); -- }); -- promises.push(promise); -- }); -- -- await Promise.all(promises); -- return encAttachments; -+ encAttachments.push(attachment); -+ }); -+ promises.push(promise); -+ }); -+ -+ await Promise.all(promises); -+ return encAttachments; -+ } -+ -+ async encryptFields(fieldsModel: FieldView[], key: SymmetricCryptoKey): Promise { -+ if (!fieldsModel || !fieldsModel.length) { -+ return null; - } - -- async encryptFields(fieldsModel: FieldView[], key: SymmetricCryptoKey): Promise { -- if (!fieldsModel || !fieldsModel.length) { -- return null; -- } -- -- const self = this; -- const encFields: Field[] = []; -- await fieldsModel.reduce((promise, field) => { -- return promise.then(() => { -- return self.encryptField(field, key); -- }).then((encField: Field) => { -- encFields.push(encField); -- }); -- }, Promise.resolve()); -- -- return encFields; -+ const self = this; -+ const encFields: Field[] = []; -+ await fieldsModel.reduce(async (promise, field) => { -+ await promise; -+ const encField = await self.encryptField(field, key); -+ encFields.push(encField); -+ }, Promise.resolve()); -+ -+ return encFields; -+ } -+ -+ async encryptField(fieldModel: FieldView, key: SymmetricCryptoKey): Promise { -+ const field = new Field(); -+ field.type = fieldModel.type; -+ field.linkedId = fieldModel.linkedId; -+ // normalize boolean type field values -+ if (fieldModel.type === FieldType.Boolean && fieldModel.value !== "true") { -+ fieldModel.value = "false"; - } - -- async encryptField(fieldModel: FieldView, key: SymmetricCryptoKey): Promise { -- const field = new Field(); -- field.type = fieldModel.type; -- field.linkedId = fieldModel.linkedId; -- // normalize boolean type field values -- if (fieldModel.type === FieldType.Boolean && fieldModel.value !== 'true') { -- fieldModel.value = 'false'; -- } -- -- await this.encryptObjProperty(fieldModel, field, { -- name: null, -- value: null, -- }, key); -- -- return field; -+ await this.encryptObjProperty( -+ fieldModel, -+ field, -+ { -+ name: null, -+ value: null, -+ }, -+ key -+ ); -+ -+ return field; -+ } -+ -+ async encryptPasswordHistories( -+ phModels: PasswordHistoryView[], -+ key: SymmetricCryptoKey -+ ): Promise { -+ if (!phModels || !phModels.length) { -+ return null; - } - -- async encryptPasswordHistories(phModels: PasswordHistoryView[], key: SymmetricCryptoKey): Promise { -- if (!phModels || !phModels.length) { -- return null; -- } -- -- const self = this; -- const encPhs: Password[] = []; -- await phModels.reduce((promise, ph) => { -- return promise.then(() => { -- return self.encryptPasswordHistory(ph, key); -- }).then((encPh: Password) => { -- encPhs.push(encPh); -- }); -- }, Promise.resolve()); -- -- return encPhs; -+ const self = this; -+ const encPhs: Password[] = []; -+ await phModels.reduce(async (promise, ph) => { -+ await promise; -+ const encPh = await self.encryptPasswordHistory(ph, key); -+ encPhs.push(encPh); -+ }, Promise.resolve()); -+ -+ return encPhs; -+ } -+ -+ async encryptPasswordHistory( -+ phModel: PasswordHistoryView, -+ key: SymmetricCryptoKey -+ ): Promise { -+ const ph = new Password(); -+ ph.lastUsedDate = phModel.lastUsedDate; -+ -+ await this.encryptObjProperty( -+ phModel, -+ ph, -+ { -+ password: null, -+ }, -+ key -+ ); -+ -+ return ph; -+ } -+ -+ async get(id: string): Promise { -+ const ciphers = await this.stateService.getEncryptedCiphers(); -+ if (ciphers == null || !ciphers.hasOwnProperty(id)) { -+ return null; - } - -- async encryptPasswordHistory(phModel: PasswordHistoryView, key: SymmetricCryptoKey): Promise { -- const ph = new Password(); -- ph.lastUsedDate = phModel.lastUsedDate; -- -- await this.encryptObjProperty(phModel, ph, { -- password: null, -- }, key); -- -- return ph; -+ const localData = await this.stateService.getLocalData(); -+ return new Cipher(ciphers[id], false, localData ? localData[id] : null); -+ } -+ -+ async getAll(): Promise { -+ const localData = await this.stateService.getLocalData(); -+ const ciphers = await this.stateService.getEncryptedCiphers(); -+ const response: Cipher[] = []; -+ for (const id in ciphers) { -+ if (ciphers.hasOwnProperty(id)) { -+ response.push(new Cipher(ciphers[id], false, localData ? localData[id] : null)); -+ } -+ } -+ return response; -+ } -+ -+ @sequentialize(() => "getAllDecrypted") -+ async getAllDecrypted(): Promise { -+ const userId = await this.stateService.getUserId(); -+ if ((await this.getDecryptedCipherCache()) != null) { -+ if ( -+ this.searchService != null && -+ (this.searchService().indexedEntityId ?? userId) !== userId -+ ) { -+ await this.searchService().indexCiphers(userId, await this.getDecryptedCipherCache()); -+ } -+ return await this.getDecryptedCipherCache(); - } - -- async get(id: string): Promise { -- const userId = await this.userService.getUserId(); -- const localData = await this.storageService.get(Keys.localData); -- const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( -- Keys.ciphersPrefix + userId); -- if (ciphers == null || !ciphers.hasOwnProperty(id)) { -- return null; -- } -- -- return new Cipher(ciphers[id], false, localData ? localData[id] : null); -+ const decCiphers: CipherView[] = []; -+ const hasKey = await this.cryptoService.hasKey(); -+ if (!hasKey) { -+ throw new Error("No key."); - } - -- async getAll(): Promise { -- const userId = await this.userService.getUserId(); -- const localData = await this.storageService.get(Keys.localData); -- const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( -- Keys.ciphersPrefix + userId); -- const response: Cipher[] = []; -- for (const id in ciphers) { -- if (ciphers.hasOwnProperty(id)) { -- response.push(new Cipher(ciphers[id], false, localData ? localData[id] : null)); -- } -- } -- return response; -+ const promises: any[] = []; -+ const ciphers = await this.getAll(); -+ ciphers.forEach(async (cipher) => { -+ promises.push(cipher.decrypt().then((c) => decCiphers.push(c))); -+ }); -+ -+ await Promise.all(promises); -+ decCiphers.sort(this.getLocaleSortingFunction()); -+ await this.setDecryptedCipherCache(decCiphers); -+ return decCiphers; -+ } -+ -+ async getAllDecryptedForGrouping( -+ groupingId: string, -+ folder: boolean = true -+ ): Promise { -+ const ciphers = await this.getAllDecrypted(); -+ -+ return ciphers.filter((cipher) => { -+ if (cipher.isDeleted) { -+ return false; -+ } -+ if (folder && cipher.folderId === groupingId) { -+ return true; -+ } else if ( -+ !folder && -+ cipher.collectionIds != null && -+ cipher.collectionIds.indexOf(groupingId) > -1 -+ ) { -+ return true; -+ } -+ -+ return false; -+ }); -+ } -+ -+ async getAllDecryptedForUrl( -+ url: string, -+ includeOtherTypes?: CipherType[], -+ defaultMatch: UriMatchType = null -+ ): Promise { -+ if (url == null && includeOtherTypes == null) { -+ return Promise.resolve([]); - } - -- @sequentialize(() => 'getAllDecrypted') -- async getAllDecrypted(): Promise { -- if (this.decryptedCipherCache != null) { -- const userId = await this.userService.getUserId(); -- if (this.searchService != null && (this.searchService().indexedEntityId ?? userId) !== userId) -- { -- await this.searchService().indexCiphers(userId, this.decryptedCipherCache); -+ const domain = Utils.getDomain(url); -+ const eqDomainsPromise = -+ domain == null -+ ? Promise.resolve([]) -+ : this.settingsService.getEquivalentDomains().then((eqDomains: any[][]) => { -+ let matches: any[] = []; -+ eqDomains.forEach((eqDomain) => { -+ if (eqDomain.length && eqDomain.indexOf(domain) >= 0) { -+ matches = matches.concat(eqDomain); -+ } -+ }); -+ -+ if (!matches.length) { -+ matches.push(domain); - } -- return this.decryptedCipherCache; -- } - -- const decCiphers: CipherView[] = []; -- const hasKey = await this.cryptoService.hasKey(); -- if (!hasKey) { -- throw new Error('No key.'); -- } -+ return matches; -+ }); - -- const promises: any[] = []; -- const ciphers = await this.getAll(); -- ciphers.forEach(cipher => { -- promises.push(cipher.decrypt().then(c => decCiphers.push(c))); -- }); -+ const result = await Promise.all([eqDomainsPromise, this.getAllDecrypted()]); -+ const matchingDomains = result[0]; -+ const ciphers = result[1]; - -- await Promise.all(promises); -- decCiphers.sort(this.getLocaleSortingFunction()); -- this.decryptedCipherCache = decCiphers; -- return this.decryptedCipherCache; -+ if (defaultMatch == null) { -+ defaultMatch = await this.stateService.getDefaultUriMatch(); -+ if (defaultMatch == null) { -+ defaultMatch = UriMatchType.Domain; -+ } - } - -- async getAllDecryptedForGrouping(groupingId: string, folder: boolean = true): Promise { -- const ciphers = await this.getAllDecrypted(); -- -- return ciphers.filter(cipher => { -- if (cipher.isDeleted) { -- return false; -- } -- if (folder && cipher.folderId === groupingId) { -+ return ciphers.filter((cipher) => { -+ if (cipher.deletedDate != null) { -+ return false; -+ } -+ if (includeOtherTypes != null && includeOtherTypes.indexOf(cipher.type) > -1) { -+ return true; -+ } -+ -+ if (url != null && cipher.type === CipherType.Login && cipher.login.uris != null) { -+ for (let i = 0; i < cipher.login.uris.length; i++) { -+ const u = cipher.login.uris[i]; -+ if (u.uri == null) { -+ continue; -+ } -+ -+ const match = u.match == null ? defaultMatch : u.match; -+ switch (match) { -+ case UriMatchType.Domain: -+ if (domain != null && u.domain != null && matchingDomains.indexOf(u.domain) > -1) { -+ if (DomainMatchBlacklist.has(u.domain)) { -+ const domainUrlHost = Utils.getHost(url); -+ if (!DomainMatchBlacklist.get(u.domain).has(domainUrlHost)) { -+ return true; -+ } -+ } else { -+ return true; -+ } -+ } -+ break; -+ case UriMatchType.Host: -+ const urlHost = Utils.getHost(url); -+ if (urlHost != null && urlHost === Utils.getHost(u.uri)) { - return true; -- } else if (!folder && cipher.collectionIds != null && cipher.collectionIds.indexOf(groupingId) > -1) { -+ } -+ break; -+ case UriMatchType.Exact: -+ if (url === u.uri) { - return true; -- } -- -- return false; -- }); -- } -- -- async getAllDecryptedForUrl(url: string, includeOtherTypes?: CipherType[], -- defaultMatch: UriMatchType = null): Promise { -- if (url == null && includeOtherTypes == null) { -- return Promise.resolve([]); -- } -- -- const domain = Utils.getDomain(url); -- const eqDomainsPromise = domain == null ? Promise.resolve([]) : -- this.settingsService.getEquivalentDomains().then((eqDomains: any[][]) => { -- let matches: any[] = []; -- eqDomains.forEach(eqDomain => { -- if (eqDomain.length && eqDomain.indexOf(domain) >= 0) { -- matches = matches.concat(eqDomain); -- } -- }); -- -- if (!matches.length) { -- matches.push(domain); -- } -- -- return matches; -- }); -- -- const result = await Promise.all([eqDomainsPromise, this.getAllDecrypted()]); -- const matchingDomains = result[0]; -- const ciphers = result[1]; -- -- if (defaultMatch == null) { -- defaultMatch = await this.storageService.get(ConstantsService.defaultUriMatch); -- if (defaultMatch == null) { -- defaultMatch = UriMatchType.Domain; -- } -- } -- -- return ciphers.filter(cipher => { -- if (cipher.deletedDate != null) { -- return false; -- } -- if (includeOtherTypes != null && includeOtherTypes.indexOf(cipher.type) > -1) { -+ } -+ break; -+ case UriMatchType.StartsWith: -+ if (url.startsWith(u.uri)) { - return true; -- } -- -- if (url != null && cipher.type === CipherType.Login && cipher.login.uris != null) { -- for (let i = 0; i < cipher.login.uris.length; i++) { -- const u = cipher.login.uris[i]; -- if (u.uri == null) { -- continue; -- } -- -- const match = u.match == null ? defaultMatch : u.match; -- switch (match) { -- case UriMatchType.Domain: -- if (domain != null && u.domain != null && matchingDomains.indexOf(u.domain) > -1) { -- if (DomainMatchBlacklist.has(u.domain)) { -- const domainUrlHost = Utils.getHost(url); -- if (!DomainMatchBlacklist.get(u.domain).has(domainUrlHost)) { -- return true; -- } -- } else { -- return true; -- } -- } -- break; -- case UriMatchType.Host: -- const urlHost = Utils.getHost(url); -- if (urlHost != null && urlHost === Utils.getHost(u.uri)) { -- return true; -- } -- break; -- case UriMatchType.Exact: -- if (url === u.uri) { -- return true; -- } -- break; -- case UriMatchType.StartsWith: -- if (url.startsWith(u.uri)) { -- return true; -- } -- break; -- case UriMatchType.RegularExpression: -- try { -- const regex = new RegExp(u.uri, 'i'); -- if (regex.test(url)) { -- return true; -- } -- } catch (e) { -- this.logService.error(e); -- } -- break; -- case UriMatchType.Never: -- default: -- break; -- } -+ } -+ break; -+ case UriMatchType.RegularExpression: -+ try { -+ const regex = new RegExp(u.uri, "i"); -+ if (regex.test(url)) { -+ return true; - } -- } -- -- return false; -- }); -- } -- -- async getAllFromApiForOrganization(organizationId: string): Promise { -- const ciphers = await this.apiService.getCiphersOrganization(organizationId); -- if (ciphers != null && ciphers.data != null && ciphers.data.length) { -- const decCiphers: CipherView[] = []; -- const promises: any[] = []; -- ciphers.data.forEach(r => { -- const data = new CipherData(r); -- const cipher = new Cipher(data); -- promises.push(cipher.decrypt().then(c => decCiphers.push(c))); -- }); -- await Promise.all(promises); -- decCiphers.sort(this.getLocaleSortingFunction()); -- return decCiphers; -- } else { -- return []; -+ } catch (e) { -+ this.logService.error(e); -+ } -+ break; -+ case UriMatchType.Never: -+ default: -+ break; -+ } - } -+ } -+ -+ return false; -+ }); -+ } -+ -+ async getAllFromApiForOrganization(organizationId: string): Promise { -+ const ciphers = await this.apiService.getCiphersOrganization(organizationId); -+ if (ciphers != null && ciphers.data != null && ciphers.data.length) { -+ const decCiphers: CipherView[] = []; -+ const promises: any[] = []; -+ ciphers.data.forEach((r) => { -+ const data = new CipherData(r); -+ const cipher = new Cipher(data); -+ promises.push(cipher.decrypt().then((c) => decCiphers.push(c))); -+ }); -+ await Promise.all(promises); -+ decCiphers.sort(this.getLocaleSortingFunction()); -+ return decCiphers; -+ } else { -+ return []; - } -- -- async getLastUsedForUrl(url: string, autofillOnPageLoad: boolean = false): Promise { -- return this.getCipherForUrl(url, true, false, autofillOnPageLoad); -+ } -+ -+ async getLastUsedForUrl(url: string, autofillOnPageLoad: boolean = false): Promise { -+ return this.getCipherForUrl(url, true, false, autofillOnPageLoad); -+ } -+ -+ async getLastLaunchedForUrl( -+ url: string, -+ autofillOnPageLoad: boolean = false -+ ): Promise { -+ return this.getCipherForUrl(url, false, true, autofillOnPageLoad); -+ } -+ -+ async getNextCipherForUrl(url: string): Promise { -+ return this.getCipherForUrl(url, false, false, false); -+ } -+ -+ updateLastUsedIndexForUrl(url: string) { -+ this.sortedCiphersCache.updateLastUsedIndex(url); -+ } -+ -+ async updateLastUsedDate(id: string): Promise { -+ let ciphersLocalData = await this.stateService.getLocalData(); -+ if (!ciphersLocalData) { -+ ciphersLocalData = {}; - } - -- async getLastLaunchedForUrl(url: string, autofillOnPageLoad: boolean = false): Promise { -- return this.getCipherForUrl(url, false, true, autofillOnPageLoad); -+ if (ciphersLocalData[id]) { -+ ciphersLocalData[id].lastUsedDate = new Date().getTime(); -+ } else { -+ ciphersLocalData[id] = { -+ lastUsedDate: new Date().getTime(), -+ }; - } - -- async getNextCipherForUrl(url: string): Promise { -- return this.getCipherForUrl(url, false, false, false); -- } -+ await this.stateService.setLocalData(ciphersLocalData); - -- updateLastUsedIndexForUrl(url: string) { -- this.sortedCiphersCache.updateLastUsedIndex(url); -+ const decryptedCipherCache = await this.stateService.getDecryptedCiphers(); -+ if (!decryptedCipherCache) { -+ return; - } - -- async updateLastUsedDate(id: string): Promise { -- let ciphersLocalData = await this.storageService.get(Keys.localData); -- if (!ciphersLocalData) { -- ciphersLocalData = {}; -- } -- -- if (ciphersLocalData[id]) { -- ciphersLocalData[id].lastUsedDate = new Date().getTime(); -- } else { -- ciphersLocalData[id] = { -- lastUsedDate: new Date().getTime(), -- }; -- } -- -- await this.storageService.save(Keys.localData, ciphersLocalData); -- -- if (this.decryptedCipherCache == null) { -- return; -- } -- -- for (let i = 0; i < this.decryptedCipherCache.length; i++) { -- const cached = this.decryptedCipherCache[i]; -- if (cached.id === id) { -- cached.localData = ciphersLocalData[id]; -- break; -- } -- } -+ for (let i = 0; i < decryptedCipherCache.length; i++) { -+ const cached = decryptedCipherCache[i]; -+ if (cached.id === id) { -+ cached.localData = ciphersLocalData[id]; -+ break; -+ } - } -+ await this.stateService.setDecryptedCiphers(decryptedCipherCache); -+ } - -- async updateLastLaunchedDate(id: string): Promise { -- let ciphersLocalData = await this.storageService.get(Keys.localData); -- if (!ciphersLocalData) { -- ciphersLocalData = {}; -- } -- -- if (ciphersLocalData[id]) { -- ciphersLocalData[id].lastLaunched = new Date().getTime(); -- } else { -- ciphersLocalData[id] = { -- lastUsedDate: new Date().getTime(), -- }; -- } -- -- await this.storageService.save(Keys.localData, ciphersLocalData); -- -- if (this.decryptedCipherCache == null) { -- return; -- } -- -- for (let i = 0; i < this.decryptedCipherCache.length; i++) { -- const cached = this.decryptedCipherCache[i]; -- if (cached.id === id) { -- cached.localData = ciphersLocalData[id]; -- break; -- } -- } -+ async updateLastLaunchedDate(id: string): Promise { -+ let ciphersLocalData = await this.stateService.getLocalData(); -+ if (!ciphersLocalData) { -+ ciphersLocalData = {}; - } - -- async saveNeverDomain(domain: string): Promise { -- if (domain == null) { -- return; -- } -- -- let domains = await this.storageService.get<{ [id: string]: any; }>(Keys.neverDomains); -- if (!domains) { -- domains = {}; -- } -- domains[domain] = null; -- await this.storageService.save(Keys.neverDomains, domains); -+ if (ciphersLocalData[id]) { -+ ciphersLocalData[id].lastLaunched = new Date().getTime(); -+ } else { -+ ciphersLocalData[id] = { -+ lastUsedDate: new Date().getTime(), -+ }; - } - -- async saveWithServer(cipher: Cipher): Promise { -- let response: CipherResponse; -- if (cipher.id == null) { -- if (cipher.collectionIds != null) { -- const request = new CipherCreateRequest(cipher); -- response = await this.apiService.postCipherCreate(request); -- } else { -- const request = new CipherRequest(cipher); -- response = await this.apiService.postCipher(request); -- } -- cipher.id = response.id; -- } else { -- const request = new CipherRequest(cipher); -- response = await this.apiService.putCipher(cipher.id, request); -- } -+ await this.stateService.setLocalData(ciphersLocalData); - -- const userId = await this.userService.getUserId(); -- const data = new CipherData(response, userId, cipher.collectionIds); -- await this.upsert(data); -+ const decryptedCipherCache = await this.stateService.getDecryptedCiphers(); -+ if (!decryptedCipherCache) { -+ return; - } - -- async shareWithServer(cipher: CipherView, organizationId: string, collectionIds: string[]): Promise { -- const attachmentPromises: Promise[] = []; -- if (cipher.attachments != null) { -- cipher.attachments.forEach(attachment => { -- if (attachment.key == null) { -- attachmentPromises.push(this.shareAttachmentWithServer(attachment, cipher.id, organizationId)); -- } -- }); -- } -- await Promise.all(attachmentPromises); -- -- cipher.organizationId = organizationId; -- cipher.collectionIds = collectionIds; -- const encCipher = await this.encrypt(cipher); -- const request = new CipherShareRequest(encCipher); -- const response = await this.apiService.putShareCipher(cipher.id, request); -- const userId = await this.userService.getUserId(); -- const data = new CipherData(response, userId, collectionIds); -- await this.upsert(data); -+ for (let i = 0; i < decryptedCipherCache.length; i++) { -+ const cached = decryptedCipherCache[i]; -+ if (cached.id === id) { -+ cached.localData = ciphersLocalData[id]; -+ break; -+ } - } -+ await this.stateService.setDecryptedCiphers(decryptedCipherCache); -+ } - -- async shareManyWithServer(ciphers: CipherView[], organizationId: string, collectionIds: string[]): Promise { -- const promises: Promise[] = []; -- const encCiphers: Cipher[] = []; -- for (const cipher of ciphers) { -- cipher.organizationId = organizationId; -- cipher.collectionIds = collectionIds; -- promises.push(this.encrypt(cipher).then(c => { -- encCiphers.push(c); -- })); -- } -- await Promise.all(promises); -- const request = new CipherBulkShareRequest(encCiphers, collectionIds); -- await this.apiService.putShareCiphers(request); -- const userId = await this.userService.getUserId(); -- await this.upsert(encCiphers.map(c => c.toCipherData(userId))); -+ async saveNeverDomain(domain: string): Promise { -+ if (domain == null) { -+ return; - } - -- saveAttachmentWithServer(cipher: Cipher, unencryptedFile: any, admin = false): Promise { -- return new Promise((resolve, reject) => { -- const reader = new FileReader(); -- reader.readAsArrayBuffer(unencryptedFile); -- reader.onload = async (evt: any) => { -- try { -- const cData = await this.saveAttachmentRawWithServer(cipher, -- unencryptedFile.name, evt.target.result, admin); -- resolve(cData); -- } catch (e) { -- reject(e); -- } -- }; -- reader.onerror = evt => { -- reject('Error reading file.'); -- }; -- }); -+ let domains = await this.stateService.getNeverDomains(); -+ if (!domains) { -+ domains = {}; - } -- -- async saveAttachmentRawWithServer(cipher: Cipher, filename: string, -- data: ArrayBuffer, admin = false): Promise { -- const key = await this.cryptoService.getOrgKey(cipher.organizationId); -- const encFileName = await this.cryptoService.encrypt(filename, key); -- -- const dataEncKey = await this.cryptoService.makeEncKey(key); -- const encData = await this.cryptoService.encryptToBytes(data, dataEncKey[0]); -- -- const request: AttachmentRequest = { -- key: dataEncKey[1].encryptedString, -- fileName: encFileName.encryptedString, -- fileSize: encData.buffer.byteLength, -- adminRequest: admin, -- }; -- -- let response: CipherResponse; -- try { -- const uploadDataResponse = await this.apiService.postCipherAttachment(cipher.id, request); -- response = admin ? uploadDataResponse.cipherMiniResponse : uploadDataResponse.cipherResponse; -- await this.fileUploadService.uploadCipherAttachment(admin, uploadDataResponse, encFileName, encData); -- } catch (e) { -- if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404 || (e as ErrorResponse).statusCode === 405) { -- response = await this.legacyServerAttachmentFileUpload(admin, cipher.id, encFileName, encData, dataEncKey[1]); -- } else if (e instanceof ErrorResponse) { -- throw new Error((e as ErrorResponse).getSingleMessage()); -- } else { -- throw e; -- } -- } -- -- const userId = await this.userService.getUserId(); -- const cData = new CipherData(response, userId, cipher.collectionIds); -- if (!admin) { -- await this.upsert(cData); -- } -- return new Cipher(cData); -+ domains[domain] = null; -+ await this.stateService.setNeverDomains(domains); -+ } -+ -+ async saveWithServer(cipher: Cipher): Promise { -+ let response: CipherResponse; -+ if (cipher.id == null) { -+ if (cipher.collectionIds != null) { -+ const request = new CipherCreateRequest(cipher); -+ response = await this.apiService.postCipherCreate(request); -+ } else { -+ const request = new CipherRequest(cipher); -+ response = await this.apiService.postCipher(request); -+ } -+ cipher.id = response.id; -+ } else { -+ const request = new CipherRequest(cipher); -+ response = await this.apiService.putCipher(cipher.id, request); - } - -- /** -- * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. -- * This method still exists for backward compatibility with old server versions. -- */ -- async legacyServerAttachmentFileUpload(admin: boolean, cipherId: string, encFileName: EncString, -- encData: EncArrayBuffer, key: EncString) { -- const fd = new FormData(); -- try { -- const blob = new Blob([encData.buffer], { type: 'application/octet-stream' }); -- fd.append('key', key.encryptedString); -- fd.append('data', blob, encFileName.encryptedString); -- } catch (e) { -- if (Utils.isNode && !Utils.isBrowser) { -- fd.append('key', key.encryptedString); -- fd.append('data', Buffer.from(encData.buffer) as any, { -- filepath: encFileName.encryptedString, -- contentType: 'application/octet-stream', -- } as any); -- } else { -- throw e; -- } -+ const data = new CipherData( -+ response, -+ await this.stateService.getUserId(), -+ cipher.collectionIds -+ ); -+ await this.upsert(data); -+ } -+ -+ async shareWithServer( -+ cipher: CipherView, -+ organizationId: string, -+ collectionIds: string[] -+ ): Promise { -+ const attachmentPromises: Promise[] = []; -+ if (cipher.attachments != null) { -+ cipher.attachments.forEach((attachment) => { -+ if (attachment.key == null) { -+ attachmentPromises.push( -+ this.shareAttachmentWithServer(attachment, cipher.id, organizationId) -+ ); - } -- -- let response: CipherResponse; -+ }); -+ } -+ await Promise.all(attachmentPromises); -+ -+ cipher.organizationId = organizationId; -+ cipher.collectionIds = collectionIds; -+ const encCipher = await this.encrypt(cipher); -+ const request = new CipherShareRequest(encCipher); -+ const response = await this.apiService.putShareCipher(cipher.id, request); -+ const data = new CipherData(response, await this.stateService.getUserId(), collectionIds); -+ await this.upsert(data); -+ } -+ -+ async shareManyWithServer( -+ ciphers: CipherView[], -+ organizationId: string, -+ collectionIds: string[] -+ ): Promise { -+ const promises: Promise[] = []; -+ const encCiphers: Cipher[] = []; -+ for (const cipher of ciphers) { -+ cipher.organizationId = organizationId; -+ cipher.collectionIds = collectionIds; -+ promises.push( -+ this.encrypt(cipher).then((c) => { -+ encCiphers.push(c); -+ }) -+ ); -+ } -+ await Promise.all(promises); -+ const request = new CipherBulkShareRequest(encCiphers, collectionIds); -+ await this.apiService.putShareCiphers(request); -+ const userId = await this.stateService.getUserId(); -+ await this.upsert(encCiphers.map((c) => c.toCipherData(userId))); -+ } -+ -+ saveAttachmentWithServer(cipher: Cipher, unencryptedFile: any, admin = false): Promise { -+ return new Promise((resolve, reject) => { -+ const reader = new FileReader(); -+ reader.readAsArrayBuffer(unencryptedFile); -+ reader.onload = async (evt: any) => { - try { -- if (admin) { -- response = await this.apiService.postCipherAttachmentAdminLegacy(cipherId, fd); -- } else { -- response = await this.apiService.postCipherAttachmentLegacy(cipherId, fd); -- } -+ const cData = await this.saveAttachmentRawWithServer( -+ cipher, -+ unencryptedFile.name, -+ evt.target.result, -+ admin -+ ); -+ resolve(cData); - } catch (e) { -- throw new Error((e as ErrorResponse).getSingleMessage()); -+ reject(e); - } -+ }; -+ reader.onerror = (_evt) => { -+ reject("Error reading file."); -+ }; -+ }); -+ } -+ -+ async saveAttachmentRawWithServer( -+ cipher: Cipher, -+ filename: string, -+ data: ArrayBuffer, -+ admin = false -+ ): Promise { -+ const key = await this.cryptoService.getOrgKey(cipher.organizationId); -+ const encFileName = await this.cryptoService.encrypt(filename, key); -+ -+ const dataEncKey = await this.cryptoService.makeEncKey(key); -+ const encData = await this.cryptoService.encryptToBytes(data, dataEncKey[0]); -+ -+ const request: AttachmentRequest = { -+ key: dataEncKey[1].encryptedString, -+ fileName: encFileName.encryptedString, -+ fileSize: encData.buffer.byteLength, -+ adminRequest: admin, -+ }; -+ -+ let response: CipherResponse; -+ try { -+ const uploadDataResponse = await this.apiService.postCipherAttachment(cipher.id, request); -+ response = admin ? uploadDataResponse.cipherMiniResponse : uploadDataResponse.cipherResponse; -+ await this.fileUploadService.uploadCipherAttachment( -+ admin, -+ uploadDataResponse, -+ encFileName, -+ encData -+ ); -+ } catch (e) { -+ if ( -+ (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) || -+ (e as ErrorResponse).statusCode === 405 -+ ) { -+ response = await this.legacyServerAttachmentFileUpload( -+ admin, -+ cipher.id, -+ encFileName, -+ encData, -+ dataEncKey[1] -+ ); -+ } else if (e instanceof ErrorResponse) { -+ throw new Error((e as ErrorResponse).getSingleMessage()); -+ } else { -+ throw e; -+ } -+ } - -- return response; -+ const cData = new CipherData( -+ response, -+ await this.stateService.getUserId(), -+ cipher.collectionIds -+ ); -+ if (!admin) { -+ await this.upsert(cData); -+ } -+ return new Cipher(cData); -+ } -+ -+ /** -+ * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. -+ * This method still exists for backward compatibility with old server versions. -+ */ -+ async legacyServerAttachmentFileUpload( -+ admin: boolean, -+ cipherId: string, -+ encFileName: EncString, -+ encData: EncArrayBuffer, -+ key: EncString -+ ) { -+ const fd = new FormData(); -+ try { -+ const blob = new Blob([encData.buffer], { type: "application/octet-stream" }); -+ fd.append("key", key.encryptedString); -+ fd.append("data", blob, encFileName.encryptedString); -+ } catch (e) { -+ if (Utils.isNode && !Utils.isBrowser) { -+ fd.append("key", key.encryptedString); -+ fd.append( -+ "data", -+ Buffer.from(encData.buffer) as any, -+ { -+ filepath: encFileName.encryptedString, -+ contentType: "application/octet-stream", -+ } as any -+ ); -+ } else { -+ throw e; -+ } - } - -- async saveCollectionsWithServer(cipher: Cipher): Promise { -- const request = new CipherCollectionsRequest(cipher.collectionIds); -- await this.apiService.putCipherCollections(cipher.id, request); -- const userId = await this.userService.getUserId(); -- const data = cipher.toCipherData(userId); -- await this.upsert(data); -+ let response: CipherResponse; -+ try { -+ if (admin) { -+ response = await this.apiService.postCipherAttachmentAdminLegacy(cipherId, fd); -+ } else { -+ response = await this.apiService.postCipherAttachmentLegacy(cipherId, fd); -+ } -+ } catch (e) { -+ throw new Error((e as ErrorResponse).getSingleMessage()); - } - -- async upsert(cipher: CipherData | CipherData[]): Promise { -- const userId = await this.userService.getUserId(); -- let ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( -- Keys.ciphersPrefix + userId); -- if (ciphers == null) { -- ciphers = {}; -- } -+ return response; -+ } - -- if (cipher instanceof CipherData) { -- const c = cipher as CipherData; -- ciphers[c.id] = c; -- } else { -- (cipher as CipherData[]).forEach(c => { -- ciphers[c.id] = c; -- }); -- } -+ async saveCollectionsWithServer(cipher: Cipher): Promise { -+ const request = new CipherCollectionsRequest(cipher.collectionIds); -+ await this.apiService.putCipherCollections(cipher.id, request); -+ const data = cipher.toCipherData(await this.stateService.getUserId()); -+ await this.upsert(data); -+ } - -- await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); -- this.decryptedCipherCache = null; -+ async upsert(cipher: CipherData | CipherData[]): Promise { -+ let ciphers = await this.stateService.getEncryptedCiphers(); -+ if (ciphers == null) { -+ ciphers = {}; - } - -- async replace(ciphers: { [id: string]: CipherData; }): Promise { -- const userId = await this.userService.getUserId(); -- await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); -- this.decryptedCipherCache = null; -+ if (cipher instanceof CipherData) { -+ const c = cipher as CipherData; -+ ciphers[c.id] = c; -+ } else { -+ (cipher as CipherData[]).forEach((c) => { -+ ciphers[c.id] = c; -+ }); - } - -- async clear(userId: string): Promise { -- await this.storageService.remove(Keys.ciphersPrefix + userId); -- this.clearCache(); -- } -+ await this.replace(ciphers); -+ } - -- async moveManyWithServer(ids: string[], folderId: string): Promise { -- await this.apiService.putMoveCiphers(new CipherBulkMoveRequest(ids, folderId)); -+ async replace(ciphers: { [id: string]: CipherData }): Promise { -+ await this.clearDecryptedCiphersState(); -+ await this.stateService.setEncryptedCiphers(ciphers); -+ } - -- const userId = await this.userService.getUserId(); -- let ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( -- Keys.ciphersPrefix + userId); -- if (ciphers == null) { -- ciphers = {}; -- } -+ async clear(userId?: string): Promise { -+ await this.clearEncryptedCiphersState(userId); -+ await this.clearCache(userId); -+ } - -- ids.forEach(id => { -- if (ciphers.hasOwnProperty(id)) { -- ciphers[id].folderId = folderId; -- } -- }); -+ async moveManyWithServer(ids: string[], folderId: string): Promise { -+ await this.apiService.putMoveCiphers(new CipherBulkMoveRequest(ids, folderId)); - -- await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); -- this.decryptedCipherCache = null; -+ let ciphers = await this.stateService.getEncryptedCiphers(); -+ if (ciphers == null) { -+ ciphers = {}; - } - -- async delete(id: string | string[]): Promise { -- const userId = await this.userService.getUserId(); -- const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( -- Keys.ciphersPrefix + userId); -- if (ciphers == null) { -- return; -- } -+ ids.forEach((id) => { -+ if (ciphers.hasOwnProperty(id)) { -+ ciphers[id].folderId = folderId; -+ } -+ }); - -- if (typeof id === 'string') { -- if (ciphers[id] == null) { -- return; -- } -- delete ciphers[id]; -- } else { -- (id as string[]).forEach(i => { -- delete ciphers[i]; -- }); -- } -+ await this.clearCache(); -+ await this.stateService.setEncryptedCiphers(ciphers); -+ } - -- await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); -- this.decryptedCipherCache = null; -+ async delete(id: string | string[]): Promise { -+ const ciphers = await this.stateService.getEncryptedCiphers(); -+ if (ciphers == null) { -+ return; - } - -- async deleteWithServer(id: string): Promise { -- await this.apiService.deleteCipher(id); -- await this.delete(id); -+ if (typeof id === "string") { -+ if (ciphers[id] == null) { -+ return; -+ } -+ delete ciphers[id]; -+ } else { -+ (id as string[]).forEach((i) => { -+ delete ciphers[i]; -+ }); - } - -- async deleteManyWithServer(ids: string[]): Promise { -- await this.apiService.deleteManyCiphers(new CipherBulkDeleteRequest(ids)); -- await this.delete(ids); -- } -+ await this.clearCache(); -+ await this.stateService.setEncryptedCiphers(ciphers); -+ } - -- async deleteAttachment(id: string, attachmentId: string): Promise { -- const userId = await this.userService.getUserId(); -- const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( -- Keys.ciphersPrefix + userId); -+ async deleteWithServer(id: string): Promise { -+ await this.apiService.deleteCipher(id); -+ await this.delete(id); -+ } - -- if (ciphers == null || !ciphers.hasOwnProperty(id) || ciphers[id].attachments == null) { -- return; -- } -+ async deleteManyWithServer(ids: string[]): Promise { -+ await this.apiService.deleteManyCiphers(new CipherBulkDeleteRequest(ids)); -+ await this.delete(ids); -+ } - -- for (let i = 0; i < ciphers[id].attachments.length; i++) { -- if (ciphers[id].attachments[i].id === attachmentId) { -- ciphers[id].attachments.splice(i, 1); -- } -- } -+ async deleteAttachment(id: string, attachmentId: string): Promise { -+ const ciphers = await this.stateService.getEncryptedCiphers(); - -- await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); -- this.decryptedCipherCache = null; -+ if (ciphers == null || !ciphers.hasOwnProperty(id) || ciphers[id].attachments == null) { -+ return; - } - -- async deleteAttachmentWithServer(id: string, attachmentId: string): Promise { -- try { -- await this.apiService.deleteCipherAttachment(id, attachmentId); -- } catch (e) { -- return Promise.reject((e as ErrorResponse).getSingleMessage()); -- } -- await this.deleteAttachment(id, attachmentId); -+ for (let i = 0; i < ciphers[id].attachments.length; i++) { -+ if (ciphers[id].attachments[i].id === attachmentId) { -+ ciphers[id].attachments.splice(i, 1); -+ } - } - -- sortCiphersByLastUsed(a: CipherView, b: CipherView): number { -- const aLastUsed = a.localData && a.localData.lastUsedDate ? a.localData.lastUsedDate as number : null; -- const bLastUsed = b.localData && b.localData.lastUsedDate ? b.localData.lastUsedDate as number : null; -- -- const bothNotNull = aLastUsed != null && bLastUsed != null; -- if (bothNotNull && aLastUsed < bLastUsed) { -- return 1; -- } -- if (aLastUsed != null && bLastUsed == null) { -- return -1; -- } -+ await this.clearCache(); -+ await this.stateService.setEncryptedCiphers(ciphers); -+ } - -- if (bothNotNull && aLastUsed > bLastUsed) { -- return -1; -- } -- if (bLastUsed != null && aLastUsed == null) { -- return 1; -- } -- -- return 0; -+ async deleteAttachmentWithServer(id: string, attachmentId: string): Promise { -+ try { -+ await this.apiService.deleteCipherAttachment(id, attachmentId); -+ } catch (e) { -+ return Promise.reject((e as ErrorResponse).getSingleMessage()); - } -- -- sortCiphersByLastUsedThenName(a: CipherView, b: CipherView): number { -- const result = this.sortCiphersByLastUsed(a, b); -- if (result !== 0) { -- return result; -- } -- -- return this.getLocaleSortingFunction()(a, b); -+ await this.deleteAttachment(id, attachmentId); -+ } -+ -+ sortCiphersByLastUsed(a: CipherView, b: CipherView): number { -+ const aLastUsed = -+ a.localData && a.localData.lastUsedDate ? (a.localData.lastUsedDate as number) : null; -+ const bLastUsed = -+ b.localData && b.localData.lastUsedDate ? (b.localData.lastUsedDate as number) : null; -+ -+ const bothNotNull = aLastUsed != null && bLastUsed != null; -+ if (bothNotNull && aLastUsed < bLastUsed) { -+ return 1; - } -- -- getLocaleSortingFunction(): (a: CipherView, b: CipherView) => number { -- return (a, b) => { -- let aName = a.name; -- let bName = b.name; -- -- if (aName == null && bName != null) { -- return -1; -- } -- if (aName != null && bName == null) { -- return 1; -- } -- if (aName == null && bName == null) { -- return 0; -- } -- -- const result = this.i18nService.collator ? this.i18nService.collator.compare(aName, bName) : -- aName.localeCompare(bName); -- -- if (result !== 0 || a.type !== CipherType.Login || b.type !== CipherType.Login) { -- return result; -- } -- -- if (a.login.username != null) { -- aName += a.login.username; -- } -- -- if (b.login.username != null) { -- bName += b.login.username; -- } -- -- return this.i18nService.collator ? this.i18nService.collator.compare(aName, bName) : -- aName.localeCompare(bName); -- }; -+ if (aLastUsed != null && bLastUsed == null) { -+ return -1; - } - -- async softDelete(id: string | string[]): Promise { -- const userId = await this.userService.getUserId(); -- const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( -- Keys.ciphersPrefix + userId); -- if (ciphers == null) { -- return; -- } -- -- const setDeletedDate = (cipherId: string) => { -- if (ciphers[cipherId] == null) { -- return; -- } -- ciphers[cipherId].deletedDate = new Date().toISOString(); -- }; -+ if (bothNotNull && aLastUsed > bLastUsed) { -+ return -1; -+ } -+ if (bLastUsed != null && aLastUsed == null) { -+ return 1; -+ } - -- if (typeof id === 'string') { -- setDeletedDate(id); -- } else { -- (id as string[]).forEach(setDeletedDate); -- } -+ return 0; -+ } - -- await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); -- this.decryptedCipherCache = null; -+ sortCiphersByLastUsedThenName(a: CipherView, b: CipherView): number { -+ const result = this.sortCiphersByLastUsed(a, b); -+ if (result !== 0) { -+ return result; - } - -- async softDeleteWithServer(id: string): Promise { -- await this.apiService.putDeleteCipher(id); -- await this.softDelete(id); -+ return this.getLocaleSortingFunction()(a, b); -+ } -+ -+ getLocaleSortingFunction(): (a: CipherView, b: CipherView) => number { -+ return (a, b) => { -+ let aName = a.name; -+ let bName = b.name; -+ -+ if (aName == null && bName != null) { -+ return -1; -+ } -+ if (aName != null && bName == null) { -+ return 1; -+ } -+ if (aName == null && bName == null) { -+ return 0; -+ } -+ -+ const result = this.i18nService.collator -+ ? this.i18nService.collator.compare(aName, bName) -+ : aName.localeCompare(bName); -+ -+ if (result !== 0 || a.type !== CipherType.Login || b.type !== CipherType.Login) { -+ return result; -+ } -+ -+ if (a.login.username != null) { -+ aName += a.login.username; -+ } -+ -+ if (b.login.username != null) { -+ bName += b.login.username; -+ } -+ -+ return this.i18nService.collator -+ ? this.i18nService.collator.compare(aName, bName) -+ : aName.localeCompare(bName); -+ }; -+ } -+ -+ async softDelete(id: string | string[]): Promise { -+ const ciphers = await this.stateService.getEncryptedCiphers(); -+ if (ciphers == null) { -+ return; - } - -- async softDeleteManyWithServer(ids: string[]): Promise { -- await this.apiService.putDeleteManyCiphers(new CipherBulkDeleteRequest(ids)); -- await this.softDelete(ids); -+ const setDeletedDate = (cipherId: string) => { -+ if (ciphers[cipherId] == null) { -+ return; -+ } -+ ciphers[cipherId].deletedDate = new Date().toISOString(); -+ }; -+ -+ if (typeof id === "string") { -+ setDeletedDate(id); -+ } else { -+ (id as string[]).forEach(setDeletedDate); - } - -- async restore(cipher: { id: string, revisionDate: string; } | { id: string, revisionDate: string; }[]) { -- const userId = await this.userService.getUserId(); -- const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( -- Keys.ciphersPrefix + userId); -- if (ciphers == null) { -- return; -- } -+ await this.clearCache(); -+ await this.stateService.setEncryptedCiphers(ciphers); -+ } -+ -+ async softDeleteWithServer(id: string): Promise { -+ await this.apiService.putDeleteCipher(id); -+ await this.softDelete(id); -+ } -+ -+ async softDeleteManyWithServer(ids: string[]): Promise { -+ await this.apiService.putDeleteManyCiphers(new CipherBulkDeleteRequest(ids)); -+ await this.softDelete(ids); -+ } -+ -+ async restore( -+ cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[] -+ ) { -+ const ciphers = await this.stateService.getEncryptedCiphers(); -+ if (ciphers == null) { -+ return; -+ } - -- const clearDeletedDate = (c: { id: string, revisionDate: string; }) => { -- if (ciphers[c.id] == null) { -- return; -- } -- ciphers[c.id].deletedDate = null; -- ciphers[c.id].revisionDate = c.revisionDate; -- }; -+ const clearDeletedDate = (c: { id: string; revisionDate: string }) => { -+ if (ciphers[c.id] == null) { -+ return; -+ } -+ ciphers[c.id].deletedDate = null; -+ ciphers[c.id].revisionDate = c.revisionDate; -+ }; -+ -+ if (cipher.constructor.name === "Array") { -+ (cipher as { id: string; revisionDate: string }[]).forEach(clearDeletedDate); -+ } else { -+ clearDeletedDate(cipher as { id: string; revisionDate: string }); -+ } - -+ await this.clearCache(); -+ await this.stateService.setEncryptedCiphers(ciphers); -+ } - -- if (cipher.constructor.name === 'Array') { -- (cipher as { id: string, revisionDate: string; }[]).forEach(clearDeletedDate); -- } else { -- clearDeletedDate(cipher as { id: string, revisionDate: string; }); -- } -+ async restoreWithServer(id: string): Promise { -+ const response = await this.apiService.putRestoreCipher(id); -+ await this.restore({ id: id, revisionDate: response.revisionDate }); -+ } - -- await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); -- this.decryptedCipherCache = null; -+ async restoreManyWithServer(ids: string[]): Promise { -+ const response = await this.apiService.putRestoreManyCiphers(new CipherBulkRestoreRequest(ids)); -+ const restores: { id: string; revisionDate: string }[] = []; -+ for (const cipher of response.data) { -+ restores.push({ id: cipher.id, revisionDate: cipher.revisionDate }); - } -- -- async restoreWithServer(id: string): Promise { -- const response = await this.apiService.putRestoreCipher(id); -- await this.restore({ id: id, revisionDate: response.revisionDate }); -+ await this.restore(restores); -+ } -+ -+ // Helpers -+ -+ private async shareAttachmentWithServer( -+ attachmentView: AttachmentView, -+ cipherId: string, -+ organizationId: string -+ ): Promise { -+ const attachmentResponse = await this.apiService.nativeFetch( -+ new Request(attachmentView.url, { cache: "no-store" }) -+ ); -+ if (attachmentResponse.status !== 200) { -+ throw Error("Failed to download attachment: " + attachmentResponse.status.toString()); - } - -- async restoreManyWithServer(ids: string[]): Promise { -- const response = await this.apiService.putRestoreManyCiphers(new CipherBulkRestoreRequest(ids)); -- const restores: { id: string, revisionDate: string; }[] = []; -- for (const cipher of response.data) { -- restores.push({ id: cipher.id, revisionDate: cipher.revisionDate }); -- } -- await this.restore(restores); -+ const buf = await attachmentResponse.arrayBuffer(); -+ const decBuf = await this.cryptoService.decryptFromBytes(buf, null); -+ const key = await this.cryptoService.getOrgKey(organizationId); -+ const encFileName = await this.cryptoService.encrypt(attachmentView.fileName, key); -+ -+ const dataEncKey = await this.cryptoService.makeEncKey(key); -+ const encData = await this.cryptoService.encryptToBytes(decBuf, dataEncKey[0]); -+ -+ const fd = new FormData(); -+ try { -+ const blob = new Blob([encData.buffer], { type: "application/octet-stream" }); -+ fd.append("key", dataEncKey[1].encryptedString); -+ fd.append("data", blob, encFileName.encryptedString); -+ } catch (e) { -+ if (Utils.isNode && !Utils.isBrowser) { -+ fd.append("key", dataEncKey[1].encryptedString); -+ fd.append( -+ "data", -+ Buffer.from(encData.buffer) as any, -+ { -+ filepath: encFileName.encryptedString, -+ contentType: "application/octet-stream", -+ } as any -+ ); -+ } else { -+ throw e; -+ } - } - -- // Helpers -- -- private async shareAttachmentWithServer(attachmentView: AttachmentView, cipherId: string, -- organizationId: string): Promise { -- const attachmentResponse = await this.apiService.nativeFetch( -- new Request(attachmentView.url, { cache: 'no-store' })); -- if (attachmentResponse.status !== 200) { -- throw Error('Failed to download attachment: ' + attachmentResponse.status.toString()); -- } -- -- const buf = await attachmentResponse.arrayBuffer(); -- const decBuf = await this.cryptoService.decryptFromBytes(buf, null); -- const key = await this.cryptoService.getOrgKey(organizationId); -- const encFileName = await this.cryptoService.encrypt(attachmentView.fileName, key); -- -- const dataEncKey = await this.cryptoService.makeEncKey(key); -- const encData = await this.cryptoService.encryptToBytes(decBuf, dataEncKey[0]); -- -- const fd = new FormData(); -- try { -- const blob = new Blob([encData.buffer], { type: 'application/octet-stream' }); -- fd.append('key', dataEncKey[1].encryptedString); -- fd.append('data', blob, encFileName.encryptedString); -- } catch (e) { -- if (Utils.isNode && !Utils.isBrowser) { -- fd.append('key', dataEncKey[1].encryptedString); -- fd.append('data', Buffer.from(encData.buffer) as any, { -- filepath: encFileName.encryptedString, -- contentType: 'application/octet-stream', -- } as any); -- } else { -- throw e; -+ try { -+ await this.apiService.postShareCipherAttachment( -+ cipherId, -+ attachmentView.id, -+ fd, -+ organizationId -+ ); -+ } catch (e) { -+ throw new Error((e as ErrorResponse).getSingleMessage()); -+ } -+ } -+ -+ private async encryptObjProperty( -+ model: V, -+ obj: D, -+ map: any, -+ key: SymmetricCryptoKey -+ ): Promise { -+ const promises = []; -+ const self = this; -+ -+ for (const prop in map) { -+ if (!map.hasOwnProperty(prop)) { -+ continue; -+ } -+ -+ // tslint:disable-next-line -+ (function (theProp, theObj) { -+ const p = Promise.resolve() -+ .then(() => { -+ const modelProp = (model as any)[map[theProp] || theProp]; -+ if (modelProp && modelProp !== "") { -+ return self.cryptoService.encrypt(modelProp, key); - } -- } -+ return null; -+ }) -+ .then((val: EncString) => { -+ (theObj as any)[theProp] = val; -+ }); -+ promises.push(p); -+ })(prop, obj); -+ } - -- try { -- await this.apiService.postShareCipherAttachment(cipherId, attachmentView.id, fd, organizationId); -- } catch (e) { -- throw new Error((e as ErrorResponse).getSingleMessage()); -+ await Promise.all(promises); -+ } -+ -+ private async encryptCipherData(cipher: Cipher, model: CipherView, key: SymmetricCryptoKey) { -+ switch (cipher.type) { -+ case CipherType.Login: -+ cipher.login = new Login(); -+ cipher.login.passwordRevisionDate = model.login.passwordRevisionDate; -+ cipher.login.autofillOnPageLoad = model.login.autofillOnPageLoad; -+ await this.encryptObjProperty( -+ model.login, -+ cipher.login, -+ { -+ username: null, -+ password: null, -+ totp: null, -+ }, -+ key -+ ); -+ -+ if (model.login.uris != null) { -+ cipher.login.uris = []; -+ for (let i = 0; i < model.login.uris.length; i++) { -+ const loginUri = new LoginUri(); -+ loginUri.match = model.login.uris[i].match; -+ await this.encryptObjProperty( -+ model.login.uris[i], -+ loginUri, -+ { -+ uri: null, -+ }, -+ key -+ ); -+ cipher.login.uris.push(loginUri); -+ } - } -+ return; -+ case CipherType.SecureNote: -+ cipher.secureNote = new SecureNote(); -+ cipher.secureNote.type = model.secureNote.type; -+ return; -+ case CipherType.Card: -+ cipher.card = new Card(); -+ await this.encryptObjProperty( -+ model.card, -+ cipher.card, -+ { -+ cardholderName: null, -+ brand: null, -+ number: null, -+ expMonth: null, -+ expYear: null, -+ code: null, -+ }, -+ key -+ ); -+ return; -+ case CipherType.Identity: -+ cipher.identity = new Identity(); -+ await this.encryptObjProperty( -+ model.identity, -+ cipher.identity, -+ { -+ title: null, -+ firstName: null, -+ middleName: null, -+ lastName: null, -+ address1: null, -+ address2: null, -+ address3: null, -+ city: null, -+ state: null, -+ postalCode: null, -+ country: null, -+ company: null, -+ email: null, -+ phone: null, -+ ssn: null, -+ username: null, -+ passportNumber: null, -+ licenseNumber: null, -+ }, -+ key -+ ); -+ return; -+ default: -+ throw new Error("Unknown cipher type."); - } -- -- private async encryptObjProperty(model: V, obj: D, -- map: any, key: SymmetricCryptoKey): Promise { -- const promises = []; -- const self = this; -- -- for (const prop in map) { -- if (!map.hasOwnProperty(prop)) { -- continue; -- } -- -- // tslint:disable-next-line -- (function (theProp, theObj) { -- const p = Promise.resolve().then(() => { -- const modelProp = (model as any)[(map[theProp] || theProp)]; -- if (modelProp && modelProp !== '') { -- return self.cryptoService.encrypt(modelProp, key); -- } -- return null; -- }).then((val: EncString) => { -- (theObj as any)[theProp] = val; -- }); -- promises.push(p); -- })(prop, obj); -+ } -+ -+ private async getCipherForUrl( -+ url: string, -+ lastUsed: boolean, -+ lastLaunched: boolean, -+ autofillOnPageLoad: boolean -+ ): Promise { -+ const cacheKey = autofillOnPageLoad ? "autofillOnPageLoad-" + url : url; -+ -+ if (!this.sortedCiphersCache.isCached(cacheKey)) { -+ let ciphers = await this.getAllDecryptedForUrl(url); -+ if (!ciphers) { -+ return null; -+ } -+ -+ if (autofillOnPageLoad) { -+ const autofillOnPageLoadDefault = await this.stateService.getAutoFillOnPageLoadDefault(); -+ ciphers = ciphers.filter( -+ (cipher) => -+ cipher.login.autofillOnPageLoad || -+ (cipher.login.autofillOnPageLoad == null && autofillOnPageLoadDefault !== false) -+ ); -+ if (ciphers.length === 0) { -+ return null; - } -+ } - -- await Promise.all(promises); -+ this.sortedCiphersCache.addCiphers(cacheKey, ciphers); - } - -- private async encryptCipherData(cipher: Cipher, model: CipherView, key: SymmetricCryptoKey) { -- switch (cipher.type) { -- case CipherType.Login: -- cipher.login = new Login(); -- cipher.login.passwordRevisionDate = model.login.passwordRevisionDate; -- cipher.login.autofillOnPageLoad = model.login.autofillOnPageLoad; -- await this.encryptObjProperty(model.login, cipher.login, { -- username: null, -- password: null, -- totp: null, -- }, key); -- -- if (model.login.uris != null) { -- cipher.login.uris = []; -- for (let i = 0; i < model.login.uris.length; i++) { -- const loginUri = new LoginUri(); -- loginUri.match = model.login.uris[i].match; -- await this.encryptObjProperty(model.login.uris[i], loginUri, { -- uri: null, -- }, key); -- cipher.login.uris.push(loginUri); -- } -- } -- return; -- case CipherType.SecureNote: -- cipher.secureNote = new SecureNote(); -- cipher.secureNote.type = model.secureNote.type; -- return; -- case CipherType.Card: -- cipher.card = new Card(); -- await this.encryptObjProperty(model.card, cipher.card, { -- cardholderName: null, -- brand: null, -- number: null, -- expMonth: null, -- expYear: null, -- code: null, -- }, key); -- return; -- case CipherType.Identity: -- cipher.identity = new Identity(); -- await this.encryptObjProperty(model.identity, cipher.identity, { -- title: null, -- firstName: null, -- middleName: null, -- lastName: null, -- address1: null, -- address2: null, -- address3: null, -- city: null, -- state: null, -- postalCode: null, -- country: null, -- company: null, -- email: null, -- phone: null, -- ssn: null, -- username: null, -- passportNumber: null, -- licenseNumber: null, -- }, key); -- return; -- default: -- throw new Error('Unknown cipher type.'); -- } -+ if (lastLaunched) { -+ return this.sortedCiphersCache.getLastLaunched(cacheKey); -+ } else if (lastUsed) { -+ return this.sortedCiphersCache.getLastUsed(cacheKey); -+ } else { -+ return this.sortedCiphersCache.getNext(cacheKey); - } -+ } - -- private async getCipherForUrl(url: string, lastUsed: boolean, lastLaunched: boolean, autofillOnPageLoad: boolean): Promise { -- const cacheKey = autofillOnPageLoad ? 'autofillOnPageLoad-' + url : url; -- -- if (!this.sortedCiphersCache.isCached(cacheKey)) { -- let ciphers = await this.getAllDecryptedForUrl(url); -- if (!ciphers) { -- return null; -- } -- -- if (autofillOnPageLoad) { -- const autofillOnPageLoadDefault = await this.storageService.get(ConstantsService.autoFillOnPageLoadDefaultKey); -- ciphers = ciphers.filter(cipher => cipher.login.autofillOnPageLoad || -- (cipher.login.autofillOnPageLoad == null && autofillOnPageLoadDefault !== false)); -- if (ciphers.length === 0) { -- return null; -- } -- } -+ private async clearEncryptedCiphersState(userId?: string) { -+ await this.stateService.setEncryptedCiphers(null, { userId: userId }); -+ } - -- this.sortedCiphersCache.addCiphers(cacheKey, ciphers); -- } -+ private async clearDecryptedCiphersState(userId?: string) { -+ await this.stateService.setDecryptedCiphers(null, { userId: userId }); -+ this.clearSortedCiphers(); -+ } - -- if (lastLaunched) { -- return this.sortedCiphersCache.getLastLaunched(cacheKey); -- } else if (lastUsed) { -- return this.sortedCiphersCache.getLastUsed(cacheKey); -- } else { -- return this.sortedCiphersCache.getNext(cacheKey); -- } -- } -+ private clearSortedCiphers() { -+ this.sortedCiphersCache.clear(); -+ } - } -diff --git a/jslib/common/src/services/collection.service.ts b/jslib/common/src/services/collection.service.ts -index 3c594080..7f555e63 100644 ---- a/jslib/common/src/services/collection.service.ts -+++ b/jslib/common/src/services/collection.service.ts -@@ -1,173 +1,159 @@ --import { CollectionData } from '../models/data/collectionData'; -+import { CollectionData } from "../models/data/collectionData"; - --import { Collection } from '../models/domain/collection'; --import { TreeNode } from '../models/domain/treeNode'; -+import { Collection } from "../models/domain/collection"; -+import { TreeNode } from "../models/domain/treeNode"; - --import { CollectionView } from '../models/view/collectionView'; -+import { CollectionView } from "../models/view/collectionView"; - --import { CollectionService as CollectionServiceAbstraction } from '../abstractions/collection.service'; --import { CryptoService } from '../abstractions/crypto.service'; --import { I18nService } from '../abstractions/i18n.service'; --import { StorageService } from '../abstractions/storage.service'; --import { UserService } from '../abstractions/user.service'; -+import { CollectionService as CollectionServiceAbstraction } from "../abstractions/collection.service"; -+import { CryptoService } from "../abstractions/crypto.service"; -+import { I18nService } from "../abstractions/i18n.service"; -+import { StateService } from "../abstractions/state.service"; - --import { ServiceUtils } from '../misc/serviceUtils'; --import { Utils } from '../misc/utils'; -+import { ServiceUtils } from "../misc/serviceUtils"; -+import { Utils } from "../misc/utils"; - --const Keys = { -- collectionsPrefix: 'collections_', --}; --const NestingDelimiter = '/'; -+const NestingDelimiter = "/"; - - export class CollectionService implements CollectionServiceAbstraction { -- decryptedCollectionCache: CollectionView[]; -- -- constructor(private cryptoService: CryptoService, private userService: UserService, -- private storageService: StorageService, private i18nService: I18nService) { -+ constructor( -+ private cryptoService: CryptoService, -+ private i18nService: I18nService, -+ private stateService: StateService -+ ) {} -+ -+ async clearCache(userId?: string): Promise { -+ await this.stateService.setDecryptedCollections(null, { userId: userId }); -+ } -+ -+ async encrypt(model: CollectionView): Promise { -+ if (model.organizationId == null) { -+ throw new Error("Collection has no organization id."); - } -- -- clearCache(): void { -- this.decryptedCollectionCache = null; -+ const key = await this.cryptoService.getOrgKey(model.organizationId); -+ if (key == null) { -+ throw new Error("No key for this collection's organization."); - } -- -- async encrypt(model: CollectionView): Promise { -- if (model.organizationId == null) { -- throw new Error('Collection has no organization id.'); -- } -- const key = await this.cryptoService.getOrgKey(model.organizationId); -- if (key == null) { -- throw new Error('No key for this collection\'s organization.'); -- } -- const collection = new Collection(); -- collection.id = model.id; -- collection.organizationId = model.organizationId; -- collection.readOnly = model.readOnly; -- collection.name = await this.cryptoService.encrypt(model.name, key); -- return collection; -+ const collection = new Collection(); -+ collection.id = model.id; -+ collection.organizationId = model.organizationId; -+ collection.readOnly = model.readOnly; -+ collection.name = await this.cryptoService.encrypt(model.name, key); -+ return collection; -+ } -+ -+ async decryptMany(collections: Collection[]): Promise { -+ if (collections == null) { -+ return []; - } -- -- async decryptMany(collections: Collection[]): Promise { -- if (collections == null) { -- return []; -- } -- const decCollections: CollectionView[] = []; -- const promises: Promise[] = []; -- collections.forEach(collection => { -- promises.push(collection.decrypt().then(c => decCollections.push(c))); -- }); -- await Promise.all(promises); -- return decCollections.sort(Utils.getSortFunction(this.i18nService, 'name')); -+ const decCollections: CollectionView[] = []; -+ const promises: Promise[] = []; -+ collections.forEach((collection) => { -+ promises.push(collection.decrypt().then((c) => decCollections.push(c))); -+ }); -+ await Promise.all(promises); -+ return decCollections.sort(Utils.getSortFunction(this.i18nService, "name")); -+ } -+ -+ async get(id: string): Promise { -+ const collections = await this.stateService.getEncryptedCollections(); -+ if (collections == null || !collections.hasOwnProperty(id)) { -+ return null; - } - -- async get(id: string): Promise { -- const userId = await this.userService.getUserId(); -- const collections = await this.storageService.get<{ [id: string]: CollectionData; }>( -- Keys.collectionsPrefix + userId); -- if (collections == null || !collections.hasOwnProperty(id)) { -- return null; -- } -+ return new Collection(collections[id]); -+ } - -- return new Collection(collections[id]); -+ async getAll(): Promise { -+ const collections = await this.stateService.getEncryptedCollections(); -+ const response: Collection[] = []; -+ for (const id in collections) { -+ if (collections.hasOwnProperty(id)) { -+ response.push(new Collection(collections[id])); -+ } - } -+ return response; -+ } - -- async getAll(): Promise { -- const userId = await this.userService.getUserId(); -- const collections = await this.storageService.get<{ [id: string]: CollectionData; }>( -- Keys.collectionsPrefix + userId); -- const response: Collection[] = []; -- for (const id in collections) { -- if (collections.hasOwnProperty(id)) { -- response.push(new Collection(collections[id])); -- } -- } -- return response; -+ async getAllDecrypted(): Promise { -+ let decryptedCollections = await this.stateService.getDecryptedCollections(); -+ if (decryptedCollections != null) { -+ return decryptedCollections; - } - -- async getAllDecrypted(): Promise { -- if (this.decryptedCollectionCache != null) { -- return this.decryptedCollectionCache; -- } -+ const hasKey = await this.cryptoService.hasKey(); -+ if (!hasKey) { -+ throw new Error("No key."); -+ } - -- const hasKey = await this.cryptoService.hasKey(); -- if (!hasKey) { -- throw new Error('No key.'); -- } -+ const collections = await this.getAll(); -+ decryptedCollections = await this.decryptMany(collections); -+ await this.stateService.setDecryptedCollections(decryptedCollections); -+ return decryptedCollections; -+ } - -- const collections = await this.getAll(); -- this.decryptedCollectionCache = await this.decryptMany(collections); -- return this.decryptedCollectionCache; -+ async getAllNested(collections: CollectionView[] = null): Promise[]> { -+ if (collections == null) { -+ collections = await this.getAllDecrypted(); - } -- -- async getAllNested(collections: CollectionView[] = null): Promise[]> { -- if (collections == null) { -- collections = await this.getAllDecrypted(); -- } -- const nodes: TreeNode[] = []; -- collections.forEach(c => { -- const collectionCopy = new CollectionView(); -- collectionCopy.id = c.id; -- collectionCopy.organizationId = c.organizationId; -- const parts = c.name != null ? c.name.replace(/^\/+|\/+$/g, '').split(NestingDelimiter) : []; -- ServiceUtils.nestedTraverse(nodes, 0, parts, collectionCopy, null, NestingDelimiter); -- }); -- return nodes; -+ const nodes: TreeNode[] = []; -+ collections.forEach((c) => { -+ const collectionCopy = new CollectionView(); -+ collectionCopy.id = c.id; -+ collectionCopy.organizationId = c.organizationId; -+ const parts = c.name != null ? c.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : []; -+ ServiceUtils.nestedTraverse(nodes, 0, parts, collectionCopy, null, NestingDelimiter); -+ }); -+ return nodes; -+ } -+ -+ async getNested(id: string): Promise> { -+ const collections = await this.getAllNested(); -+ return ServiceUtils.getTreeNodeObject(collections, id) as TreeNode; -+ } -+ -+ async upsert(collection: CollectionData | CollectionData[]): Promise { -+ let collections = await this.stateService.getEncryptedCollections(); -+ if (collections == null) { -+ collections = {}; - } - -- async getNested(id: string): Promise> { -- const collections = await this.getAllNested(); -- return ServiceUtils.getTreeNodeObject(collections, id) as TreeNode; -+ if (collection instanceof CollectionData) { -+ const c = collection as CollectionData; -+ collections[c.id] = c; -+ } else { -+ (collection as CollectionData[]).forEach((c) => { -+ collections[c.id] = c; -+ }); - } - -- async upsert(collection: CollectionData | CollectionData[]): Promise { -- const userId = await this.userService.getUserId(); -- let collections = await this.storageService.get<{ [id: string]: CollectionData; }>( -- Keys.collectionsPrefix + userId); -- if (collections == null) { -- collections = {}; -- } -- -- if (collection instanceof CollectionData) { -- const c = collection as CollectionData; -- collections[c.id] = c; -- } else { -- (collection as CollectionData[]).forEach(c => { -- collections[c.id] = c; -- }); -- } -- -- await this.storageService.save(Keys.collectionsPrefix + userId, collections); -- this.decryptedCollectionCache = null; -- } -+ await this.replace(collections); -+ } - -- async replace(collections: { [id: string]: CollectionData; }): Promise { -- const userId = await this.userService.getUserId(); -- await this.storageService.save(Keys.collectionsPrefix + userId, collections); -- this.decryptedCollectionCache = null; -- } -+ async replace(collections: { [id: string]: CollectionData }): Promise { -+ await this.clearCache(); -+ await this.stateService.setEncryptedCollections(collections); -+ } - -- async clear(userId: string): Promise { -- await this.storageService.remove(Keys.collectionsPrefix + userId); -- this.decryptedCollectionCache = null; -+ async clear(userId?: string): Promise { -+ await this.clearCache(userId); -+ await this.stateService.setEncryptedCollections(null, { userId: userId }); -+ } -+ -+ async delete(id: string | string[]): Promise { -+ const collections = await this.stateService.getEncryptedCollections(); -+ if (collections == null) { -+ return; - } - -- async delete(id: string | string[]): Promise { -- const userId = await this.userService.getUserId(); -- const collections = await this.storageService.get<{ [id: string]: CollectionData; }>( -- Keys.collectionsPrefix + userId); -- if (collections == null) { -- return; -- } -- -- if (typeof id === 'string') { -- const i = id as string; -- delete collections[id]; -- } else { -- (id as string[]).forEach(i => { -- delete collections[i]; -- }); -- } -- -- await this.storageService.save(Keys.collectionsPrefix + userId, collections); -- this.decryptedCollectionCache = null; -+ if (typeof id === "string") { -+ delete collections[id]; -+ } else { -+ (id as string[]).forEach((i) => { -+ delete collections[i]; -+ }); - } -+ -+ await this.replace(collections); -+ } - } -diff --git a/jslib/common/src/services/consoleLog.service.ts b/jslib/common/src/services/consoleLog.service.ts -index 64d81266..1ecd2499 100644 ---- a/jslib/common/src/services/consoleLog.service.ts -+++ b/jslib/common/src/services/consoleLog.service.ts -@@ -1,70 +1,73 @@ --import { LogLevelType } from '../enums/logLevelType'; -+import { LogLevelType } from "../enums/logLevelType"; - --import { LogService as LogServiceAbstraction } from '../abstractions/log.service'; -+import { LogService as LogServiceAbstraction } from "../abstractions/log.service"; - --import * as hrtime from 'browser-hrtime'; -+import * as hrtime from "browser-hrtime"; - - export class ConsoleLogService implements LogServiceAbstraction { -- protected timersMap: Map = new Map(); -+ protected timersMap: Map = new Map(); - -- constructor(protected isDev: boolean, protected filter: (level: LogLevelType) => boolean = null) { } -+ constructor( -+ protected isDev: boolean, -+ protected filter: (level: LogLevelType) => boolean = null -+ ) {} - -- debug(message: string) { -- if (!this.isDev) { -- return; -- } -- this.write(LogLevelType.Debug, message); -+ debug(message: string) { -+ if (!this.isDev) { -+ return; - } -+ this.write(LogLevelType.Debug, message); -+ } - -- info(message: string) { -- this.write(LogLevelType.Info, message); -- } -+ info(message: string) { -+ this.write(LogLevelType.Info, message); -+ } - -- warning(message: string) { -- this.write(LogLevelType.Warning, message); -- } -+ warning(message: string) { -+ this.write(LogLevelType.Warning, message); -+ } - -- error(message: string) { -- this.write(LogLevelType.Error, message); -- } -+ error(message: string) { -+ this.write(LogLevelType.Error, message); -+ } - -- write(level: LogLevelType, message: string) { -- if (this.filter != null && this.filter(level)) { -- return; -- } -- -- switch (level) { -- case LogLevelType.Debug: -- // tslint:disable-next-line -- console.log(message); -- break; -- case LogLevelType.Info: -- // tslint:disable-next-line -- console.log(message); -- break; -- case LogLevelType.Warning: -- // tslint:disable-next-line -- console.warn(message); -- break; -- case LogLevelType.Error: -- // tslint:disable-next-line -- console.error(message); -- break; -- default: -- break; -- } -+ write(level: LogLevelType, message: string) { -+ if (this.filter != null && this.filter(level)) { -+ return; - } - -- time(label: string = 'default') { -- if (!this.timersMap.has(label)) { -- this.timersMap.set(label, hrtime()); -- } -+ switch (level) { -+ case LogLevelType.Debug: -+ // tslint:disable-next-line -+ console.log(message); -+ break; -+ case LogLevelType.Info: -+ // tslint:disable-next-line -+ console.log(message); -+ break; -+ case LogLevelType.Warning: -+ // tslint:disable-next-line -+ console.warn(message); -+ break; -+ case LogLevelType.Error: -+ // tslint:disable-next-line -+ console.error(message); -+ break; -+ default: -+ break; - } -+ } - -- timeEnd(label: string = 'default'): [number, number] { -- const elapsed = hrtime(this.timersMap.get(label)); -- this.timersMap.delete(label); -- this.write(LogLevelType.Info, `${label}: ${elapsed[0] * 1000 + elapsed[1] / 10e6}ms`); -- return elapsed; -+ time(label: string = "default") { -+ if (!this.timersMap.has(label)) { -+ this.timersMap.set(label, hrtime()); - } -+ } -+ -+ timeEnd(label: string = "default"): [number, number] { -+ const elapsed = hrtime(this.timersMap.get(label)); -+ this.timersMap.delete(label); -+ this.write(LogLevelType.Info, `${label}: ${elapsed[0] * 1000 + elapsed[1] / 10e6}ms`); -+ return elapsed; -+ } - } -diff --git a/jslib/common/src/services/constants.service.ts b/jslib/common/src/services/constants.service.ts -deleted file mode 100644 -index 9202f862..00000000 ---- a/jslib/common/src/services/constants.service.ts -+++ /dev/null -@@ -1,68 +0,0 @@ --export class ConstantsService { -- static readonly environmentUrlsKey: string = 'environmentUrls'; -- static readonly disableGaKey: string = 'disableGa'; -- static readonly disableAddLoginNotificationKey: string = 'disableAddLoginNotification'; -- static readonly disableChangedPasswordNotificationKey: string = 'disableChangedPasswordNotification'; -- static readonly disableContextMenuItemKey: string = 'disableContextMenuItem'; -- static readonly disableFaviconKey: string = 'disableFavicon'; -- static readonly disableBadgeCounterKey: string = 'disableBadgeCounter'; -- static readonly disableAutoTotpCopyKey: string = 'disableAutoTotpCopy'; -- static readonly disableAutoBiometricsPromptKey: string = 'noAutoPromptBiometrics'; -- static readonly enableAutoFillOnPageLoadKey: string = 'enableAutoFillOnPageLoad'; -- static readonly autoFillOnPageLoadDefaultKey: string = 'autoFillOnPageLoadDefault'; -- static readonly vaultTimeoutKey: string = 'lockOption'; -- static readonly vaultTimeoutActionKey: string = 'vaultTimeoutAction'; -- static readonly lastActiveKey: string = 'lastActive'; -- static readonly neverDomainsKey: string = 'neverDomains'; -- static readonly installedVersionKey: string = 'installedVersion'; -- static readonly localeKey: string = 'locale'; -- static readonly themeKey: string = 'theme'; -- static readonly collapsedGroupingsKey: string = 'collapsedGroupings'; -- static readonly autoConfirmFingerprints: string = 'autoConfirmFingerprints'; -- static readonly dontShowCardsCurrentTab: string = 'dontShowCardsCurrentTab'; -- static readonly dontShowIdentitiesCurrentTab: string = 'dontShowIdentitiesCurrentTab'; -- static readonly defaultUriMatch: string = 'defaultUriMatch'; -- static readonly pinProtectedKey: string = 'pinProtectedKey'; -- static readonly protectedPin: string = 'protectedPin'; -- static readonly clearClipboardKey: string = 'clearClipboardKey'; -- static readonly eventCollectionKey: string = 'eventCollection'; -- static readonly ssoCodeVerifierKey: string = 'ssoCodeVerifier'; -- static readonly ssoStateKey: string = 'ssoState'; -- static readonly biometricUnlockKey: string = 'biometric'; -- static readonly biometricText: string = 'biometricText'; -- static readonly biometricAwaitingAcceptance: string = 'biometricAwaitingAcceptance'; -- static readonly biometricFingerprintValidated: string = 'biometricFingerprintValidated'; -- -- readonly environmentUrlsKey: string = ConstantsService.environmentUrlsKey; -- readonly disableGaKey: string = ConstantsService.disableGaKey; -- readonly disableAddLoginNotificationKey: string = ConstantsService.disableAddLoginNotificationKey; -- readonly disableContextMenuItemKey: string = ConstantsService.disableContextMenuItemKey; -- readonly disableFaviconKey: string = ConstantsService.disableFaviconKey; -- readonly disableBadgeCounterKey: string = ConstantsService.disableBadgeCounterKey; -- readonly disableAutoTotpCopyKey: string = ConstantsService.disableAutoTotpCopyKey; -- readonly disableAutoBiometricsPromptKey: string = ConstantsService.disableAutoBiometricsPromptKey; -- readonly enableAutoFillOnPageLoadKey: string = ConstantsService.enableAutoFillOnPageLoadKey; -- readonly autoFillOnPageLoadDefaultKey: string = ConstantsService.autoFillOnPageLoadDefaultKey; -- readonly vaultTimeoutKey: string = ConstantsService.vaultTimeoutKey; -- readonly vaultTimeoutActionKey: string = ConstantsService.vaultTimeoutActionKey; -- readonly lastActiveKey: string = ConstantsService.lastActiveKey; -- readonly neverDomainsKey: string = ConstantsService.neverDomainsKey; -- readonly installedVersionKey: string = ConstantsService.installedVersionKey; -- readonly localeKey: string = ConstantsService.localeKey; -- readonly themeKey: string = ConstantsService.themeKey; -- readonly collapsedGroupingsKey: string = ConstantsService.collapsedGroupingsKey; -- readonly autoConfirmFingerprints: string = ConstantsService.autoConfirmFingerprints; -- readonly dontShowCardsCurrentTab: string = ConstantsService.dontShowCardsCurrentTab; -- readonly dontShowIdentitiesCurrentTab: string = ConstantsService.dontShowIdentitiesCurrentTab; -- readonly defaultUriMatch: string = ConstantsService.defaultUriMatch; -- readonly pinProtectedKey: string = ConstantsService.pinProtectedKey; -- readonly protectedPin: string = ConstantsService.protectedPin; -- readonly clearClipboardKey: string = ConstantsService.clearClipboardKey; -- readonly eventCollectionKey: string = ConstantsService.eventCollectionKey; -- readonly ssoCodeVerifierKey: string = ConstantsService.ssoCodeVerifierKey; -- readonly ssoStateKey: string = ConstantsService.ssoStateKey; -- readonly biometricUnlockKey: string = ConstantsService.biometricUnlockKey; -- readonly biometricText: string = ConstantsService.biometricText; -- readonly biometricAwaitingAcceptance: string = ConstantsService.biometricAwaitingAcceptance; -- readonly biometricFingerprintValidated: string = ConstantsService.biometricFingerprintValidated; --} -diff --git a/jslib/common/src/services/container.service.ts b/jslib/common/src/services/container.service.ts -index 08f428ad..2880e7c7 100644 ---- a/jslib/common/src/services/container.service.ts -+++ b/jslib/common/src/services/container.service.ts -@@ -1,21 +1,20 @@ --import { CryptoService } from '../abstractions/crypto.service'; -+import { CryptoService } from "../abstractions/crypto.service"; - - export class ContainerService { -- constructor(private cryptoService: CryptoService) { -- } -+ constructor(private cryptoService: CryptoService) {} - -- // deprecated, use attachToGlobal instead -- attachToWindow(win: any) { -- this.attachToGlobal(win); -- } -+ // deprecated, use attachToGlobal instead -+ attachToWindow(win: any) { -+ this.attachToGlobal(win); -+ } - -- attachToGlobal(global: any) { -- if (!global.bitwardenContainerService) { -- global.bitwardenContainerService = this; -- } -+ attachToGlobal(global: any) { -+ if (!global.bitwardenContainerService) { -+ global.bitwardenContainerService = this; - } -+ } - -- getCryptoService(): CryptoService { -- return this.cryptoService; -- } -+ getCryptoService(): CryptoService { -+ return this.cryptoService; -+ } - } -diff --git a/jslib/common/src/services/crypto.service.ts b/jslib/common/src/services/crypto.service.ts -index 60ef0ea0..fd9d83c4 100644 ---- a/jslib/common/src/services/crypto.service.ts -+++ b/jslib/common/src/services/crypto.service.ts -@@ -1,888 +1,968 @@ --import * as bigInt from 'big-integer'; -- --import { EncryptionType } from '../enums/encryptionType'; --import { HashPurpose } from '../enums/hashPurpose'; --import { KdfType } from '../enums/kdfType'; -- --import { EncArrayBuffer } from '../models/domain/encArrayBuffer'; --import { EncryptedObject } from '../models/domain/encryptedObject'; --import { EncString } from '../models/domain/encString'; --import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; --import { ProfileOrganizationResponse } from '../models/response/profileOrganizationResponse'; -- --import { CryptoService as CryptoServiceAbstraction } from '../abstractions/crypto.service'; --import { CryptoFunctionService } from '../abstractions/cryptoFunction.service'; --import { LogService } from '../abstractions/log.service'; --import { PlatformUtilsService } from '../abstractions/platformUtils.service'; --import { -- KeySuffixOptions, -- StorageService, --} from '../abstractions/storage.service'; -- --import { ConstantsService } from './constants.service'; -- --import { sequentialize } from '../misc/sequentialize'; --import { Utils } from '../misc/utils'; --import { EEFLongWordList } from '../misc/wordlist'; --import { ProfileProviderOrganizationResponse } from '../models/response/profileProviderOrganizationResponse'; --import { ProfileProviderResponse } from '../models/response/profileProviderResponse'; -- --export const Keys = { -- key: 'key', // Master Key -- encOrgKeys: 'encOrgKeys', -- encProviderKeys: 'encProviderKeys', -- encPrivateKey: 'encPrivateKey', -- encKey: 'encKey', // Generated Symmetric Key -- keyHash: 'keyHash', --}; -+import * as bigInt from "big-integer"; - --export class CryptoService implements CryptoServiceAbstraction { -- private key: SymmetricCryptoKey; -- private encKey: SymmetricCryptoKey; -- private legacyEtmKey: SymmetricCryptoKey; -- private keyHash: string; -- private publicKey: ArrayBuffer; -- private privateKey: ArrayBuffer; -- private orgKeys: Map; -- private providerKeys: Map; -- -- constructor(private storageService: StorageService, protected secureStorageService: StorageService, -- private cryptoFunctionService: CryptoFunctionService, protected platformUtilService: PlatformUtilsService, -- protected logService: LogService) { -- } -+import { EncryptionType } from "../enums/encryptionType"; -+import { HashPurpose } from "../enums/hashPurpose"; -+import { KdfType } from "../enums/kdfType"; -+import { KeySuffixOptions } from "../enums/keySuffixOptions"; - -- async setKey(key: SymmetricCryptoKey): Promise { -- this.key = key; -+import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; -+import { EncryptedObject } from "../models/domain/encryptedObject"; -+import { EncString } from "../models/domain/encString"; -+import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; - -- await this.storeKey(key); -- } -+import { CryptoService as CryptoServiceAbstraction } from "../abstractions/crypto.service"; -+import { CryptoFunctionService } from "../abstractions/cryptoFunction.service"; -+import { LogService } from "../abstractions/log.service"; -+import { PlatformUtilsService } from "../abstractions/platformUtils.service"; -+import { StateService } from "../abstractions/state.service"; - -- setKeyHash(keyHash: string): Promise<{}> { -- this.keyHash = keyHash; -- return this.storageService.save(Keys.keyHash, keyHash); -- } -+import { sequentialize } from "../misc/sequentialize"; -+import { Utils } from "../misc/utils"; -+import { EEFLongWordList } from "../misc/wordlist"; - -- async setEncKey(encKey: string): Promise<{}> { -- if (encKey == null) { -- return; -- } -+import { ProfileOrganizationResponse } from "../models/response/profileOrganizationResponse"; -+import { ProfileProviderOrganizationResponse } from "../models/response/profileProviderOrganizationResponse"; -+import { ProfileProviderResponse } from "../models/response/profileProviderResponse"; - -- await this.storageService.save(Keys.encKey, encKey); -- this.encKey = null; -- } -- -- async setEncPrivateKey(encPrivateKey: string): Promise<{}> { -- if (encPrivateKey == null) { -- return; -- } -- -- await this.storageService.save(Keys.encPrivateKey, encPrivateKey); -- this.privateKey = null; -- } -+export class CryptoService implements CryptoServiceAbstraction { -+ constructor( -+ private cryptoFunctionService: CryptoFunctionService, -+ protected platformUtilService: PlatformUtilsService, -+ protected logService: LogService, -+ protected stateService: StateService -+ ) {} - -- async setOrgKeys(orgs: ProfileOrganizationResponse[], providerOrgs: ProfileProviderOrganizationResponse[]): Promise<{}> { -- const orgKeys: any = {}; -- orgs.forEach(org => { -- orgKeys[org.id] = org.key; -- }); -+ async setKey(key: SymmetricCryptoKey, userId?: string): Promise { -+ await this.stateService.setCryptoMasterKey(key, { userId: userId }); -+ await this.storeKey(key, userId); -+ } - -- for (const providerOrg of providerOrgs) { -- // Convert provider encrypted keys to user encrypted. -- const providerKey = await this.getProviderKey(providerOrg.providerId); -- const decValue = await this.decryptToBytes(new EncString(providerOrg.key), providerKey); -- orgKeys[providerOrg.id] = await (await this.rsaEncrypt(decValue)).encryptedString; -- } -+ async setKeyHash(keyHash: string): Promise { -+ await this.stateService.setKeyHash(keyHash); -+ } - -- this.orgKeys = null; -- return this.storageService.save(Keys.encOrgKeys, orgKeys); -+ async setEncKey(encKey: string): Promise { -+ if (encKey == null) { -+ return; - } - -- setProviderKeys(providers: ProfileProviderResponse[]): Promise<{}> { -- const providerKeys: any = {}; -- providers.forEach(provider => { -- providerKeys[provider.id] = provider.key; -- }); -+ await this.stateService.setDecryptedCryptoSymmetricKey(null); -+ await this.stateService.setEncryptedCryptoSymmetricKey(encKey); -+ } - -- this.providerKeys = null; -- return this.storageService.save(Keys.encProviderKeys, providerKeys); -+ async setEncPrivateKey(encPrivateKey: string): Promise { -+ if (encPrivateKey == null) { -+ return; - } - -- async getKey(keySuffix?: KeySuffixOptions): Promise { -- if (this.key != null) { -- return this.key; -- } -- -- keySuffix ||= 'auto'; -- const symmetricKey = await this.getKeyFromStorage(keySuffix); -+ await this.stateService.setDecryptedPrivateKey(null); -+ await this.stateService.setEncryptedPrivateKey(encPrivateKey); -+ } - -- if (symmetricKey != null) { -- this.setKey(symmetricKey); -- } -+ async setOrgKeys( -+ orgs: ProfileOrganizationResponse[], -+ providerOrgs: ProfileProviderOrganizationResponse[] -+ ): Promise { -+ const orgKeys: any = {}; -+ orgs.forEach((org) => { -+ orgKeys[org.id] = org.key; -+ }); - -- return symmetricKey; -+ for (const providerOrg of providerOrgs) { -+ // Convert provider encrypted keys to user encrypted. -+ const providerKey = await this.getProviderKey(providerOrg.providerId); -+ const decValue = await this.decryptToBytes(new EncString(providerOrg.key), providerKey); -+ orgKeys[providerOrg.id] = (await this.rsaEncrypt(decValue)).encryptedString; - } - -- async getKeyFromStorage(keySuffix: KeySuffixOptions): Promise { -- const key = await this.retrieveKeyFromStorage(keySuffix); -- if (key != null) { -+ await this.stateService.setDecryptedOrganizationKeys(null); -+ return await this.stateService.setEncryptedOrganizationKeys(orgKeys); -+ } - -- const symmetricKey = new SymmetricCryptoKey(Utils.fromB64ToArray(key).buffer); -+ async setProviderKeys(providers: ProfileProviderResponse[]): Promise { -+ const providerKeys: any = {}; -+ providers.forEach((provider) => { -+ providerKeys[provider.id] = provider.key; -+ }); - -- if (!await this.validateKey(symmetricKey)) { -- this.logService.warning('Wrong key, throwing away stored key'); -- this.secureStorageService.remove(Keys.key, { keySuffix: keySuffix }); -- return null; -- } -+ await this.stateService.setDecryptedProviderKeys(null); -+ return await this.stateService.setEncryptedProviderKeys(providerKeys); -+ } - -- return symmetricKey; -- } -- return null; -- } -+ async getKey(keySuffix?: KeySuffixOptions, userId?: string): Promise { -+ const inMemoryKey = await this.stateService.getCryptoMasterKey({ userId: userId }); - -- async getKeyHash(): Promise { -- if (this.keyHash != null) { -- return this.keyHash; -- } -- -- const keyHash = await this.storageService.get(Keys.keyHash); -- if (keyHash != null) { -- this.keyHash = keyHash; -- } -- -- return keyHash == null ? null : this.keyHash; -+ if (inMemoryKey != null) { -+ return inMemoryKey; - } - -- async compareAndUpdateKeyHash(masterPassword: string, key: SymmetricCryptoKey): Promise { -- const storedKeyHash = await this.getKeyHash(); -- if (masterPassword != null && storedKeyHash != null) { -- const localKeyHash = await this.hashPassword(masterPassword, key, HashPurpose.LocalAuthorization); -- if (localKeyHash != null && storedKeyHash === localKeyHash) { -- return true; -- } -- -- // TODO: remove serverKeyHash check in 1-2 releases after everyone's keyHash has been updated -- const serverKeyHash = await this.hashPassword(masterPassword, key, HashPurpose.ServerAuthorization); -- if (serverKeyHash != null && storedKeyHash === serverKeyHash) { -- await this.setKeyHash(localKeyHash); -- return true; -- } -- } -+ keySuffix ||= KeySuffixOptions.Auto; -+ const symmetricKey = await this.getKeyFromStorage(keySuffix, userId); - -- return false; -+ if (symmetricKey != null) { -+ // TODO: Refactor here so get key doesn't also set key -+ this.setKey(symmetricKey, userId); - } - -- @sequentialize(() => 'getEncKey') -- async getEncKey(key: SymmetricCryptoKey = null): Promise { -- if (this.encKey != null) { -- return this.encKey; -- } -+ return symmetricKey; -+ } - -- const encKey = await this.storageService.get(Keys.encKey); -- if (encKey == null) { -- return null; -- } -- -- if (key == null) { -- key = await this.getKey(); -- } -- if (key == null) { -- return null; -- } -- -- let decEncKey: ArrayBuffer; -- const encKeyCipher = new EncString(encKey); -- if (encKeyCipher.encryptionType === EncryptionType.AesCbc256_B64) { -- decEncKey = await this.decryptToBytes(encKeyCipher, key); -- } else if (encKeyCipher.encryptionType === EncryptionType.AesCbc256_HmacSha256_B64) { -- const newKey = await this.stretchKey(key); -- decEncKey = await this.decryptToBytes(encKeyCipher, newKey); -- } else { -- throw new Error('Unsupported encKey type.'); -- } -+ async getKeyFromStorage( -+ keySuffix: KeySuffixOptions, -+ userId?: string -+ ): Promise { -+ const key = await this.retrieveKeyFromStorage(keySuffix, userId); -+ if (key != null) { -+ const symmetricKey = new SymmetricCryptoKey(Utils.fromB64ToArray(key).buffer); - -- if (decEncKey == null) { -- return null; -- } -- this.encKey = new SymmetricCryptoKey(decEncKey); -- return this.encKey; -- } -- -- async getPublicKey(): Promise { -- if (this.publicKey != null) { -- return this.publicKey; -- } -- -- const privateKey = await this.getPrivateKey(); -- if (privateKey == null) { -- return null; -- } -- -- this.publicKey = await this.cryptoFunctionService.rsaExtractPublicKey(privateKey); -- return this.publicKey; -- } -- -- async getPrivateKey(): Promise { -- if (this.privateKey != null) { -- return this.privateKey; -- } -- -- const encPrivateKey = await this.storageService.get(Keys.encPrivateKey); -- if (encPrivateKey == null) { -- return null; -- } -- -- this.privateKey = await this.decryptToBytes(new EncString(encPrivateKey), null); -- return this.privateKey; -- } -- -- async getFingerprint(userId: string, publicKey?: ArrayBuffer): Promise { -- if (publicKey == null) { -- publicKey = await this.getPublicKey(); -- } -- if (publicKey === null) { -- throw new Error('No public key available.'); -- } -- const keyFingerprint = await this.cryptoFunctionService.hash(publicKey, 'sha256'); -- const userFingerprint = await this.cryptoFunctionService.hkdfExpand(keyFingerprint, userId, 32, 'sha256'); -- return this.hashPhrase(userFingerprint); -- } -- -- @sequentialize(() => 'getOrgKeys') -- async getOrgKeys(): Promise> { -- if (this.orgKeys != null && this.orgKeys.size > 0) { -- return this.orgKeys; -- } -- -- const encOrgKeys = await this.storageService.get(Keys.encOrgKeys); -- if (encOrgKeys == null) { -- return null; -- } -- -- const orgKeys: Map = new Map(); -- let setKey = false; -- -- for (const orgId in encOrgKeys) { -- if (!encOrgKeys.hasOwnProperty(orgId)) { -- continue; -- } -- -- const decValue = await this.rsaDecrypt(encOrgKeys[orgId]); -- orgKeys.set(orgId, new SymmetricCryptoKey(decValue)); -- setKey = true; -- } -- -- if (setKey) { -- this.orgKeys = orgKeys; -- } -- -- return this.orgKeys; -+ if (!(await this.validateKey(symmetricKey))) { -+ this.logService.warning("Wrong key, throwing away stored key"); -+ await this.clearSecretKeyStore(userId); -+ return null; -+ } -+ -+ return symmetricKey; -+ } -+ return null; -+ } -+ -+ async getKeyHash(): Promise { -+ return await this.stateService.getKeyHash(); -+ } -+ -+ async compareAndUpdateKeyHash(masterPassword: string, key: SymmetricCryptoKey): Promise { -+ const storedKeyHash = await this.getKeyHash(); -+ if (masterPassword != null && storedKeyHash != null) { -+ const localKeyHash = await this.hashPassword( -+ masterPassword, -+ key, -+ HashPurpose.LocalAuthorization -+ ); -+ if (localKeyHash != null && storedKeyHash === localKeyHash) { -+ return true; -+ } -+ -+ // TODO: remove serverKeyHash check in 1-2 releases after everyone's keyHash has been updated -+ const serverKeyHash = await this.hashPassword( -+ masterPassword, -+ key, -+ HashPurpose.ServerAuthorization -+ ); -+ if (serverKeyHash != null && storedKeyHash === serverKeyHash) { -+ await this.setKeyHash(localKeyHash); -+ return true; -+ } - } - -- async getOrgKey(orgId: string): Promise { -- if (orgId == null) { -- return null; -- } -- -- const orgKeys = await this.getOrgKeys(); -- if (orgKeys == null || !orgKeys.has(orgId)) { -- return null; -- } -+ return false; -+ } - -- return orgKeys.get(orgId); -+ @sequentialize(() => "getEncKey") -+ async getEncKey(key: SymmetricCryptoKey = null): Promise { -+ const inMemoryKey = await this.stateService.getDecryptedCryptoSymmetricKey(); -+ if (inMemoryKey != null) { -+ return inMemoryKey; - } - -- @sequentialize(() => 'getProviderKeys') -- async getProviderKeys(): Promise> { -- if (this.providerKeys != null && this.providerKeys.size > 0) { -- return this.providerKeys; -- } -- -- const encProviderKeys = await this.storageService.get(Keys.encProviderKeys); -- if (encProviderKeys == null) { -- return null; -- } -- -- const providerKeys: Map = new Map(); -- let setKey = false; -- -- for (const orgId in encProviderKeys) { -- if (!encProviderKeys.hasOwnProperty(orgId)) { -- continue; -- } -- -- const decValue = await this.rsaDecrypt(encProviderKeys[orgId]); -- providerKeys.set(orgId, new SymmetricCryptoKey(decValue)); -- setKey = true; -- } -- -- if (setKey) { -- this.providerKeys = providerKeys; -- } -- -- return this.providerKeys; -+ const encKey = await this.stateService.getEncryptedCryptoSymmetricKey(); -+ if (encKey == null) { -+ return null; - } - -- async getProviderKey(providerId: string): Promise { -- if (providerId == null) { -- return null; -- } -- -- const providerKeys = await this.getProviderKeys(); -- if (providerKeys == null || !providerKeys.has(providerId)) { -- return null; -- } -- -- return providerKeys.get(providerId); -+ if (key == null) { -+ key = await this.getKey(); - } -- -- async hasKey(): Promise { -- return this.hasKeyInMemory() || await this.hasKeyStored('auto') || await this.hasKeyStored('biometric'); -+ if (key == null) { -+ return null; - } - -- hasKeyInMemory(): boolean { -- return this.key != null; -+ let decEncKey: ArrayBuffer; -+ const encKeyCipher = new EncString(encKey); -+ if (encKeyCipher.encryptionType === EncryptionType.AesCbc256_B64) { -+ decEncKey = await this.decryptToBytes(encKeyCipher, key); -+ } else if (encKeyCipher.encryptionType === EncryptionType.AesCbc256_HmacSha256_B64) { -+ const newKey = await this.stretchKey(key); -+ decEncKey = await this.decryptToBytes(encKeyCipher, newKey); -+ } else { -+ throw new Error("Unsupported encKey type."); - } - -- hasKeyStored(keySuffix: KeySuffixOptions): Promise { -- return this.secureStorageService.has(Keys.key, { keySuffix: keySuffix }); -+ if (decEncKey == null) { -+ return null; - } -+ const symmetricCryptoKey = new SymmetricCryptoKey(decEncKey); -+ await this.stateService.setDecryptedCryptoSymmetricKey(symmetricCryptoKey); -+ return symmetricCryptoKey; -+ } - -- async hasEncKey(): Promise { -- const encKey = await this.storageService.get(Keys.encKey); -- return encKey != null; -+ async getPublicKey(): Promise { -+ const inMemoryPublicKey = await this.stateService.getPublicKey(); -+ if (inMemoryPublicKey != null) { -+ return inMemoryPublicKey; - } - -- async clearKey(clearSecretStorage: boolean = true): Promise { -- this.key = this.legacyEtmKey = null; -- if (clearSecretStorage) { -- this.clearStoredKey('auto'); -- this.clearStoredKey('biometric'); -- } -+ const privateKey = await this.getPrivateKey(); -+ if (privateKey == null) { -+ return null; - } - -- async clearStoredKey(keySuffix: KeySuffixOptions) { -- await this.secureStorageService.remove(Keys.key, { keySuffix: keySuffix }); -- } -+ const publicKey = await this.cryptoFunctionService.rsaExtractPublicKey(privateKey); -+ await this.stateService.setPublicKey(publicKey); -+ return publicKey; -+ } - -- clearKeyHash(): Promise { -- this.keyHash = null; -- return this.storageService.remove(Keys.keyHash); -+ async getPrivateKey(): Promise { -+ const decryptedPrivateKey = await this.stateService.getDecryptedPrivateKey(); -+ if (decryptedPrivateKey != null) { -+ return decryptedPrivateKey; - } - -- clearEncKey(memoryOnly?: boolean): Promise { -- this.encKey = null; -- if (memoryOnly) { -- return Promise.resolve(); -- } -- return this.storageService.remove(Keys.encKey); -+ const encPrivateKey = await this.stateService.getEncryptedPrivateKey(); -+ if (encPrivateKey == null) { -+ return null; - } - -- clearKeyPair(memoryOnly?: boolean): Promise { -- this.privateKey = null; -- this.publicKey = null; -- if (memoryOnly) { -- return Promise.resolve(); -- } -- return this.storageService.remove(Keys.encPrivateKey); -- } -+ const privateKey = await this.decryptToBytes(new EncString(encPrivateKey), null); -+ await this.stateService.setDecryptedPrivateKey(privateKey); -+ return privateKey; -+ } - -- clearOrgKeys(memoryOnly?: boolean): Promise { -- this.orgKeys = null; -- if (memoryOnly) { -- return Promise.resolve(); -- } -- return this.storageService.remove(Keys.encOrgKeys); -+ async getFingerprint(userId: string, publicKey?: ArrayBuffer): Promise { -+ if (publicKey == null) { -+ publicKey = await this.getPublicKey(); - } -- -- clearProviderKeys(memoryOnly?: boolean): Promise { -- this.providerKeys = null; -- if (memoryOnly) { -- return Promise.resolve(); -- } -- return this.storageService.remove(Keys.encOrgKeys); -+ if (publicKey === null) { -+ throw new Error("No public key available."); - } -+ const keyFingerprint = await this.cryptoFunctionService.hash(publicKey, "sha256"); -+ const userFingerprint = await this.cryptoFunctionService.hkdfExpand( -+ keyFingerprint, -+ userId, -+ 32, -+ "sha256" -+ ); -+ return this.hashPhrase(userFingerprint); -+ } - -- clearPinProtectedKey(): Promise { -- return this.storageService.remove(ConstantsService.pinProtectedKey); -+ @sequentialize(() => "getOrgKeys") -+ async getOrgKeys(): Promise> { -+ const orgKeys: Map = new Map(); -+ const decryptedOrganizationKeys = await this.stateService.getDecryptedOrganizationKeys(); -+ if (decryptedOrganizationKeys != null && decryptedOrganizationKeys.size > 0) { -+ return decryptedOrganizationKeys; - } - -- async clearKeys(): Promise { -- await this.clearKey(); -- await this.clearKeyHash(); -- await this.clearOrgKeys(); -- await this.clearProviderKeys(); -- await this.clearEncKey(); -- await this.clearKeyPair(); -- await this.clearPinProtectedKey(); -+ const encOrgKeys = await this.stateService.getEncryptedOrganizationKeys(); -+ if (encOrgKeys == null) { -+ return null; - } - -- async toggleKey(): Promise { -- const key = await this.getKey(); -+ let setKey = false; - -- await this.setKey(key); -- } -+ for (const orgId in encOrgKeys) { -+ if (!encOrgKeys.hasOwnProperty(orgId)) { -+ continue; -+ } - -- async makeKey(password: string, salt: string, kdf: KdfType, kdfIterations: number): -- Promise { -- let key: ArrayBuffer = null; -- if (kdf == null || kdf === KdfType.PBKDF2_SHA256) { -- if (kdfIterations == null) { -- kdfIterations = 5000; -- } else if (kdfIterations < 5000) { -- throw new Error('PBKDF2 iteration minimum is 5000.'); -- } -- key = await this.cryptoFunctionService.pbkdf2(password, salt, 'sha256', kdfIterations); -- } else { -- throw new Error('Unknown Kdf.'); -- } -- return new SymmetricCryptoKey(key); -- } -- -- async makeKeyFromPin(pin: string, salt: string, kdf: KdfType, kdfIterations: number, -- protectedKeyCs: EncString = null): -- Promise { -- if (protectedKeyCs == null) { -- const pinProtectedKey = await this.storageService.get(ConstantsService.pinProtectedKey); -- if (pinProtectedKey == null) { -- throw new Error('No PIN protected key found.'); -- } -- protectedKeyCs = new EncString(pinProtectedKey); -- } -- const pinKey = await this.makePinKey(pin, salt, kdf, kdfIterations); -- const decKey = await this.decryptToBytes(protectedKeyCs, pinKey); -- return new SymmetricCryptoKey(decKey); -+ const decValue = await this.rsaDecrypt(encOrgKeys[orgId]); -+ orgKeys.set(orgId, new SymmetricCryptoKey(decValue)); -+ setKey = true; - } - -- async makeShareKey(): Promise<[EncString, SymmetricCryptoKey]> { -- const shareKey = await this.cryptoFunctionService.randomBytes(64); -- const publicKey = await this.getPublicKey(); -- const encShareKey = await this.rsaEncrypt(shareKey, publicKey); -- return [encShareKey, new SymmetricCryptoKey(shareKey)]; -+ if (setKey) { -+ await this.stateService.setDecryptedOrganizationKeys(orgKeys); - } - -- async makeKeyPair(key?: SymmetricCryptoKey): Promise<[string, EncString]> { -- const keyPair = await this.cryptoFunctionService.rsaGenerateKeyPair(2048); -- const publicB64 = Utils.fromBufferToB64(keyPair[0]); -- const privateEnc = await this.encrypt(keyPair[1], key); -- return [publicB64, privateEnc]; -- } -+ return orgKeys; -+ } - -- async makePinKey(pin: string, salt: string, kdf: KdfType, kdfIterations: number): Promise { -- const pinKey = await this.makeKey(pin, salt, kdf, kdfIterations); -- return await this.stretchKey(pinKey); -+ async getOrgKey(orgId: string): Promise { -+ if (orgId == null) { -+ return null; - } - -- async makeSendKey(keyMaterial: ArrayBuffer): Promise { -- const sendKey = await this.cryptoFunctionService.hkdf(keyMaterial, 'bitwarden-send', 'send', 64, 'sha256'); -- return new SymmetricCryptoKey(sendKey); -+ const orgKeys = await this.getOrgKeys(); -+ if (orgKeys == null || !orgKeys.has(orgId)) { -+ return null; - } - -- async hashPassword(password: string, key: SymmetricCryptoKey, hashPurpose?: HashPurpose): Promise { -- if (key == null) { -- key = await this.getKey(); -- } -- if (password == null || key == null) { -- throw new Error('Invalid parameters.'); -- } -- -- const iterations = hashPurpose === HashPurpose.LocalAuthorization ? 2 : 1; -- const hash = await this.cryptoFunctionService.pbkdf2(key.key, password, 'sha256', iterations); -- return Utils.fromBufferToB64(hash); -- } -+ return orgKeys.get(orgId); -+ } - -- async makeEncKey(key: SymmetricCryptoKey): Promise<[SymmetricCryptoKey, EncString]> { -- const theKey = await this.getKeyForEncryption(key); -- const encKey = await this.cryptoFunctionService.randomBytes(64); -- return this.buildEncKey(theKey, encKey); -+ @sequentialize(() => "getProviderKeys") -+ async getProviderKeys(): Promise> { -+ const providerKeys: Map = new Map(); -+ const decryptedProviderKeys = await this.stateService.getDecryptedProviderKeys(); -+ if (decryptedProviderKeys != null && decryptedProviderKeys.size > 0) { -+ return decryptedProviderKeys; - } - -- async remakeEncKey(key: SymmetricCryptoKey, encKey?: SymmetricCryptoKey): Promise<[SymmetricCryptoKey, EncString]> { -- if (encKey == null) { -- encKey = await this.getEncKey(); -- } -- return this.buildEncKey(key, encKey.key); -+ const encProviderKeys = await this.stateService.getEncryptedProviderKeys(); -+ if (encProviderKeys == null) { -+ return null; - } - -- async encrypt(plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey): Promise { -- if (plainValue == null) { -- return Promise.resolve(null); -- } -+ let setKey = false; - -- let plainBuf: ArrayBuffer; -- if (typeof (plainValue) === 'string') { -- plainBuf = Utils.fromUtf8ToArray(plainValue).buffer; -- } else { -- plainBuf = plainValue; -- } -+ for (const orgId in encProviderKeys) { -+ if (!encProviderKeys.hasOwnProperty(orgId)) { -+ continue; -+ } - -- const encObj = await this.aesEncrypt(plainBuf, key); -- const iv = Utils.fromBufferToB64(encObj.iv); -- const data = Utils.fromBufferToB64(encObj.data); -- const mac = encObj.mac != null ? Utils.fromBufferToB64(encObj.mac) : null; -- return new EncString(encObj.key.encType, data, iv, mac); -+ const decValue = await this.rsaDecrypt(encProviderKeys[orgId]); -+ providerKeys.set(orgId, new SymmetricCryptoKey(decValue)); -+ setKey = true; - } - -- async encryptToBytes(plainValue: ArrayBuffer, key?: SymmetricCryptoKey): Promise { -- const encValue = await this.aesEncrypt(plainValue, key); -- let macLen = 0; -- if (encValue.mac != null) { -- macLen = encValue.mac.byteLength; -- } -- -- const encBytes = new Uint8Array(1 + encValue.iv.byteLength + macLen + encValue.data.byteLength); -- encBytes.set([encValue.key.encType]); -- encBytes.set(new Uint8Array(encValue.iv), 1); -- if (encValue.mac != null) { -- encBytes.set(new Uint8Array(encValue.mac), 1 + encValue.iv.byteLength); -- } -- -- encBytes.set(new Uint8Array(encValue.data), 1 + encValue.iv.byteLength + macLen); -- return new EncArrayBuffer(encBytes.buffer); -+ if (setKey) { -+ await this.stateService.setDecryptedProviderKeys(providerKeys); - } - -- async rsaEncrypt(data: ArrayBuffer, publicKey?: ArrayBuffer): Promise { -- if (publicKey == null) { -- publicKey = await this.getPublicKey(); -- } -- if (publicKey == null) { -- throw new Error('Public key unavailable.'); -- } -- -- const encBytes = await this.cryptoFunctionService.rsaEncrypt(data, publicKey, 'sha1'); -- return new EncString(EncryptionType.Rsa2048_OaepSha1_B64, Utils.fromBufferToB64(encBytes)); -- } -- -- async rsaDecrypt(encValue: string, privateKeyValue?: ArrayBuffer): Promise { -- const headerPieces = encValue.split('.'); -- let encType: EncryptionType = null; -- let encPieces: string[]; -- -- if (headerPieces.length === 1) { -- encType = EncryptionType.Rsa2048_OaepSha256_B64; -- encPieces = [headerPieces[0]]; -- } else if (headerPieces.length === 2) { -- try { -- encType = parseInt(headerPieces[0], null); -- encPieces = headerPieces[1].split('|'); -- } catch (e) { -- this.logService.error(e); -- } -- } -- -- switch (encType) { -- case EncryptionType.Rsa2048_OaepSha256_B64: -- case EncryptionType.Rsa2048_OaepSha1_B64: -- // HmacSha256 types are deprecated -- case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64: -- case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64: -- break; -- default: -- throw new Error('encType unavailable.'); -- } -- -- if (encPieces == null || encPieces.length <= 0) { -- throw new Error('encPieces unavailable.'); -- } -- -- const data = Utils.fromB64ToArray(encPieces[0]).buffer; -- const privateKey = privateKeyValue ?? await this.getPrivateKey(); -- if (privateKey == null) { -- throw new Error('No private key.'); -- } -- -- let alg: 'sha1' | 'sha256' = 'sha1'; -- switch (encType) { -- case EncryptionType.Rsa2048_OaepSha256_B64: -- case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64: -- alg = 'sha256'; -- break; -- case EncryptionType.Rsa2048_OaepSha1_B64: -- case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64: -- break; -- default: -- throw new Error('encType unavailable.'); -- } -+ return providerKeys; -+ } - -- return this.cryptoFunctionService.rsaDecrypt(data, privateKey, alg); -+ async getProviderKey(providerId: string): Promise { -+ if (providerId == null) { -+ return null; - } - -- async decryptToBytes(encString: EncString, key?: SymmetricCryptoKey): Promise { -- const iv = Utils.fromB64ToArray(encString.iv).buffer; -- const data = Utils.fromB64ToArray(encString.data).buffer; -- const mac = encString.mac ? Utils.fromB64ToArray(encString.mac).buffer : null; -- const decipher = await this.aesDecryptToBytes(encString.encryptionType, data, iv, mac, key); -- if (decipher == null) { -- return null; -- } -- -- return decipher; -+ const providerKeys = await this.getProviderKeys(); -+ if (providerKeys == null || !providerKeys.has(providerId)) { -+ return null; - } - -- async decryptToUtf8(encString: EncString, key?: SymmetricCryptoKey): Promise { -- return await this.aesDecryptToUtf8(encString.encryptionType, encString.data, -- encString.iv, encString.mac, key); -- } -- -- async decryptFromBytes(encBuf: ArrayBuffer, key: SymmetricCryptoKey): Promise { -- if (encBuf == null) { -- throw new Error('no encBuf.'); -- } -- -- const encBytes = new Uint8Array(encBuf); -- const encType = encBytes[0]; -- let ctBytes: Uint8Array = null; -- let ivBytes: Uint8Array = null; -- let macBytes: Uint8Array = null; -- -- switch (encType) { -- case EncryptionType.AesCbc128_HmacSha256_B64: -- case EncryptionType.AesCbc256_HmacSha256_B64: -- if (encBytes.length <= 49) { // 1 + 16 + 32 + ctLength -- return null; -- } -- -- ivBytes = encBytes.slice(1, 17); -- macBytes = encBytes.slice(17, 49); -- ctBytes = encBytes.slice(49); -- break; -- case EncryptionType.AesCbc256_B64: -- if (encBytes.length <= 17) { // 1 + 16 + ctLength -- return null; -- } -- -- ivBytes = encBytes.slice(1, 17); -- ctBytes = encBytes.slice(17); -- break; -- default: -- return null; -- } -+ return providerKeys.get(providerId); -+ } - -- return await this.aesDecryptToBytes(encType, ctBytes.buffer, ivBytes.buffer, -- macBytes != null ? macBytes.buffer : null, key); -- } -+ async hasKey(): Promise { -+ return ( -+ (await this.hasKeyInMemory()) || -+ (await this.hasKeyStored(KeySuffixOptions.Auto)) || -+ (await this.hasKeyStored(KeySuffixOptions.Biometric)) -+ ); -+ } - -- // EFForg/OpenWireless -- // ref https://github.com/EFForg/OpenWireless/blob/master/app/js/diceware.js -- async randomNumber(min: number, max: number): Promise { -- let rval = 0; -- const range = max - min + 1; -- const bitsNeeded = Math.ceil(Math.log2(range)); -- if (bitsNeeded > 53) { -- throw new Error('We cannot generate numbers larger than 53 bits.'); -- } -+ async hasKeyInMemory(userId?: string): Promise { -+ return (await this.stateService.getCryptoMasterKey({ userId: userId })) != null; -+ } - -- const bytesNeeded = Math.ceil(bitsNeeded / 8); -- const mask = Math.pow(2, bitsNeeded) - 1; -- // 7776 -> (2^13 = 8192) -1 == 8191 or 0x00001111 11111111 -+ async hasKeyStored(keySuffix: KeySuffixOptions, userId?: string): Promise { -+ const key = -+ keySuffix === KeySuffixOptions.Auto -+ ? await this.stateService.getCryptoMasterKeyAuto({ userId: userId }) -+ : await this.stateService.hasCryptoMasterKeyBiometric({ userId: userId }); - -- // Fill a byte array with N random numbers -- const byteArray = new Uint8Array(await this.cryptoFunctionService.randomBytes(bytesNeeded)); -+ return key != null; -+ } - -- let p = (bytesNeeded - 1) * 8; -- for (let i = 0; i < bytesNeeded; i++) { -- rval += byteArray[i] * Math.pow(2, p); -- p -= 8; -- } -+ async hasEncKey(): Promise { -+ return (await this.stateService.getEncryptedCryptoSymmetricKey()) != null; -+ } - -- // Use & to apply the mask and reduce the number of recursive lookups -- // tslint:disable-next-line -- rval = rval & mask; -- -- if (rval >= range) { -- // Integer out of acceptable range -- return this.randomNumber(min, max); -- } -- -- // Return an integer that falls within the range -- return min + rval; -+ async clearKey(clearSecretStorage: boolean = true, userId?: string): Promise { -+ await this.stateService.setCryptoMasterKey(null, { userId: userId }); -+ await this.stateService.setLegacyEtmKey(null, { userId: userId }); -+ if (clearSecretStorage) { -+ await this.clearSecretKeyStore(userId); - } -+ } - -- async validateKey(key: SymmetricCryptoKey) { -- try { -- const encPrivateKey = await this.storageService.get(Keys.encPrivateKey); -- const encKey = await this.getEncKey(key); -- if (encPrivateKey == null || encKey == null) { -- return false; -- } -- -- const privateKey = await this.decryptToBytes(new EncString(encPrivateKey), encKey); -- await this.cryptoFunctionService.rsaExtractPublicKey(privateKey); -- } catch (e) { -- return false; -- } -- -- return true; -- } -- -- // Helpers -- -- protected async storeKey(key: SymmetricCryptoKey) { -- if (await this.shouldStoreKey('auto') || await this.shouldStoreKey('biometric')) { -- this.secureStorageService.save(Keys.key, key.keyB64); -- } else { -- this.secureStorageService.remove(Keys.key); -- } -+ async clearStoredKey(keySuffix: KeySuffixOptions) { -+ keySuffix === KeySuffixOptions.Auto -+ ? await this.stateService.setCryptoMasterKeyAuto(null) -+ : await this.stateService.setCryptoMasterKeyBiometric(null); -+ } -+ -+ async clearKeyHash(userId?: string): Promise { -+ return await this.stateService.setKeyHash(null, { userId: userId }); -+ } -+ -+ async clearEncKey(memoryOnly?: boolean, userId?: string): Promise { -+ await this.stateService.setDecryptedCryptoSymmetricKey(null, { userId: userId }); -+ if (!memoryOnly) { -+ await this.stateService.setEncryptedCryptoSymmetricKey(null, { userId: userId }); -+ } -+ } -+ -+ async clearKeyPair(memoryOnly?: boolean, userId?: string): Promise { -+ const keysToClear: Promise[] = [ -+ this.stateService.setDecryptedPrivateKey(null, { userId: userId }), -+ this.stateService.setPublicKey(null, { userId: userId }), -+ ]; -+ if (!memoryOnly) { -+ keysToClear.push(this.stateService.setEncryptedPrivateKey(null, { userId: userId })); -+ } -+ return Promise.all(keysToClear); -+ } -+ -+ async clearOrgKeys(memoryOnly?: boolean, userId?: string): Promise { -+ await this.stateService.setDecryptedOrganizationKeys(null, { userId: userId }); -+ if (!memoryOnly) { -+ await this.stateService.setEncryptedOrganizationKeys(null, { userId: userId }); -+ } -+ } -+ -+ async clearProviderKeys(memoryOnly?: boolean, userId?: string): Promise { -+ await this.stateService.setDecryptedProviderKeys(null, { userId: userId }); -+ if (!memoryOnly) { -+ await this.stateService.setEncryptedProviderKeys(null, { userId: userId }); -+ } -+ } -+ -+ async clearPinProtectedKey(userId?: string): Promise { -+ return await this.stateService.setEncryptedPinProtected(null, { userId: userId }); -+ } -+ -+ async clearKeys(userId?: string): Promise { -+ await this.clearKey(true, userId); -+ await this.clearKeyHash(userId); -+ await this.clearOrgKeys(false, userId); -+ await this.clearProviderKeys(false, userId); -+ await this.clearEncKey(false, userId); -+ await this.clearKeyPair(false, userId); -+ await this.clearPinProtectedKey(userId); -+ } -+ -+ async toggleKey(): Promise { -+ const key = await this.getKey(); -+ -+ await this.setKey(key); -+ } -+ -+ async makeKey( -+ password: string, -+ salt: string, -+ kdf: KdfType, -+ kdfIterations: number -+ ): Promise { -+ let key: ArrayBuffer = null; -+ if (kdf == null || kdf === KdfType.PBKDF2_SHA256) { -+ if (kdfIterations == null) { -+ kdfIterations = 5000; -+ } else if (kdfIterations < 5000) { -+ throw new Error("PBKDF2 iteration minimum is 5000."); -+ } -+ key = await this.cryptoFunctionService.pbkdf2(password, salt, "sha256", kdfIterations); -+ } else { -+ throw new Error("Unknown Kdf."); -+ } -+ return new SymmetricCryptoKey(key); -+ } -+ -+ async makeKeyFromPin( -+ pin: string, -+ salt: string, -+ kdf: KdfType, -+ kdfIterations: number, -+ protectedKeyCs: EncString = null -+ ): Promise { -+ if (protectedKeyCs == null) { -+ const pinProtectedKey = await this.stateService.getEncryptedPinProtected(); -+ if (pinProtectedKey == null) { -+ throw new Error("No PIN protected key found."); -+ } -+ protectedKeyCs = new EncString(pinProtectedKey); -+ } -+ const pinKey = await this.makePinKey(pin, salt, kdf, kdfIterations); -+ const decKey = await this.decryptToBytes(protectedKeyCs, pinKey); -+ return new SymmetricCryptoKey(decKey); -+ } -+ -+ async makeShareKey(): Promise<[EncString, SymmetricCryptoKey]> { -+ const shareKey = await this.cryptoFunctionService.randomBytes(64); -+ const publicKey = await this.getPublicKey(); -+ const encShareKey = await this.rsaEncrypt(shareKey, publicKey); -+ return [encShareKey, new SymmetricCryptoKey(shareKey)]; -+ } -+ -+ async makeKeyPair(key?: SymmetricCryptoKey): Promise<[string, EncString]> { -+ const keyPair = await this.cryptoFunctionService.rsaGenerateKeyPair(2048); -+ const publicB64 = Utils.fromBufferToB64(keyPair[0]); -+ const privateEnc = await this.encrypt(keyPair[1], key); -+ return [publicB64, privateEnc]; -+ } -+ -+ async makePinKey( -+ pin: string, -+ salt: string, -+ kdf: KdfType, -+ kdfIterations: number -+ ): Promise { -+ const pinKey = await this.makeKey(pin, salt, kdf, kdfIterations); -+ return await this.stretchKey(pinKey); -+ } -+ -+ async makeSendKey(keyMaterial: ArrayBuffer): Promise { -+ const sendKey = await this.cryptoFunctionService.hkdf( -+ keyMaterial, -+ "bitwarden-send", -+ "send", -+ 64, -+ "sha256" -+ ); -+ return new SymmetricCryptoKey(sendKey); -+ } -+ -+ async hashPassword( -+ password: string, -+ key: SymmetricCryptoKey, -+ hashPurpose?: HashPurpose -+ ): Promise { -+ if (key == null) { -+ key = await this.getKey(); -+ } -+ if (password == null || key == null) { -+ throw new Error("Invalid parameters."); -+ } -+ -+ const iterations = hashPurpose === HashPurpose.LocalAuthorization ? 2 : 1; -+ const hash = await this.cryptoFunctionService.pbkdf2(key.key, password, "sha256", iterations); -+ return Utils.fromBufferToB64(hash); -+ } -+ -+ async makeEncKey(key: SymmetricCryptoKey): Promise<[SymmetricCryptoKey, EncString]> { -+ const theKey = await this.getKeyForEncryption(key); -+ const encKey = await this.cryptoFunctionService.randomBytes(64); -+ return this.buildEncKey(theKey, encKey); -+ } -+ -+ async remakeEncKey( -+ key: SymmetricCryptoKey, -+ encKey?: SymmetricCryptoKey -+ ): Promise<[SymmetricCryptoKey, EncString]> { -+ if (encKey == null) { -+ encKey = await this.getEncKey(); -+ } -+ return this.buildEncKey(key, encKey.key); -+ } -+ -+ async encrypt(plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey): Promise { -+ if (plainValue == null) { -+ return Promise.resolve(null); -+ } -+ -+ let plainBuf: ArrayBuffer; -+ if (typeof plainValue === "string") { -+ plainBuf = Utils.fromUtf8ToArray(plainValue).buffer; -+ } else { -+ plainBuf = plainValue; -+ } -+ -+ const encObj = await this.aesEncrypt(plainBuf, key); -+ const iv = Utils.fromBufferToB64(encObj.iv); -+ const data = Utils.fromBufferToB64(encObj.data); -+ const mac = encObj.mac != null ? Utils.fromBufferToB64(encObj.mac) : null; -+ return new EncString(encObj.key.encType, data, iv, mac); -+ } -+ -+ async encryptToBytes(plainValue: ArrayBuffer, key?: SymmetricCryptoKey): Promise { -+ const encValue = await this.aesEncrypt(plainValue, key); -+ let macLen = 0; -+ if (encValue.mac != null) { -+ macLen = encValue.mac.byteLength; -+ } -+ -+ const encBytes = new Uint8Array(1 + encValue.iv.byteLength + macLen + encValue.data.byteLength); -+ encBytes.set([encValue.key.encType]); -+ encBytes.set(new Uint8Array(encValue.iv), 1); -+ if (encValue.mac != null) { -+ encBytes.set(new Uint8Array(encValue.mac), 1 + encValue.iv.byteLength); -+ } -+ -+ encBytes.set(new Uint8Array(encValue.data), 1 + encValue.iv.byteLength + macLen); -+ return new EncArrayBuffer(encBytes.buffer); -+ } -+ -+ async rsaEncrypt(data: ArrayBuffer, publicKey?: ArrayBuffer): Promise { -+ if (publicKey == null) { -+ publicKey = await this.getPublicKey(); -+ } -+ if (publicKey == null) { -+ throw new Error("Public key unavailable."); -+ } -+ -+ const encBytes = await this.cryptoFunctionService.rsaEncrypt(data, publicKey, "sha1"); -+ return new EncString(EncryptionType.Rsa2048_OaepSha1_B64, Utils.fromBufferToB64(encBytes)); -+ } -+ -+ async rsaDecrypt(encValue: string, privateKeyValue?: ArrayBuffer): Promise { -+ const headerPieces = encValue.split("."); -+ let encType: EncryptionType = null; -+ let encPieces: string[]; -+ -+ if (headerPieces.length === 1) { -+ encType = EncryptionType.Rsa2048_OaepSha256_B64; -+ encPieces = [headerPieces[0]]; -+ } else if (headerPieces.length === 2) { -+ try { -+ encType = parseInt(headerPieces[0], null); -+ encPieces = headerPieces[1].split("|"); -+ } catch (e) { -+ this.logService.error(e); -+ } -+ } -+ -+ switch (encType) { -+ case EncryptionType.Rsa2048_OaepSha256_B64: -+ case EncryptionType.Rsa2048_OaepSha1_B64: -+ // HmacSha256 types are deprecated -+ case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64: -+ case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64: -+ break; -+ default: -+ throw new Error("encType unavailable."); -+ } -+ -+ if (encPieces == null || encPieces.length <= 0) { -+ throw new Error("encPieces unavailable."); -+ } -+ -+ const data = Utils.fromB64ToArray(encPieces[0]).buffer; -+ const privateKey = privateKeyValue ?? (await this.getPrivateKey()); -+ if (privateKey == null) { -+ throw new Error("No private key."); -+ } -+ -+ let alg: "sha1" | "sha256" = "sha1"; -+ switch (encType) { -+ case EncryptionType.Rsa2048_OaepSha256_B64: -+ case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64: -+ alg = "sha256"; -+ break; -+ case EncryptionType.Rsa2048_OaepSha1_B64: -+ case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64: -+ break; -+ default: -+ throw new Error("encType unavailable."); -+ } -+ -+ return this.cryptoFunctionService.rsaDecrypt(data, privateKey, alg); -+ } -+ -+ async decryptToBytes(encString: EncString, key?: SymmetricCryptoKey): Promise { -+ const iv = Utils.fromB64ToArray(encString.iv).buffer; -+ const data = Utils.fromB64ToArray(encString.data).buffer; -+ const mac = encString.mac ? Utils.fromB64ToArray(encString.mac).buffer : null; -+ const decipher = await this.aesDecryptToBytes(encString.encryptionType, data, iv, mac, key); -+ if (decipher == null) { -+ return null; -+ } -+ -+ return decipher; -+ } -+ -+ async decryptToUtf8(encString: EncString, key?: SymmetricCryptoKey): Promise { -+ return await this.aesDecryptToUtf8( -+ encString.encryptionType, -+ encString.data, -+ encString.iv, -+ encString.mac, -+ key -+ ); -+ } -+ -+ async decryptFromBytes(encBuf: ArrayBuffer, key: SymmetricCryptoKey): Promise { -+ if (encBuf == null) { -+ throw new Error("no encBuf."); -+ } -+ -+ const encBytes = new Uint8Array(encBuf); -+ const encType = encBytes[0]; -+ let ctBytes: Uint8Array = null; -+ let ivBytes: Uint8Array = null; -+ let macBytes: Uint8Array = null; -+ -+ switch (encType) { -+ case EncryptionType.AesCbc128_HmacSha256_B64: -+ case EncryptionType.AesCbc256_HmacSha256_B64: -+ if (encBytes.length <= 49) { -+ // 1 + 16 + 32 + ctLength -+ return null; -+ } -+ -+ ivBytes = encBytes.slice(1, 17); -+ macBytes = encBytes.slice(17, 49); -+ ctBytes = encBytes.slice(49); -+ break; -+ case EncryptionType.AesCbc256_B64: -+ if (encBytes.length <= 17) { -+ // 1 + 16 + ctLength -+ return null; -+ } -+ -+ ivBytes = encBytes.slice(1, 17); -+ ctBytes = encBytes.slice(17); -+ break; -+ default: -+ return null; - } - -- protected async shouldStoreKey(keySuffix: KeySuffixOptions) { -- let shouldStoreKey = false; -- if (keySuffix === 'auto') { -- const vaultTimeout = await this.storageService.get(ConstantsService.vaultTimeoutKey); -- shouldStoreKey = vaultTimeout == null; -- } else if (keySuffix === 'biometric') { -- const biometricUnlock = await this.storageService.get(ConstantsService.biometricUnlockKey); -- shouldStoreKey = biometricUnlock && this.platformUtilService.supportsSecureStorage(); -- } -- return shouldStoreKey; -- } -+ return await this.aesDecryptToBytes( -+ encType, -+ ctBytes.buffer, -+ ivBytes.buffer, -+ macBytes != null ? macBytes.buffer : null, -+ key -+ ); -+ } - -- protected retrieveKeyFromStorage(keySuffix: KeySuffixOptions) { -- return this.secureStorageService.get(Keys.key, { keySuffix: keySuffix }); -+ // EFForg/OpenWireless -+ // ref https://github.com/EFForg/OpenWireless/blob/master/app/js/diceware.js -+ async randomNumber(min: number, max: number): Promise { -+ let rval = 0; -+ const range = max - min + 1; -+ const bitsNeeded = Math.ceil(Math.log2(range)); -+ if (bitsNeeded > 53) { -+ throw new Error("We cannot generate numbers larger than 53 bits."); - } - -- private async aesEncrypt(data: ArrayBuffer, key: SymmetricCryptoKey): Promise { -- const obj = new EncryptedObject(); -- obj.key = await this.getKeyForEncryption(key); -- obj.iv = await this.cryptoFunctionService.randomBytes(16); -- obj.data = await this.cryptoFunctionService.aesEncrypt(data, obj.iv, obj.key.encKey); -+ const bytesNeeded = Math.ceil(bitsNeeded / 8); -+ const mask = Math.pow(2, bitsNeeded) - 1; -+ // 7776 -> (2^13 = 8192) -1 == 8191 or 0x00001111 11111111 - -- if (obj.key.macKey != null) { -- const macData = new Uint8Array(obj.iv.byteLength + obj.data.byteLength); -- macData.set(new Uint8Array(obj.iv), 0); -- macData.set(new Uint8Array(obj.data), obj.iv.byteLength); -- obj.mac = await this.cryptoFunctionService.hmac(macData.buffer, obj.key.macKey, 'sha256'); -- } -+ // Fill a byte array with N random numbers -+ const byteArray = new Uint8Array(await this.cryptoFunctionService.randomBytes(bytesNeeded)); - -- return obj; -+ let p = (bytesNeeded - 1) * 8; -+ for (let i = 0; i < bytesNeeded; i++) { -+ rval += byteArray[i] * Math.pow(2, p); -+ p -= 8; - } - -- private async aesDecryptToUtf8(encType: EncryptionType, data: string, iv: string, mac: string, -- key: SymmetricCryptoKey): Promise { -- const keyForEnc = await this.getKeyForEncryption(key); -- const theKey = this.resolveLegacyKey(encType, keyForEnc); -- -- if (theKey.macKey != null && mac == null) { -- this.logService.error('mac required.'); -- return null; -- } -- -- if (theKey.encType !== encType) { -- this.logService.error('encType unavailable.'); -- return null; -- } -- -- const fastParams = this.cryptoFunctionService.aesDecryptFastParameters(data, iv, mac, theKey); -- if (fastParams.macKey != null && fastParams.mac != null) { -- const computedMac = await this.cryptoFunctionService.hmacFast(fastParams.macData, -- fastParams.macKey, 'sha256'); -- const macsEqual = await this.cryptoFunctionService.compareFast(fastParams.mac, computedMac); -- if (!macsEqual) { -- this.logService.error('mac failed.'); -- return null; -- } -- } -+ // Use & to apply the mask and reduce the number of recursive lookups -+ // tslint:disable-next-line -+ rval = rval & mask; - -- return this.cryptoFunctionService.aesDecryptFast(fastParams); -+ if (rval >= range) { -+ // Integer out of acceptable range -+ return this.randomNumber(min, max); - } - -- private async aesDecryptToBytes(encType: EncryptionType, data: ArrayBuffer, iv: ArrayBuffer, -- mac: ArrayBuffer, key: SymmetricCryptoKey): Promise { -- const keyForEnc = await this.getKeyForEncryption(key); -- const theKey = this.resolveLegacyKey(encType, keyForEnc); -+ // Return an integer that falls within the range -+ return min + rval; -+ } - -- if (theKey.macKey != null && mac == null) { -- return null; -- } -- -- if (theKey.encType !== encType) { -- return null; -- } -- -- if (theKey.macKey != null && mac != null) { -- const macData = new Uint8Array(iv.byteLength + data.byteLength); -- macData.set(new Uint8Array(iv), 0); -- macData.set(new Uint8Array(data), iv.byteLength); -- const computedMac = await this.cryptoFunctionService.hmac(macData.buffer, theKey.macKey, 'sha256'); -- if (computedMac === null) { -- return null; -- } -- -- const macsMatch = await this.cryptoFunctionService.compare(mac, computedMac); -- if (!macsMatch) { -- this.logService.error('mac failed.'); -- return null; -- } -- } -- -- return await this.cryptoFunctionService.aesDecrypt(data, iv, theKey.encKey); -+ async validateKey(key: SymmetricCryptoKey) { -+ try { -+ const encPrivateKey = await this.stateService.getEncryptedPrivateKey(); -+ const encKey = await this.getEncKey(key); -+ if (encPrivateKey == null || encKey == null) { -+ return false; -+ } -+ -+ const privateKey = await this.decryptToBytes(new EncString(encPrivateKey), encKey); -+ await this.cryptoFunctionService.rsaExtractPublicKey(privateKey); -+ } catch (e) { -+ return false; -+ } -+ -+ return true; -+ } -+ -+ // Helpers -+ protected async storeKey(key: SymmetricCryptoKey, userId?: string) { -+ if (await this.shouldStoreKey(KeySuffixOptions.Auto, userId)) { -+ await this.stateService.setCryptoMasterKeyAuto(key.keyB64, { userId: userId }); -+ } else if (await this.shouldStoreKey(KeySuffixOptions.Biometric, userId)) { -+ await this.stateService.setCryptoMasterKeyBiometric(key.keyB64, { userId: userId }); -+ } else { -+ await this.stateService.setCryptoMasterKeyAuto(null, { userId: userId }); -+ await this.stateService.setCryptoMasterKeyBiometric(null, { userId: userId }); -+ } -+ } -+ -+ protected async shouldStoreKey(keySuffix: KeySuffixOptions, userId?: string) { -+ let shouldStoreKey = false; -+ if (keySuffix === KeySuffixOptions.Auto) { -+ const vaultTimeout = await this.stateService.getVaultTimeout({ userId: userId }); -+ shouldStoreKey = vaultTimeout == null; -+ } else if (keySuffix === KeySuffixOptions.Biometric) { -+ const biometricUnlock = await this.stateService.getBiometricUnlock({ userId: userId }); -+ shouldStoreKey = biometricUnlock && this.platformUtilService.supportsSecureStorage(); -+ } -+ return shouldStoreKey; -+ } -+ -+ protected async retrieveKeyFromStorage(keySuffix: KeySuffixOptions, userId?: string) { -+ return keySuffix === KeySuffixOptions.Auto -+ ? await this.stateService.getCryptoMasterKeyAuto({ userId: userId }) -+ : await this.stateService.getCryptoMasterKeyBiometric({ userId: userId }); -+ } -+ -+ private async aesEncrypt(data: ArrayBuffer, key: SymmetricCryptoKey): Promise { -+ const obj = new EncryptedObject(); -+ obj.key = await this.getKeyForEncryption(key); -+ obj.iv = await this.cryptoFunctionService.randomBytes(16); -+ obj.data = await this.cryptoFunctionService.aesEncrypt(data, obj.iv, obj.key.encKey); -+ -+ if (obj.key.macKey != null) { -+ const macData = new Uint8Array(obj.iv.byteLength + obj.data.byteLength); -+ macData.set(new Uint8Array(obj.iv), 0); -+ macData.set(new Uint8Array(obj.data), obj.iv.byteLength); -+ obj.mac = await this.cryptoFunctionService.hmac(macData.buffer, obj.key.macKey, "sha256"); -+ } -+ -+ return obj; -+ } -+ -+ private async aesDecryptToUtf8( -+ encType: EncryptionType, -+ data: string, -+ iv: string, -+ mac: string, -+ key: SymmetricCryptoKey -+ ): Promise { -+ const keyForEnc = await this.getKeyForEncryption(key); -+ const theKey = await this.resolveLegacyKey(encType, keyForEnc); -+ -+ if (theKey.macKey != null && mac == null) { -+ this.logService.error("mac required."); -+ return null; -+ } -+ -+ if (theKey.encType !== encType) { -+ this.logService.error("encType unavailable."); -+ return null; -+ } -+ -+ const fastParams = this.cryptoFunctionService.aesDecryptFastParameters(data, iv, mac, theKey); -+ if (fastParams.macKey != null && fastParams.mac != null) { -+ const computedMac = await this.cryptoFunctionService.hmacFast( -+ fastParams.macData, -+ fastParams.macKey, -+ "sha256" -+ ); -+ const macsEqual = await this.cryptoFunctionService.compareFast(fastParams.mac, computedMac); -+ if (!macsEqual) { -+ this.logService.error("mac failed."); -+ return null; -+ } - } - -- private async getKeyForEncryption(key?: SymmetricCryptoKey): Promise { -- if (key != null) { -- return key; -- } -+ return this.cryptoFunctionService.aesDecryptFast(fastParams); -+ } - -- const encKey = await this.getEncKey(); -- if (encKey != null) { -- return encKey; -- } -+ private async aesDecryptToBytes( -+ encType: EncryptionType, -+ data: ArrayBuffer, -+ iv: ArrayBuffer, -+ mac: ArrayBuffer, -+ key: SymmetricCryptoKey -+ ): Promise { -+ const keyForEnc = await this.getKeyForEncryption(key); -+ const theKey = await this.resolveLegacyKey(encType, keyForEnc); - -- return await this.getKey(); -+ if (theKey.macKey != null && mac == null) { -+ return null; - } - -- private resolveLegacyKey(encType: EncryptionType, key: SymmetricCryptoKey): SymmetricCryptoKey { -- if (encType === EncryptionType.AesCbc128_HmacSha256_B64 && -- key.encType === EncryptionType.AesCbc256_B64) { -- // Old encrypt-then-mac scheme, make a new key -- if (this.legacyEtmKey == null) { -- this.legacyEtmKey = new SymmetricCryptoKey(key.key, EncryptionType.AesCbc128_HmacSha256_B64); -- } -- return this.legacyEtmKey; -- } -- -- return key; -- } -- -- private async stretchKey(key: SymmetricCryptoKey): Promise { -- const newKey = new Uint8Array(64); -- const encKey = await this.cryptoFunctionService.hkdfExpand(key.key, 'enc', 32, 'sha256'); -- const macKey = await this.cryptoFunctionService.hkdfExpand(key.key, 'mac', 32, 'sha256'); -- newKey.set(new Uint8Array(encKey)); -- newKey.set(new Uint8Array(macKey), 32); -- return new SymmetricCryptoKey(newKey.buffer); -+ if (theKey.encType !== encType) { -+ return null; - } - -- private async hashPhrase(hash: ArrayBuffer, minimumEntropy: number = 64) { -- const entropyPerWord = Math.log(EEFLongWordList.length) / Math.log(2); -- let numWords = Math.ceil(minimumEntropy / entropyPerWord); -- -- const hashArr = Array.from(new Uint8Array(hash)); -- const entropyAvailable = hashArr.length * 4; -- if (numWords * entropyPerWord > entropyAvailable) { -- throw new Error('Output entropy of hash function is too small'); -- } -+ if (theKey.macKey != null && mac != null) { -+ const macData = new Uint8Array(iv.byteLength + data.byteLength); -+ macData.set(new Uint8Array(iv), 0); -+ macData.set(new Uint8Array(data), iv.byteLength); -+ const computedMac = await this.cryptoFunctionService.hmac( -+ macData.buffer, -+ theKey.macKey, -+ "sha256" -+ ); -+ if (computedMac === null) { -+ return null; -+ } - -- const phrase: string[] = []; -- let hashNumber = bigInt.fromArray(hashArr, 256); -- while (numWords--) { -- const remainder = hashNumber.mod(EEFLongWordList.length); -- hashNumber = hashNumber.divide(EEFLongWordList.length); -- phrase.push(EEFLongWordList[remainder as any]); -- } -- return phrase; -- } -- -- private async buildEncKey(key: SymmetricCryptoKey, encKey: ArrayBuffer) -- : Promise<[SymmetricCryptoKey, EncString]> { -- let encKeyEnc: EncString = null; -- if (key.key.byteLength === 32) { -- const newKey = await this.stretchKey(key); -- encKeyEnc = await this.encrypt(encKey, newKey); -- } else if (key.key.byteLength === 64) { -- encKeyEnc = await this.encrypt(encKey, key); -- } else { -- throw new Error('Invalid key size.'); -- } -- return [new SymmetricCryptoKey(encKey), encKeyEnc]; -- } -+ const macsMatch = await this.cryptoFunctionService.compare(mac, computedMac); -+ if (!macsMatch) { -+ this.logService.error("mac failed."); -+ return null; -+ } -+ } -+ -+ return await this.cryptoFunctionService.aesDecrypt(data, iv, theKey.encKey); -+ } -+ -+ private async getKeyForEncryption(key?: SymmetricCryptoKey): Promise { -+ if (key != null) { -+ return key; -+ } -+ -+ const encKey = await this.getEncKey(); -+ if (encKey != null) { -+ return encKey; -+ } -+ -+ return await this.getKey(); -+ } -+ -+ private async resolveLegacyKey( -+ encType: EncryptionType, -+ key: SymmetricCryptoKey -+ ): Promise { -+ if ( -+ encType === EncryptionType.AesCbc128_HmacSha256_B64 && -+ key.encType === EncryptionType.AesCbc256_B64 -+ ) { -+ // Old encrypt-then-mac scheme, make a new key -+ let legacyKey = await this.stateService.getLegacyEtmKey(); -+ if (legacyKey == null) { -+ legacyKey = new SymmetricCryptoKey(key.key, EncryptionType.AesCbc128_HmacSha256_B64); -+ await this.stateService.setLegacyEtmKey(legacyKey); -+ } -+ return legacyKey; -+ } -+ -+ return key; -+ } -+ -+ private async stretchKey(key: SymmetricCryptoKey): Promise { -+ const newKey = new Uint8Array(64); -+ const encKey = await this.cryptoFunctionService.hkdfExpand(key.key, "enc", 32, "sha256"); -+ const macKey = await this.cryptoFunctionService.hkdfExpand(key.key, "mac", 32, "sha256"); -+ newKey.set(new Uint8Array(encKey)); -+ newKey.set(new Uint8Array(macKey), 32); -+ return new SymmetricCryptoKey(newKey.buffer); -+ } -+ -+ private async hashPhrase(hash: ArrayBuffer, minimumEntropy: number = 64) { -+ const entropyPerWord = Math.log(EEFLongWordList.length) / Math.log(2); -+ let numWords = Math.ceil(minimumEntropy / entropyPerWord); -+ -+ const hashArr = Array.from(new Uint8Array(hash)); -+ const entropyAvailable = hashArr.length * 4; -+ if (numWords * entropyPerWord > entropyAvailable) { -+ throw new Error("Output entropy of hash function is too small"); -+ } -+ -+ const phrase: string[] = []; -+ let hashNumber = bigInt.fromArray(hashArr, 256); -+ while (numWords--) { -+ const remainder = hashNumber.mod(EEFLongWordList.length); -+ hashNumber = hashNumber.divide(EEFLongWordList.length); -+ phrase.push(EEFLongWordList[remainder as any]); -+ } -+ return phrase; -+ } -+ -+ private async buildEncKey( -+ key: SymmetricCryptoKey, -+ encKey: ArrayBuffer -+ ): Promise<[SymmetricCryptoKey, EncString]> { -+ let encKeyEnc: EncString = null; -+ if (key.key.byteLength === 32) { -+ const newKey = await this.stretchKey(key); -+ encKeyEnc = await this.encrypt(encKey, newKey); -+ } else if (key.key.byteLength === 64) { -+ encKeyEnc = await this.encrypt(encKey, key); -+ } else { -+ throw new Error("Invalid key size."); -+ } -+ return [new SymmetricCryptoKey(encKey), encKeyEnc]; -+ } -+ -+ private async clearSecretKeyStore(userId?: string): Promise { -+ await this.stateService.setCryptoMasterKeyAuto(null, { userId: userId }); -+ await this.stateService.setCryptoMasterKeyBiometric(null, { userId: userId }); -+ } - } -diff --git a/jslib/common/src/services/environment.service.ts b/jslib/common/src/services/environment.service.ts -index 8d10995a..28caaf47 100644 ---- a/jslib/common/src/services/environment.service.ts -+++ b/jslib/common/src/services/environment.service.ts -@@ -1,202 +1,189 @@ --import { Observable, Subject } from 'rxjs'; -+import { Observable, Subject } from "rxjs"; - --import { EnvironmentUrls } from '../models/domain/environmentUrls'; -+import { EnvironmentUrls } from "../models/domain/environmentUrls"; - --import { ConstantsService } from './constants.service'; -- --import { EnvironmentService as EnvironmentServiceAbstraction, Urls } from '../abstractions/environment.service'; --import { StorageService } from '../abstractions/storage.service'; -+import { -+ EnvironmentService as EnvironmentServiceAbstraction, -+ Urls, -+} from "../abstractions/environment.service"; -+import { StateService } from "../abstractions/state.service"; - - export class EnvironmentService implements EnvironmentServiceAbstraction { -- -- private readonly urlsSubject = new Subject(); -- urls: Observable = this.urlsSubject; // tslint:disable-line -- -- private baseUrl: string; -- private webVaultUrl: string; -- private apiUrl: string; -- private identityUrl: string; -- private iconsUrl: string; -- private notificationsUrl: string; -- private eventsUrl: string; -- private keyConnectorUrl: string; -- -- constructor(private storageService: StorageService) {} -- -- hasBaseUrl() { -- return this.baseUrl != null; -+ private readonly urlsSubject = new Subject(); -+ urls: Observable = this.urlsSubject; // tslint:disable-line -+ -+ private baseUrl: string; -+ private webVaultUrl: string; -+ private apiUrl: string; -+ private identityUrl: string; -+ private iconsUrl: string; -+ private notificationsUrl: string; -+ private eventsUrl: string; -+ private keyConnectorUrl: string; -+ -+ constructor(private stateService: StateService) { -+ this.stateService.activeAccount.subscribe(async (_userId) => { -+ await this.setUrlsFromStorage(); -+ }); -+ } -+ -+ hasBaseUrl() { -+ return this.baseUrl != null; -+ } -+ -+ getNotificationsUrl() { -+ if (this.notificationsUrl != null) { -+ return this.notificationsUrl; - } - -- getNotificationsUrl() { -- if (this.notificationsUrl != null) { -- return this.notificationsUrl; -- } -- -- if (this.baseUrl != null) { -- return this.baseUrl + '/notifications'; -- } -- -- return 'https://notifications.bitwarden.com'; -+ if (this.baseUrl != null) { -+ return this.baseUrl + "/notifications"; - } - -- getWebVaultUrl() { -- if (this.webVaultUrl != null) { -- return this.webVaultUrl; -- } -+ return "https://notifications.bitwarden.com"; -+ } - -- if (this.baseUrl) { -- return this.baseUrl; -- } -- return 'https://vault.bitwarden.com'; -+ getWebVaultUrl() { -+ if (this.webVaultUrl != null) { -+ return this.webVaultUrl; - } - -- getSendUrl() { -- return this.getWebVaultUrl() === 'https://vault.bitwarden.com' -- ? 'https://send.bitwarden.com/#' -- : this.getWebVaultUrl() + '/#/send/'; -+ if (this.baseUrl) { -+ return this.baseUrl; - } -- -- getIconsUrl() { -- if (this.iconsUrl != null) { -- return this.iconsUrl; -- } -- -- if (this.baseUrl) { -- return this.baseUrl + '/icons'; -- } -- -- return 'https://icons.bitwarden.net'; -+ return "https://vault.bitwarden.com"; -+ } -+ -+ getSendUrl() { -+ return this.getWebVaultUrl() === "https://vault.bitwarden.com" -+ ? "https://send.bitwarden.com/#" -+ : this.getWebVaultUrl() + "/#/send/"; -+ } -+ -+ getIconsUrl() { -+ if (this.iconsUrl != null) { -+ return this.iconsUrl; - } - -- getApiUrl() { -- if (this.apiUrl != null) { -- return this.apiUrl; -- } -- -- if (this.baseUrl) { -- return this.baseUrl + '/api'; -- } -- -- return 'https://api.bitwarden.com'; -+ if (this.baseUrl) { -+ return this.baseUrl + "/icons"; - } - -- getIdentityUrl() { -- if (this.identityUrl != null) { -- return this.identityUrl; -- } -+ return "https://icons.bitwarden.net"; -+ } - -- if (this.baseUrl) { -- return this.baseUrl + '/identity'; -- } -- -- return 'https://identity.bitwarden.com'; -+ getApiUrl() { -+ if (this.apiUrl != null) { -+ return this.apiUrl; - } - -- getEventsUrl() { -- if (this.eventsUrl != null) { -- return this.eventsUrl; -- } -+ if (this.baseUrl) { -+ return this.baseUrl + "/api"; -+ } - -- if (this.baseUrl) { -- return this.baseUrl + '/events'; -- } -+ return "https://api.bitwarden.com"; -+ } - -- return 'https://events.bitwarden.com'; -+ getIdentityUrl() { -+ if (this.identityUrl != null) { -+ return this.identityUrl; - } - -- getKeyConnectorUrl() { -- return this.keyConnectorUrl; -+ if (this.baseUrl) { -+ return this.baseUrl + "/identity"; - } - -- async setUrlsFromStorage(): Promise { -- const urlsObj: any = await this.storageService.get(ConstantsService.environmentUrlsKey); -- const urls = urlsObj || { -- base: null, -- api: null, -- identity: null, -- icons: null, -- notifications: null, -- events: null, -- webVault: null, -- keyConnector: null, -- }; -- -- const envUrls = new EnvironmentUrls(); -- -- if (urls.base) { -- this.baseUrl = envUrls.base = urls.base; -- return; -- } -- -- this.webVaultUrl = urls.webVault; -- this.apiUrl = envUrls.api = urls.api; -- this.identityUrl = envUrls.identity = urls.identity; -- this.iconsUrl = urls.icons; -- this.notificationsUrl = urls.notifications; -- this.eventsUrl = envUrls.events = urls.events; -- this.keyConnectorUrl = urls.keyConnector; -- } -+ return "https://identity.bitwarden.com"; -+ } - -- async setUrls(urls: Urls, saveSettings: boolean = true): Promise { -- urls.base = this.formatUrl(urls.base); -- urls.webVault = this.formatUrl(urls.webVault); -- urls.api = this.formatUrl(urls.api); -- urls.identity = this.formatUrl(urls.identity); -- urls.icons = this.formatUrl(urls.icons); -- urls.notifications = this.formatUrl(urls.notifications); -- urls.events = this.formatUrl(urls.events); -- urls.keyConnector = this.formatUrl(urls.keyConnector); -- -- if (saveSettings) { -- await this.storageService.save(ConstantsService.environmentUrlsKey, { -- base: urls.base, -- api: urls.api, -- identity: urls.identity, -- webVault: urls.webVault, -- icons: urls.icons, -- notifications: urls.notifications, -- events: urls.events, -- keyConnector: urls.keyConnector, -- }); -- } -- -- this.baseUrl = urls.base; -- this.webVaultUrl = urls.webVault; -- this.apiUrl = urls.api; -- this.identityUrl = urls.identity; -- this.iconsUrl = urls.icons; -- this.notificationsUrl = urls.notifications; -- this.eventsUrl = urls.events; -- this.keyConnectorUrl = urls.keyConnector; -- -- this.urlsSubject.next(urls); -- -- return urls; -+ getEventsUrl() { -+ if (this.eventsUrl != null) { -+ return this.eventsUrl; - } - -- getUrls() { -- return { -- base: this.baseUrl, -- webVault: this.webVaultUrl, -- api: this.apiUrl, -- identity: this.identityUrl, -- icons: this.iconsUrl, -- notifications: this.notificationsUrl, -- events: this.eventsUrl, -- keyConnector: this.keyConnectorUrl, -- }; -+ if (this.baseUrl) { -+ return this.baseUrl + "/events"; - } - -- private formatUrl(url: string): string { -- if (url == null || url === '') { -- return null; -- } -- -- url = url.replace(/\/+$/g, ''); -- if (!url.startsWith('http://') && !url.startsWith('https://')) { -- url = 'https://' + url; -- } -+ return "https://events.bitwarden.com"; -+ } -+ -+ getKeyConnectorUrl() { -+ return this.keyConnectorUrl; -+ } -+ -+ async setUrlsFromStorage(): Promise { -+ const urls: any = await this.stateService.getEnvironmentUrls(); -+ const envUrls = new EnvironmentUrls(); -+ -+ this.baseUrl = envUrls.base = urls.base; -+ this.webVaultUrl = urls.webVault; -+ this.apiUrl = envUrls.api = urls.api; -+ this.identityUrl = envUrls.identity = urls.identity; -+ this.iconsUrl = urls.icons; -+ this.notificationsUrl = urls.notifications; -+ this.eventsUrl = envUrls.events = urls.events; -+ this.keyConnectorUrl = urls.keyConnector; -+ } -+ -+ async setUrls(urls: Urls): Promise { -+ urls.base = this.formatUrl(urls.base); -+ urls.webVault = this.formatUrl(urls.webVault); -+ urls.api = this.formatUrl(urls.api); -+ urls.identity = this.formatUrl(urls.identity); -+ urls.icons = this.formatUrl(urls.icons); -+ urls.notifications = this.formatUrl(urls.notifications); -+ urls.events = this.formatUrl(urls.events); -+ urls.keyConnector = this.formatUrl(urls.keyConnector); -+ -+ await this.stateService.setEnvironmentUrls({ -+ base: urls.base, -+ api: urls.api, -+ identity: urls.identity, -+ webVault: urls.webVault, -+ icons: urls.icons, -+ notifications: urls.notifications, -+ events: urls.events, -+ keyConnector: urls.keyConnector, -+ }); -+ -+ this.baseUrl = urls.base; -+ this.webVaultUrl = urls.webVault; -+ this.apiUrl = urls.api; -+ this.identityUrl = urls.identity; -+ this.iconsUrl = urls.icons; -+ this.notificationsUrl = urls.notifications; -+ this.eventsUrl = urls.events; -+ this.keyConnectorUrl = urls.keyConnector; -+ -+ this.urlsSubject.next(urls); -+ -+ return urls; -+ } -+ -+ getUrls() { -+ return { -+ base: this.baseUrl, -+ webVault: this.webVaultUrl, -+ api: this.apiUrl, -+ identity: this.identityUrl, -+ icons: this.iconsUrl, -+ notifications: this.notificationsUrl, -+ events: this.eventsUrl, -+ keyConnector: this.keyConnectorUrl, -+ }; -+ } -+ -+ private formatUrl(url: string): string { -+ if (url == null || url === "") { -+ return null; -+ } - -- return url.trim(); -+ url = url.replace(/\/+$/g, ""); -+ if (!url.startsWith("http://") && !url.startsWith("https://")) { -+ url = "https://" + url; - } -+ -+ return url.trim(); -+ } - } -diff --git a/jslib/common/src/services/event.service.ts b/jslib/common/src/services/event.service.ts -index 452133fa..ca5bb59f 100644 ---- a/jslib/common/src/services/event.service.ts -+++ b/jslib/common/src/services/event.service.ts -@@ -1,96 +1,102 @@ --import { EventType } from '../enums/eventType'; -+import { EventType } from "../enums/eventType"; - --import { EventData } from '../models/data/eventData'; -+import { EventData } from "../models/data/eventData"; - --import { EventRequest } from '../models/request/eventRequest'; -+import { EventRequest } from "../models/request/eventRequest"; - --import { ApiService } from '../abstractions/api.service'; --import { CipherService } from '../abstractions/cipher.service'; --import { EventService as EventServiceAbstraction } from '../abstractions/event.service'; --import { StorageService } from '../abstractions/storage.service'; --import { UserService } from '../abstractions/user.service'; -- --import { LogService } from '../abstractions/log.service'; --import { ConstantsService } from './constants.service'; -+import { ApiService } from "../abstractions/api.service"; -+import { CipherService } from "../abstractions/cipher.service"; -+import { EventService as EventServiceAbstraction } from "../abstractions/event.service"; -+import { LogService } from "../abstractions/log.service"; -+import { OrganizationService } from "../abstractions/organization.service"; -+import { StateService } from "../abstractions/state.service"; - - export class EventService implements EventServiceAbstraction { -- private inited = false; -- -- constructor(private storageService: StorageService, private apiService: ApiService, -- private userService: UserService, private cipherService: CipherService, -- private logService: LogService) { } -+ private inited = false; - -- init(checkOnInterval: boolean) { -- if (this.inited) { -- return; -- } -+ constructor( -+ private apiService: ApiService, -+ private cipherService: CipherService, -+ private stateService: StateService, -+ private logService: LogService, -+ private organizationService: OrganizationService -+ ) {} - -- this.inited = true; -- if (checkOnInterval) { -- this.uploadEvents(); -- setInterval(() => this.uploadEvents(), 60 * 1000); // check every 60 seconds -- } -+ init(checkOnInterval: boolean) { -+ if (this.inited) { -+ return; - } - -- async collect(eventType: EventType, cipherId: string = null, uploadImmediately = false): Promise { -- const authed = await this.userService.isAuthenticated(); -- if (!authed) { -- return; -- } -- const organizations = await this.userService.getAllOrganizations(); -- if (organizations == null) { -- return; -- } -- const orgIds = new Set(organizations.filter(o => o.useEvents).map(o => o.id)); -- if (orgIds.size === 0) { -- return; -- } -- if (cipherId != null) { -- const cipher = await this.cipherService.get(cipherId); -- if (cipher == null || cipher.organizationId == null || !orgIds.has(cipher.organizationId)) { -- return; -- } -- } -- let eventCollection = await this.storageService.get(ConstantsService.eventCollectionKey); -- if (eventCollection == null) { -- eventCollection = []; -- } -- const event = new EventData(); -- event.type = eventType; -- event.cipherId = cipherId; -- event.date = new Date().toISOString(); -- eventCollection.push(event); -- await this.storageService.save(ConstantsService.eventCollectionKey, eventCollection); -- if (uploadImmediately) { -- await this.uploadEvents(); -- } -+ this.inited = true; -+ if (checkOnInterval) { -+ this.uploadEvents(); -+ setInterval(() => this.uploadEvents(), 60 * 1000); // check every 60 seconds - } -+ } - -- async uploadEvents(): Promise { -- const authed = await this.userService.isAuthenticated(); -- if (!authed) { -- return; -- } -- const eventCollection = await this.storageService.get(ConstantsService.eventCollectionKey); -- if (eventCollection == null || eventCollection.length === 0) { -- return; -- } -- const request = eventCollection.map(e => { -- const req = new EventRequest(); -- req.type = e.type; -- req.cipherId = e.cipherId; -- req.date = e.date; -- return req; -- }); -- try { -- await this.apiService.postEventsCollect(request); -- this.clearEvents(); -- } catch (e) { -- this.logService.error(e); -- } -+ async collect( -+ eventType: EventType, -+ cipherId: string = null, -+ uploadImmediately = false -+ ): Promise { -+ const authed = await this.stateService.getIsAuthenticated(); -+ if (!authed) { -+ return; -+ } -+ const organizations = await this.organizationService.getAll(); -+ if (organizations == null) { -+ return; -+ } -+ const orgIds = new Set(organizations.filter((o) => o.useEvents).map((o) => o.id)); -+ if (orgIds.size === 0) { -+ return; -+ } -+ if (cipherId != null) { -+ const cipher = await this.cipherService.get(cipherId); -+ if (cipher == null || cipher.organizationId == null || !orgIds.has(cipher.organizationId)) { -+ return; -+ } - } -+ let eventCollection = await this.stateService.getEventCollection(); -+ if (eventCollection == null) { -+ eventCollection = []; -+ } -+ const event = new EventData(); -+ event.type = eventType; -+ event.cipherId = cipherId; -+ event.date = new Date().toISOString(); -+ eventCollection.push(event); -+ await this.stateService.setEventCollection(eventCollection); -+ if (uploadImmediately) { -+ await this.uploadEvents(); -+ } -+ } - -- async clearEvents(): Promise { -- await this.storageService.remove(ConstantsService.eventCollectionKey); -+ async uploadEvents(userId?: string): Promise { -+ const authed = await this.stateService.getIsAuthenticated({ userId: userId }); -+ if (!authed) { -+ return; -+ } -+ const eventCollection = await this.stateService.getEventCollection({ userId: userId }); -+ if (eventCollection == null || eventCollection.length === 0) { -+ return; - } -+ const request = eventCollection.map((e) => { -+ const req = new EventRequest(); -+ req.type = e.type; -+ req.cipherId = e.cipherId; -+ req.date = e.date; -+ return req; -+ }); -+ try { -+ await this.apiService.postEventsCollect(request); -+ this.clearEvents(userId); -+ } catch (e) { -+ this.logService.error(e); -+ } -+ } -+ -+ async clearEvents(userId?: string): Promise { -+ await this.stateService.setEventCollection(null, { userId: userId }); -+ } - } -diff --git a/jslib/common/src/services/export.service.ts b/jslib/common/src/services/export.service.ts -index 7afb6988..31b1b0b6 100644 ---- a/jslib/common/src/services/export.service.ts -+++ b/jslib/common/src/services/export.service.ts -@@ -1,369 +1,410 @@ --import * as papa from 'papaparse'; -+import * as papa from "papaparse"; - --import { CipherType } from '../enums/cipherType'; -+import { CipherType } from "../enums/cipherType"; - --import { ApiService } from '../abstractions/api.service'; --import { CipherService } from '../abstractions/cipher.service'; --import { CryptoService } from '../abstractions/crypto.service'; --import { ExportService as ExportServiceAbstraction } from '../abstractions/export.service'; --import { FolderService } from '../abstractions/folder.service'; -+import { ApiService } from "../abstractions/api.service"; -+import { CipherService } from "../abstractions/cipher.service"; -+import { CryptoService } from "../abstractions/crypto.service"; -+import { ExportService as ExportServiceAbstraction } from "../abstractions/export.service"; -+import { FolderService } from "../abstractions/folder.service"; - --import { CipherView } from '../models/view/cipherView'; --import { CollectionView } from '../models/view/collectionView'; --import { FolderView } from '../models/view/folderView'; -+import { CipherView } from "../models/view/cipherView"; -+import { CollectionView } from "../models/view/collectionView"; -+import { FolderView } from "../models/view/folderView"; - --import { Cipher } from '../models/domain/cipher'; --import { Collection } from '../models/domain/collection'; --import { Folder } from '../models/domain/folder'; -+import { Cipher } from "../models/domain/cipher"; -+import { Collection } from "../models/domain/collection"; -+import { Folder } from "../models/domain/folder"; - --import { CipherData } from '../models/data/cipherData'; --import { CollectionData } from '../models/data/collectionData'; --import { CollectionDetailsResponse } from '../models/response/collectionResponse'; -+import { CipherData } from "../models/data/cipherData"; -+import { CollectionData } from "../models/data/collectionData"; -+import { CollectionDetailsResponse } from "../models/response/collectionResponse"; - --import { CipherWithIds as CipherExport } from '../models/export/cipherWithIds'; --import { CollectionWithId as CollectionExport } from '../models/export/collectionWithId'; --import { Event } from '../models/export/event'; --import { FolderWithId as FolderExport } from '../models/export/folderWithId'; --import { EventView } from '../models/view/eventView'; -+import { CipherWithIds as CipherExport } from "../models/export/cipherWithIds"; -+import { CollectionWithId as CollectionExport } from "../models/export/collectionWithId"; -+import { Event } from "../models/export/event"; -+import { FolderWithId as FolderExport } from "../models/export/folderWithId"; -+import { EventView } from "../models/view/eventView"; - --import { Utils } from '../misc/utils'; -+import { Utils } from "../misc/utils"; - - export class ExportService implements ExportServiceAbstraction { -- constructor(private folderService: FolderService, private cipherService: CipherService, -- private apiService: ApiService, private cryptoService: CryptoService) { } -- -- async getExport(format: 'csv' | 'json' | 'encrypted_json' = 'csv'): Promise { -- if (format === 'encrypted_json') { -- return this.getEncryptedExport(); -- } else { -- return this.getDecryptedExport(format); -- } -+ constructor( -+ private folderService: FolderService, -+ private cipherService: CipherService, -+ private apiService: ApiService, -+ private cryptoService: CryptoService -+ ) {} -+ -+ async getExport(format: "csv" | "json" | "encrypted_json" = "csv"): Promise { -+ if (format === "encrypted_json") { -+ return this.getEncryptedExport(); -+ } else { -+ return this.getDecryptedExport(format); - } -- -- async getOrganizationExport(organizationId: string, -- format: 'csv' | 'json' | 'encrypted_json' = 'csv'): Promise { -- if (format === 'encrypted_json') { -- return this.getOrganizationEncryptedExport(organizationId); -- } else { -- return this.getOrganizationDecryptedExport(organizationId, format); -- } -+ } -+ -+ async getOrganizationExport( -+ organizationId: string, -+ format: "csv" | "json" | "encrypted_json" = "csv" -+ ): Promise { -+ if (format === "encrypted_json") { -+ return this.getOrganizationEncryptedExport(organizationId); -+ } else { -+ return this.getOrganizationDecryptedExport(organizationId, format); - } -+ } -+ -+ async getEventExport(events: EventView[]): Promise { -+ return papa.unparse(events.map((e) => new Event(e))); -+ } -+ -+ getFileName(prefix: string = null, extension: string = "csv"): string { -+ const now = new Date(); -+ const dateString = -+ now.getFullYear() + -+ "" + -+ this.padNumber(now.getMonth() + 1, 2) + -+ "" + -+ this.padNumber(now.getDate(), 2) + -+ this.padNumber(now.getHours(), 2) + -+ "" + -+ this.padNumber(now.getMinutes(), 2) + -+ this.padNumber(now.getSeconds(), 2); -+ -+ return "bitwarden" + (prefix ? "_" + prefix : "") + "_export_" + dateString + "." + extension; -+ } -+ -+ private async getDecryptedExport(format: "json" | "csv"): Promise { -+ let decFolders: FolderView[] = []; -+ let decCiphers: CipherView[] = []; -+ const promises = []; -+ -+ promises.push( -+ this.folderService.getAllDecrypted().then((folders) => { -+ decFolders = folders; -+ }) -+ ); -+ -+ promises.push( -+ this.cipherService.getAllDecrypted().then((ciphers) => { -+ decCiphers = ciphers.filter((f) => f.deletedDate == null); -+ }) -+ ); -+ -+ await Promise.all(promises); -+ -+ if (format === "csv") { -+ const foldersMap = new Map(); -+ decFolders.forEach((f) => { -+ if (f.id != null) { -+ foldersMap.set(f.id, f); -+ } -+ }); - -- async getEventExport(events: EventView[]): Promise { -- return papa.unparse(events.map(e => new Event(e))); -- } -+ const exportCiphers: any[] = []; -+ decCiphers.forEach((c) => { -+ // only export logins and secure notes -+ if (c.type !== CipherType.Login && c.type !== CipherType.SecureNote) { -+ return; -+ } -+ if (c.organizationId != null) { -+ return; -+ } - -- getFileName(prefix: string = null, extension: string = 'csv'): string { -- const now = new Date(); -- const dateString = -- now.getFullYear() + '' + this.padNumber(now.getMonth() + 1, 2) + '' + this.padNumber(now.getDate(), 2) + -- this.padNumber(now.getHours(), 2) + '' + this.padNumber(now.getMinutes(), 2) + -- this.padNumber(now.getSeconds(), 2); -+ const cipher: any = {}; -+ cipher.folder = -+ c.folderId != null && foldersMap.has(c.folderId) ? foldersMap.get(c.folderId).name : null; -+ cipher.favorite = c.favorite ? 1 : null; -+ this.buildCommonCipher(cipher, c); -+ exportCiphers.push(cipher); -+ }); -+ -+ return papa.unparse(exportCiphers); -+ } else { -+ const jsonDoc: any = { -+ encrypted: false, -+ folders: [], -+ items: [], -+ }; -+ -+ decFolders.forEach((f) => { -+ if (f.id == null) { -+ return; -+ } -+ const folder = new FolderExport(); -+ folder.build(f); -+ jsonDoc.folders.push(folder); -+ }); -+ -+ decCiphers.forEach((c) => { -+ if (c.organizationId != null) { -+ return; -+ } -+ const cipher = new CipherExport(); -+ cipher.build(c); -+ cipher.collectionIds = null; -+ jsonDoc.items.push(cipher); -+ }); - -- return 'bitwarden' + (prefix ? ('_' + prefix) : '') + '_export_' + dateString + '.' + extension; -+ return JSON.stringify(jsonDoc, null, " "); - } -- -- private async getDecryptedExport(format: 'json' | 'csv'): Promise { -- let decFolders: FolderView[] = []; -- let decCiphers: CipherView[] = []; -- const promises = []; -- -- promises.push(this.folderService.getAllDecrypted().then(folders => { -- decFolders = folders; -- })); -- -- promises.push(this.cipherService.getAllDecrypted().then(ciphers => { -- decCiphers = ciphers.filter(f => f.deletedDate == null); -- })); -- -- await Promise.all(promises); -- -- if (format === 'csv') { -- const foldersMap = new Map(); -- decFolders.forEach(f => { -- if (f.id != null) { -- foldersMap.set(f.id, f); -- } -- }); -- -- const exportCiphers: any[] = []; -- decCiphers.forEach(c => { -- // only export logins and secure notes -- if (c.type !== CipherType.Login && c.type !== CipherType.SecureNote) { -- return; -- } -- if (c.organizationId != null) { -- return; -- } -- -- const cipher: any = {}; -- cipher.folder = c.folderId != null && foldersMap.has(c.folderId) ? -- foldersMap.get(c.folderId).name : null; -- cipher.favorite = c.favorite ? 1 : null; -- this.buildCommonCipher(cipher, c); -- exportCiphers.push(cipher); -- }); -- -- return papa.unparse(exportCiphers); -- } else { -- const jsonDoc: any = { -- encrypted: false, -- folders: [], -- items: [], -- }; -- -- decFolders.forEach(f => { -- if (f.id == null) { -- return; -- } -- const folder = new FolderExport(); -- folder.build(f); -- jsonDoc.folders.push(folder); -- }); -- -- decCiphers.forEach(c => { -- if (c.organizationId != null) { -- return; -- } -- const cipher = new CipherExport(); -- cipher.build(c); -- cipher.collectionIds = null; -- jsonDoc.items.push(cipher); -+ } -+ -+ private async getEncryptedExport(): Promise { -+ let folders: Folder[] = []; -+ let ciphers: Cipher[] = []; -+ const promises = []; -+ -+ promises.push( -+ this.folderService.getAll().then((f) => { -+ folders = f; -+ }) -+ ); -+ -+ promises.push( -+ this.cipherService.getAll().then((c) => { -+ ciphers = c.filter((f) => f.deletedDate == null); -+ }) -+ ); -+ -+ await Promise.all(promises); -+ -+ const encKeyValidation = await this.cryptoService.encrypt(Utils.newGuid()); -+ -+ const jsonDoc: any = { -+ encrypted: true, -+ encKeyValidation_DO_NOT_EDIT: encKeyValidation.encryptedString, -+ folders: [], -+ items: [], -+ }; -+ -+ folders.forEach((f) => { -+ if (f.id == null) { -+ return; -+ } -+ const folder = new FolderExport(); -+ folder.build(f); -+ jsonDoc.folders.push(folder); -+ }); -+ -+ ciphers.forEach((c) => { -+ if (c.organizationId != null) { -+ return; -+ } -+ const cipher = new CipherExport(); -+ cipher.build(c); -+ cipher.collectionIds = null; -+ jsonDoc.items.push(cipher); -+ }); -+ -+ return JSON.stringify(jsonDoc, null, " "); -+ } -+ -+ private async getOrganizationDecryptedExport( -+ organizationId: string, -+ format: "json" | "csv" -+ ): Promise { -+ const decCollections: CollectionView[] = []; -+ const decCiphers: CipherView[] = []; -+ const promises = []; -+ -+ promises.push( -+ this.apiService.getCollections(organizationId).then((collections) => { -+ const collectionPromises: any = []; -+ if (collections != null && collections.data != null && collections.data.length > 0) { -+ collections.data.forEach((c) => { -+ const collection = new Collection(new CollectionData(c as CollectionDetailsResponse)); -+ collectionPromises.push( -+ collection.decrypt().then((decCol) => { -+ decCollections.push(decCol); -+ }) -+ ); -+ }); -+ } -+ return Promise.all(collectionPromises); -+ }) -+ ); -+ -+ promises.push( -+ this.apiService.getCiphersOrganization(organizationId).then((ciphers) => { -+ const cipherPromises: any = []; -+ if (ciphers != null && ciphers.data != null && ciphers.data.length > 0) { -+ ciphers.data -+ .filter((c) => c.deletedDate === null) -+ .forEach((c) => { -+ const cipher = new Cipher(new CipherData(c)); -+ cipherPromises.push( -+ cipher.decrypt().then((decCipher) => { -+ decCiphers.push(decCipher); -+ }) -+ ); - }); -- -- return JSON.stringify(jsonDoc, null, ' '); - } -- } -+ return Promise.all(cipherPromises); -+ }) -+ ); -+ -+ await Promise.all(promises); -+ -+ if (format === "csv") { -+ const collectionsMap = new Map(); -+ decCollections.forEach((c) => { -+ collectionsMap.set(c.id, c); -+ }); -+ -+ const exportCiphers: any[] = []; -+ decCiphers.forEach((c) => { -+ // only export logins and secure notes -+ if (c.type !== CipherType.Login && c.type !== CipherType.SecureNote) { -+ return; -+ } - -- private async getEncryptedExport(): Promise { -- let folders: Folder[] = []; -- let ciphers: Cipher[] = []; -- const promises = []; -- -- promises.push(this.folderService.getAll().then(f => { -- folders = f; -- })); -- -- promises.push(this.cipherService.getAll().then(c => { -- ciphers = c.filter(f => f.deletedDate == null); -- })); -- -- await Promise.all(promises); -- -- const encKeyValidation = await this.cryptoService.encrypt(Utils.newGuid()); -- -- const jsonDoc: any = { -- encrypted: true, -- encKeyValidation_DO_NOT_EDIT: encKeyValidation.encryptedString, -- folders: [], -- items: [], -- }; -- -- folders.forEach(f => { -- if (f.id == null) { -- return; -- } -- const folder = new FolderExport(); -- folder.build(f); -- jsonDoc.folders.push(folder); -- }); -- -- ciphers.forEach(c => { -- if (c.organizationId != null) { -- return; -- } -- const cipher = new CipherExport(); -- cipher.build(c); -- cipher.collectionIds = null; -- jsonDoc.items.push(cipher); -- }); -- -- return JSON.stringify(jsonDoc, null, ' '); -+ const cipher: any = {}; -+ cipher.collections = []; -+ if (c.collectionIds != null) { -+ cipher.collections = c.collectionIds -+ .filter((id) => collectionsMap.has(id)) -+ .map((id) => collectionsMap.get(id).name); -+ } -+ this.buildCommonCipher(cipher, c); -+ exportCiphers.push(cipher); -+ }); -+ -+ return papa.unparse(exportCiphers); -+ } else { -+ const jsonDoc: any = { -+ encrypted: false, -+ collections: [], -+ items: [], -+ }; -+ -+ decCollections.forEach((c) => { -+ const collection = new CollectionExport(); -+ collection.build(c); -+ jsonDoc.collections.push(collection); -+ }); -+ -+ decCiphers.forEach((c) => { -+ const cipher = new CipherExport(); -+ cipher.build(c); -+ jsonDoc.items.push(cipher); -+ }); -+ return JSON.stringify(jsonDoc, null, " "); - } -- -- private async getOrganizationDecryptedExport(organizationId: string, format: 'json' | 'csv'): Promise { -- const decCollections: CollectionView[] = []; -- const decCiphers: CipherView[] = []; -- const promises = []; -- -- promises.push(this.apiService.getCollections(organizationId).then(collections => { -- const collectionPromises: any = []; -- if (collections != null && collections.data != null && collections.data.length > 0) { -- collections.data.forEach(c => { -- const collection = new Collection(new CollectionData(c as CollectionDetailsResponse)); -- collectionPromises.push(collection.decrypt().then(decCol => { -- decCollections.push(decCol); -- })); -- }); -- } -- return Promise.all(collectionPromises); -- })); -- -- promises.push(this.apiService.getCiphersOrganization(organizationId).then(ciphers => { -- const cipherPromises: any = []; -- if (ciphers != null && ciphers.data != null && ciphers.data.length > 0) { -- ciphers.data.filter(c => c.deletedDate === null).forEach(c => { -- const cipher = new Cipher(new CipherData(c)); -- cipherPromises.push(cipher.decrypt().then(decCipher => { -- decCiphers.push(decCipher); -- })); -- }); -- } -- return Promise.all(cipherPromises); -- })); -- -- await Promise.all(promises); -- -- if (format === 'csv') { -- const collectionsMap = new Map(); -- decCollections.forEach(c => { -- collectionsMap.set(c.id, c); -- }); -- -- const exportCiphers: any[] = []; -- decCiphers.forEach(c => { -- // only export logins and secure notes -- if (c.type !== CipherType.Login && c.type !== CipherType.SecureNote) { -- return; -- } -- -- const cipher: any = {}; -- cipher.collections = []; -- if (c.collectionIds != null) { -- cipher.collections = c.collectionIds.filter(id => collectionsMap.has(id)) -- .map(id => collectionsMap.get(id).name); -- } -- this.buildCommonCipher(cipher, c); -- exportCiphers.push(cipher); -+ } -+ -+ private async getOrganizationEncryptedExport(organizationId: string): Promise { -+ const collections: Collection[] = []; -+ const ciphers: Cipher[] = []; -+ const promises = []; -+ -+ promises.push( -+ this.apiService.getCollections(organizationId).then((c) => { -+ const collectionPromises: any = []; -+ if (c != null && c.data != null && c.data.length > 0) { -+ c.data.forEach((r) => { -+ const collection = new Collection(new CollectionData(r as CollectionDetailsResponse)); -+ collections.push(collection); -+ }); -+ } -+ return Promise.all(collectionPromises); -+ }) -+ ); -+ -+ promises.push( -+ this.apiService.getCiphersOrganization(organizationId).then((c) => { -+ const cipherPromises: any = []; -+ if (c != null && c.data != null && c.data.length > 0) { -+ c.data -+ .filter((item) => item.deletedDate === null) -+ .forEach((item) => { -+ const cipher = new Cipher(new CipherData(item)); -+ ciphers.push(cipher); - }); -- -- return papa.unparse(exportCiphers); -+ } -+ return Promise.all(cipherPromises); -+ }) -+ ); -+ -+ await Promise.all(promises); -+ -+ const orgKey = await this.cryptoService.getOrgKey(organizationId); -+ const encKeyValidation = await this.cryptoService.encrypt(Utils.newGuid(), orgKey); -+ -+ const jsonDoc: any = { -+ encrypted: true, -+ encKeyValidation_DO_NOT_EDIT: encKeyValidation.encryptedString, -+ collections: [], -+ items: [], -+ }; -+ -+ collections.forEach((c) => { -+ const collection = new CollectionExport(); -+ collection.build(c); -+ jsonDoc.collections.push(collection); -+ }); -+ -+ ciphers.forEach((c) => { -+ const cipher = new CipherExport(); -+ cipher.build(c); -+ jsonDoc.items.push(cipher); -+ }); -+ return JSON.stringify(jsonDoc, null, " "); -+ } -+ -+ private padNumber(num: number, width: number, padCharacter: string = "0"): string { -+ const numString = num.toString(); -+ return numString.length >= width -+ ? numString -+ : new Array(width - numString.length + 1).join(padCharacter) + numString; -+ } -+ -+ private buildCommonCipher(cipher: any, c: CipherView) { -+ cipher.type = null; -+ cipher.name = c.name; -+ cipher.notes = c.notes; -+ cipher.fields = null; -+ cipher.reprompt = c.reprompt; -+ // Login props -+ cipher.login_uri = null; -+ cipher.login_username = null; -+ cipher.login_password = null; -+ cipher.login_totp = null; -+ -+ if (c.fields) { -+ c.fields.forEach((f: any) => { -+ if (!cipher.fields) { -+ cipher.fields = ""; - } else { -- const jsonDoc: any = { -- encrypted: false, -- collections: [], -- items: [], -- }; -- -- decCollections.forEach(c => { -- const collection = new CollectionExport(); -- collection.build(c); -- jsonDoc.collections.push(collection); -- }); -- -- decCiphers.forEach(c => { -- const cipher = new CipherExport(); -- cipher.build(c); -- jsonDoc.items.push(cipher); -- }); -- return JSON.stringify(jsonDoc, null, ' '); -+ cipher.fields += "\n"; - } -- } -- -- private async getOrganizationEncryptedExport(organizationId: string): Promise { -- const collections: Collection[] = []; -- const ciphers: Cipher[] = []; -- const promises = []; -- -- promises.push(this.apiService.getCollections(organizationId).then(c => { -- const collectionPromises: any = []; -- if (c != null && c.data != null && c.data.length > 0) { -- c.data.forEach(r => { -- const collection = new Collection(new CollectionData(r as CollectionDetailsResponse)); -- collections.push(collection); -- }); -- } -- return Promise.all(collectionPromises); -- })); -- -- promises.push(this.apiService.getCiphersOrganization(organizationId).then(c => { -- const cipherPromises: any = []; -- if (c != null && c.data != null && c.data.length > 0) { -- c.data.filter(item => item.deletedDate === null).forEach(item => { -- const cipher = new Cipher(new CipherData(item)); -- ciphers.push(cipher); -- }); -- } -- return Promise.all(cipherPromises); -- })); -- -- await Promise.all(promises); -- -- const orgKey = await this.cryptoService.getOrgKey(organizationId); -- const encKeyValidation = await this.cryptoService.encrypt(Utils.newGuid(), orgKey); -- -- const jsonDoc: any = { -- encrypted: true, -- encKeyValidation_DO_NOT_EDIT: encKeyValidation.encryptedString, -- collections: [], -- items: [], -- }; -- -- collections.forEach(c => { -- const collection = new CollectionExport(); -- collection.build(c); -- jsonDoc.collections.push(collection); -- }); -- -- ciphers.forEach(c => { -- const cipher = new CipherExport(); -- cipher.build(c); -- jsonDoc.items.push(cipher); -- }); -- return JSON.stringify(jsonDoc, null, ' '); -- } - -- private padNumber(num: number, width: number, padCharacter: string = '0'): string { -- const numString = num.toString(); -- return numString.length >= width ? numString : -- new Array(width - numString.length + 1).join(padCharacter) + numString; -+ cipher.fields += (f.name || "") + ": " + f.value; -+ }); - } - -- private buildCommonCipher(cipher: any, c: CipherView) { -- cipher.type = null; -- cipher.name = c.name; -- cipher.notes = c.notes; -- cipher.fields = null; -- cipher.reprompt = c.reprompt; -- // Login props -- cipher.login_uri = null; -- cipher.login_username = null; -- cipher.login_password = null; -- cipher.login_totp = null; -- -- if (c.fields) { -- c.fields.forEach((f: any) => { -- if (!cipher.fields) { -- cipher.fields = ''; -- } else { -- cipher.fields += '\n'; -- } -- -- cipher.fields += ((f.name || '') + ': ' + f.value); -- }); -- } -- -- switch (c.type) { -- case CipherType.Login: -- cipher.type = 'login'; -- cipher.login_username = c.login.username; -- cipher.login_password = c.login.password; -- cipher.login_totp = c.login.totp; -- -- if (c.login.uris) { -- cipher.login_uri = []; -- c.login.uris.forEach(u => { -- cipher.login_uri.push(u.uri); -- }); -- } -- break; -- case CipherType.SecureNote: -- cipher.type = 'note'; -- break; -- default: -- return; -+ switch (c.type) { -+ case CipherType.Login: -+ cipher.type = "login"; -+ cipher.login_username = c.login.username; -+ cipher.login_password = c.login.password; -+ cipher.login_totp = c.login.totp; -+ -+ if (c.login.uris) { -+ cipher.login_uri = []; -+ c.login.uris.forEach((u) => { -+ cipher.login_uri.push(u.uri); -+ }); - } -- -- return cipher; -+ break; -+ case CipherType.SecureNote: -+ cipher.type = "note"; -+ break; -+ default: -+ return; - } -+ -+ return cipher; -+ } - } -diff --git a/jslib/common/src/services/fileUpload.service.ts b/jslib/common/src/services/fileUpload.service.ts -index 1dedc289..516011dd 100644 ---- a/jslib/common/src/services/fileUpload.service.ts -+++ b/jslib/common/src/services/fileUpload.service.ts -@@ -1,79 +1,109 @@ --import { ApiService } from '../abstractions/api.service'; --import { FileUploadService as FileUploadServiceAbstraction } from '../abstractions/fileUpload.service'; --import { LogService } from '../abstractions/log.service'; -+import { ApiService } from "../abstractions/api.service"; -+import { FileUploadService as FileUploadServiceAbstraction } from "../abstractions/fileUpload.service"; -+import { LogService } from "../abstractions/log.service"; - --import { FileUploadType } from '../enums/fileUploadType'; -+import { FileUploadType } from "../enums/fileUploadType"; - --import { EncArrayBuffer } from '../models/domain/encArrayBuffer'; --import { EncString } from '../models/domain/encString'; -+import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; -+import { EncString } from "../models/domain/encString"; - --import { AttachmentUploadDataResponse } from '../models/response/attachmentUploadDataResponse'; --import { SendFileUploadDataResponse } from '../models/response/sendFileUploadDataResponse'; -+import { AttachmentUploadDataResponse } from "../models/response/attachmentUploadDataResponse"; -+import { SendFileUploadDataResponse } from "../models/response/sendFileUploadDataResponse"; - --import { AzureFileUploadService } from './azureFileUpload.service'; --import { BitwardenFileUploadService } from './bitwardenFileUpload.service'; -+import { AzureFileUploadService } from "./azureFileUpload.service"; -+import { BitwardenFileUploadService } from "./bitwardenFileUpload.service"; - - export class FileUploadService implements FileUploadServiceAbstraction { -- private azureFileUploadService: AzureFileUploadService; -- private bitwardenFileUploadService: BitwardenFileUploadService; -+ private azureFileUploadService: AzureFileUploadService; -+ private bitwardenFileUploadService: BitwardenFileUploadService; - -- constructor(private logService: LogService, private apiService: ApiService) { -- this.azureFileUploadService = new AzureFileUploadService(logService); -- this.bitwardenFileUploadService = new BitwardenFileUploadService(apiService); -- } -+ constructor(private logService: LogService, private apiService: ApiService) { -+ this.azureFileUploadService = new AzureFileUploadService(logService); -+ this.bitwardenFileUploadService = new BitwardenFileUploadService(apiService); -+ } - -- async uploadSendFile(uploadData: SendFileUploadDataResponse, fileName: EncString, encryptedFileData: EncArrayBuffer) { -- try { -- switch (uploadData.fileUploadType) { -- case FileUploadType.Direct: -- await this.bitwardenFileUploadService.upload(fileName.encryptedString, encryptedFileData, -- fd => this.apiService.postSendFile(uploadData.sendResponse.id, uploadData.sendResponse.file.id, fd)); -- break; -- case FileUploadType.Azure: -- const renewalCallback = async () => { -- const renewalResponse = await this.apiService.renewSendFileUploadUrl(uploadData.sendResponse.id, -- uploadData.sendResponse.file.id); -- return renewalResponse.url; -- }; -- await this.azureFileUploadService.upload(uploadData.url, encryptedFileData, -- renewalCallback); -- break; -- default: -- throw new Error('Unknown file upload type'); -- } -- } catch (e) { -- await this.apiService.deleteSend(uploadData.sendResponse.id); -- throw e; -- } -+ async uploadSendFile( -+ uploadData: SendFileUploadDataResponse, -+ fileName: EncString, -+ encryptedFileData: EncArrayBuffer -+ ) { -+ try { -+ switch (uploadData.fileUploadType) { -+ case FileUploadType.Direct: -+ await this.bitwardenFileUploadService.upload( -+ fileName.encryptedString, -+ encryptedFileData, -+ (fd) => -+ this.apiService.postSendFile( -+ uploadData.sendResponse.id, -+ uploadData.sendResponse.file.id, -+ fd -+ ) -+ ); -+ break; -+ case FileUploadType.Azure: -+ const renewalCallback = async () => { -+ const renewalResponse = await this.apiService.renewSendFileUploadUrl( -+ uploadData.sendResponse.id, -+ uploadData.sendResponse.file.id -+ ); -+ return renewalResponse.url; -+ }; -+ await this.azureFileUploadService.upload( -+ uploadData.url, -+ encryptedFileData, -+ renewalCallback -+ ); -+ break; -+ default: -+ throw new Error("Unknown file upload type"); -+ } -+ } catch (e) { -+ await this.apiService.deleteSend(uploadData.sendResponse.id); -+ throw e; - } -+ } - -- async uploadCipherAttachment(admin: boolean, uploadData: AttachmentUploadDataResponse, encryptedFileName: EncString, -- encryptedFileData: EncArrayBuffer) { -- const response = admin ? uploadData.cipherMiniResponse : uploadData.cipherResponse; -- try { -- switch (uploadData.fileUploadType) { -- case FileUploadType.Direct: -- await this.bitwardenFileUploadService.upload(encryptedFileName.encryptedString, encryptedFileData, -- fd => this.apiService.postAttachmentFile(response.id, uploadData.attachmentId, fd)); -- break; -- case FileUploadType.Azure: -- const renewalCallback = async () => { -- const renewalResponse = await this.apiService.renewAttachmentUploadUrl(response.id, -- uploadData.attachmentId); -- return renewalResponse.url; -- }; -- await this.azureFileUploadService.upload(uploadData.url, encryptedFileData, renewalCallback); -- break; -- default: -- throw new Error('Unknown file upload type.'); -- } -- } catch (e) { -- if (admin) { -- await this.apiService.deleteCipherAttachmentAdmin(response.id, uploadData.attachmentId); -- } else { -- await this.apiService.deleteCipherAttachment(response.id, uploadData.attachmentId); -- } -- throw e; -- } -+ async uploadCipherAttachment( -+ admin: boolean, -+ uploadData: AttachmentUploadDataResponse, -+ encryptedFileName: EncString, -+ encryptedFileData: EncArrayBuffer -+ ) { -+ const response = admin ? uploadData.cipherMiniResponse : uploadData.cipherResponse; -+ try { -+ switch (uploadData.fileUploadType) { -+ case FileUploadType.Direct: -+ await this.bitwardenFileUploadService.upload( -+ encryptedFileName.encryptedString, -+ encryptedFileData, -+ (fd) => this.apiService.postAttachmentFile(response.id, uploadData.attachmentId, fd) -+ ); -+ break; -+ case FileUploadType.Azure: -+ const renewalCallback = async () => { -+ const renewalResponse = await this.apiService.renewAttachmentUploadUrl( -+ response.id, -+ uploadData.attachmentId -+ ); -+ return renewalResponse.url; -+ }; -+ await this.azureFileUploadService.upload( -+ uploadData.url, -+ encryptedFileData, -+ renewalCallback -+ ); -+ break; -+ default: -+ throw new Error("Unknown file upload type."); -+ } -+ } catch (e) { -+ if (admin) { -+ await this.apiService.deleteCipherAttachmentAdmin(response.id, uploadData.attachmentId); -+ } else { -+ await this.apiService.deleteCipherAttachment(response.id, uploadData.attachmentId); -+ } -+ throw e; - } -+ } - } -diff --git a/jslib/common/src/services/folder.service.ts b/jslib/common/src/services/folder.service.ts -index 0f3ae50e..225c03aa 100644 ---- a/jslib/common/src/services/folder.service.ts -+++ b/jslib/common/src/services/folder.service.ts -@@ -1,209 +1,199 @@ --import { FolderData } from '../models/data/folderData'; -+import { FolderData } from "../models/data/folderData"; - --import { Folder } from '../models/domain/folder'; --import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; --import { TreeNode } from '../models/domain/treeNode'; -+import { Folder } from "../models/domain/folder"; -+import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; -+import { TreeNode } from "../models/domain/treeNode"; - --import { FolderRequest } from '../models/request/folderRequest'; -+import { FolderRequest } from "../models/request/folderRequest"; - --import { FolderResponse } from '../models/response/folderResponse'; -+import { FolderResponse } from "../models/response/folderResponse"; - --import { FolderView } from '../models/view/folderView'; -+import { FolderView } from "../models/view/folderView"; - --import { ApiService } from '../abstractions/api.service'; --import { CipherService } from '../abstractions/cipher.service'; --import { CryptoService } from '../abstractions/crypto.service'; --import { FolderService as FolderServiceAbstraction } from '../abstractions/folder.service'; --import { I18nService } from '../abstractions/i18n.service'; --import { StorageService } from '../abstractions/storage.service'; --import { UserService } from '../abstractions/user.service'; --import { CipherData } from '../models/data/cipherData'; -+import { ApiService } from "../abstractions/api.service"; -+import { CipherService } from "../abstractions/cipher.service"; -+import { CryptoService } from "../abstractions/crypto.service"; -+import { FolderService as FolderServiceAbstraction } from "../abstractions/folder.service"; -+import { I18nService } from "../abstractions/i18n.service"; -+import { StateService } from "../abstractions/state.service"; - --import { ServiceUtils } from '../misc/serviceUtils'; --import { Utils } from '../misc/utils'; -+import { CipherData } from "../models/data/cipherData"; - --const Keys = { -- foldersPrefix: 'folders_', -- ciphersPrefix: 'ciphers_', --}; --const NestingDelimiter = '/'; -+import { ServiceUtils } from "../misc/serviceUtils"; -+import { Utils } from "../misc/utils"; - --export class FolderService implements FolderServiceAbstraction { -- decryptedFolderCache: FolderView[]; -- -- constructor(private cryptoService: CryptoService, private userService: UserService, -- private apiService: ApiService, private storageService: StorageService, -- private i18nService: I18nService, private cipherService: CipherService) { } -- -- clearCache(): void { -- this.decryptedFolderCache = null; -- } -+const NestingDelimiter = "/"; - -- async encrypt(model: FolderView, key?: SymmetricCryptoKey): Promise { -- const folder = new Folder(); -- folder.id = model.id; -- folder.name = await this.cryptoService.encrypt(model.name, key); -- return folder; -+export class FolderService implements FolderServiceAbstraction { -+ constructor( -+ private cryptoService: CryptoService, -+ private apiService: ApiService, -+ private i18nService: I18nService, -+ private cipherService: CipherService, -+ private stateService: StateService -+ ) {} -+ -+ async clearCache(userId?: string): Promise { -+ await this.stateService.setDecryptedFolders(null, { userId: userId }); -+ } -+ -+ async encrypt(model: FolderView, key?: SymmetricCryptoKey): Promise { -+ const folder = new Folder(); -+ folder.id = model.id; -+ folder.name = await this.cryptoService.encrypt(model.name, key); -+ return folder; -+ } -+ -+ async get(id: string): Promise { -+ const folders = await this.stateService.getEncryptedFolders(); -+ if (folders == null || !folders.hasOwnProperty(id)) { -+ return null; - } - -- async get(id: string): Promise { -- const userId = await this.userService.getUserId(); -- const folders = await this.storageService.get<{ [id: string]: FolderData; }>( -- Keys.foldersPrefix + userId); -- if (folders == null || !folders.hasOwnProperty(id)) { -- return null; -- } -+ return new Folder(folders[id]); -+ } - -- return new Folder(folders[id]); -+ async getAll(): Promise { -+ const folders = await this.stateService.getEncryptedFolders(); -+ const response: Folder[] = []; -+ for (const id in folders) { -+ if (folders.hasOwnProperty(id)) { -+ response.push(new Folder(folders[id])); -+ } - } -+ return response; -+ } - -- async getAll(): Promise { -- const userId = await this.userService.getUserId(); -- const folders = await this.storageService.get<{ [id: string]: FolderData; }>( -- Keys.foldersPrefix + userId); -- const response: Folder[] = []; -- for (const id in folders) { -- if (folders.hasOwnProperty(id)) { -- response.push(new Folder(folders[id])); -- } -- } -- return response; -- } -- -- async getAllDecrypted(): Promise { -- if (this.decryptedFolderCache != null) { -- return this.decryptedFolderCache; -- } -- -- const hasKey = await this.cryptoService.hasKey(); -- if (!hasKey) { -- throw new Error('No key.'); -- } -- -- const decFolders: FolderView[] = []; -- const promises: Promise[] = []; -- const folders = await this.getAll(); -- folders.forEach(folder => { -- promises.push(folder.decrypt().then(f => decFolders.push(f))); -- }); -- -- await Promise.all(promises); -- decFolders.sort(Utils.getSortFunction(this.i18nService, 'name')); -- -- const noneFolder = new FolderView(); -- noneFolder.name = this.i18nService.t('noneFolder'); -- decFolders.push(noneFolder); -- -- this.decryptedFolderCache = decFolders; -- return this.decryptedFolderCache; -+ async getAllDecrypted(): Promise { -+ const decryptedFolders = await this.stateService.getDecryptedFolders(); -+ if (decryptedFolders != null) { -+ return decryptedFolders; - } - -- async getAllNested(): Promise[]> { -- const folders = await this.getAllDecrypted(); -- const nodes: TreeNode[] = []; -- folders.forEach(f => { -- const folderCopy = new FolderView(); -- folderCopy.id = f.id; -- folderCopy.revisionDate = f.revisionDate; -- const parts = f.name != null ? f.name.replace(/^\/+|\/+$/g, '').split(NestingDelimiter) : []; -- ServiceUtils.nestedTraverse(nodes, 0, parts, folderCopy, null, NestingDelimiter); -- }); -- return nodes; -+ const hasKey = await this.cryptoService.hasKey(); -+ if (!hasKey) { -+ throw new Error("No key."); - } - -- async getNested(id: string): Promise> { -- const folders = await this.getAllNested(); -- return ServiceUtils.getTreeNodeObject(folders, id) as TreeNode; -+ const decFolders: FolderView[] = []; -+ const promises: Promise[] = []; -+ const folders = await this.getAll(); -+ folders.forEach((folder) => { -+ promises.push(folder.decrypt().then((f) => decFolders.push(f))); -+ }); -+ -+ await Promise.all(promises); -+ decFolders.sort(Utils.getSortFunction(this.i18nService, "name")); -+ -+ const noneFolder = new FolderView(); -+ noneFolder.name = this.i18nService.t("noneFolder"); -+ decFolders.push(noneFolder); -+ -+ await this.stateService.setDecryptedFolders(decFolders); -+ return decFolders; -+ } -+ -+ async getAllNested(): Promise[]> { -+ const folders = await this.getAllDecrypted(); -+ const nodes: TreeNode[] = []; -+ folders.forEach((f) => { -+ const folderCopy = new FolderView(); -+ folderCopy.id = f.id; -+ folderCopy.revisionDate = f.revisionDate; -+ const parts = f.name != null ? f.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : []; -+ ServiceUtils.nestedTraverse(nodes, 0, parts, folderCopy, null, NestingDelimiter); -+ }); -+ return nodes; -+ } -+ -+ async getNested(id: string): Promise> { -+ const folders = await this.getAllNested(); -+ return ServiceUtils.getTreeNodeObject(folders, id) as TreeNode; -+ } -+ -+ async saveWithServer(folder: Folder): Promise { -+ const request = new FolderRequest(folder); -+ -+ let response: FolderResponse; -+ if (folder.id == null) { -+ response = await this.apiService.postFolder(request); -+ folder.id = response.id; -+ } else { -+ response = await this.apiService.putFolder(folder.id, request); - } - -- async saveWithServer(folder: Folder): Promise { -- const request = new FolderRequest(folder); -+ const userId = await this.stateService.getUserId(); -+ const data = new FolderData(response, userId); -+ await this.upsert(data); -+ } - -- let response: FolderResponse; -- if (folder.id == null) { -- response = await this.apiService.postFolder(request); -- folder.id = response.id; -- } else { -- response = await this.apiService.putFolder(folder.id, request); -- } -+ async upsert(folder: FolderData | FolderData[]): Promise { -+ let folders = await this.stateService.getEncryptedFolders(); -+ if (folders == null) { -+ folders = {}; -+ } - -- const userId = await this.userService.getUserId(); -- const data = new FolderData(response, userId); -- await this.upsert(data); -+ if (folder instanceof FolderData) { -+ const f = folder as FolderData; -+ folders[f.id] = f; -+ } else { -+ (folder as FolderData[]).forEach((f) => { -+ folders[f.id] = f; -+ }); - } - -- async upsert(folder: FolderData | FolderData[]): Promise { -- const userId = await this.userService.getUserId(); -- let folders = await this.storageService.get<{ [id: string]: FolderData; }>( -- Keys.foldersPrefix + userId); -- if (folders == null) { -- folders = {}; -- } -+ await this.stateService.setDecryptedFolders(null); -+ await this.stateService.setEncryptedFolders(folders); -+ } - -- if (folder instanceof FolderData) { -- const f = folder as FolderData; -- folders[f.id] = f; -- } else { -- (folder as FolderData[]).forEach(f => { -- folders[f.id] = f; -- }); -- } -+ async replace(folders: { [id: string]: FolderData }): Promise { -+ await this.stateService.setDecryptedFolders(null); -+ await this.stateService.setEncryptedFolders(folders); -+ } - -- await this.storageService.save(Keys.foldersPrefix + userId, folders); -- this.decryptedFolderCache = null; -- } -+ async clear(userId?: string): Promise { -+ await this.stateService.setDecryptedFolders(null, { userId: userId }); -+ await this.stateService.setEncryptedFolders(null, { userId: userId }); -+ } - -- async replace(folders: { [id: string]: FolderData; }): Promise { -- const userId = await this.userService.getUserId(); -- await this.storageService.save(Keys.foldersPrefix + userId, folders); -- this.decryptedFolderCache = null; -+ async delete(id: string | string[]): Promise { -+ const folders = await this.stateService.getEncryptedFolders(); -+ if (folders == null) { -+ return; - } - -- async clear(userId: string): Promise { -- await this.storageService.remove(Keys.foldersPrefix + userId); -- this.decryptedFolderCache = null; -+ if (typeof id === "string") { -+ if (folders[id] == null) { -+ return; -+ } -+ delete folders[id]; -+ } else { -+ (id as string[]).forEach((i) => { -+ delete folders[i]; -+ }); - } - -- async delete(id: string | string[]): Promise { -- const userId = await this.userService.getUserId(); -- const folders = await this.storageService.get<{ [id: string]: FolderData; }>( -- Keys.foldersPrefix + userId); -- if (folders == null) { -- return; -- } -- -- if (typeof id === 'string') { -- if (folders[id] == null) { -- return; -- } -- delete folders[id]; -- } else { -- (id as string[]).forEach(i => { -- delete folders[i]; -- }); -- } -- -- await this.storageService.save(Keys.foldersPrefix + userId, folders); -- this.decryptedFolderCache = null; -- -- // Items in a deleted folder are re-assigned to "No Folder" -- const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>(Keys.ciphersPrefix + userId); -- if (ciphers != null) { -- const updates: CipherData[] = []; -- for (const cId in ciphers) { -- if (ciphers[cId].folderId === id) { -- ciphers[cId].folderId = null; -- updates.push(ciphers[cId]); -- } -- } -- if (updates.length > 0) { -- this.cipherService.upsert(updates); -- } -+ await this.stateService.setDecryptedFolders(null); -+ await this.stateService.setEncryptedFolders(folders); -+ -+ // Items in a deleted folder are re-assigned to "No Folder" -+ const ciphers = await this.stateService.getEncryptedCiphers(); -+ if (ciphers != null) { -+ const updates: CipherData[] = []; -+ for (const cId in ciphers) { -+ if (ciphers[cId].folderId === id) { -+ ciphers[cId].folderId = null; -+ updates.push(ciphers[cId]); - } -+ } -+ if (updates.length > 0) { -+ this.cipherService.upsert(updates); -+ } - } -+ } - -- async deleteWithServer(id: string): Promise { -- await this.apiService.deleteFolder(id); -- await this.delete(id); -- } -+ async deleteWithServer(id: string): Promise { -+ await this.apiService.deleteFolder(id); -+ await this.delete(id); -+ } - } -diff --git a/jslib/common/src/services/i18n.service.ts b/jslib/common/src/services/i18n.service.ts -index b67ee001..698bc297 100644 ---- a/jslib/common/src/services/i18n.service.ts -+++ b/jslib/common/src/services/i18n.service.ts -@@ -1,153 +1,160 @@ --import { I18nService as I18nServiceAbstraction } from '../abstractions/i18n.service'; -+import { I18nService as I18nServiceAbstraction } from "../abstractions/i18n.service"; - - export class I18nService implements I18nServiceAbstraction { -- locale: string; -- // First locale is the default (English) -- supportedTranslationLocales: string[] = ['en']; -- translationLocale: string; -- collator: Intl.Collator; -- localeNames = new Map([ -- ['af', 'Afrikaans'], -- ['az', 'Azərbaycanca'], -- ['be', 'Беларуская'], -- ['bg', 'български'], -- ['ca', 'català'], -- ['cs', 'čeština'], -- ['da', 'dansk'], -- ['de', 'Deutsch'], -- ['el', 'Ελληνικά'], -- ['en', 'English'], -- ['en-GB', 'English (British)'], -- ['eo', 'Esperanto'], -- ['es', 'español'], -- ['et', 'eesti'], -- ['fa', 'فارسی'], -- ['fi', 'suomi'], -- ['fr', 'français'], -- ['he', 'עברית'], -- ['hi', 'हिन्दी'], -- ['hr', 'hrvatski'], -- ['hu', 'magyar'], -- ['id', 'Bahasa Indonesia'], -- ['it', 'italiano'], -- ['ja', '日本語'], -- ['ko', '한국어'], -- ['lv', 'Latvietis'], -- ['ml', 'മലയാളം'], -- ['nb', 'norsk (bokmål)'], -- ['nl', 'Nederlands'], -- ['pl', 'polski'], -- ['pt-BR', 'português do Brasil'], -- ['pt-PT', 'português'], -- ['ro', 'română'], -- ['ru', 'русский'], -- ['sk', 'slovenčina'], -- ['sr', 'Српски'], -- ['sv', 'svenska'], -- ['th', 'ไทย'], -- ['tr', 'Türkçe'], -- ['uk', 'українська'], -- ['vi', 'Tiếng Việt'], -- ['zh-CN', '中文(中国大陆)'], -- ['zh-TW', '中文(台灣)'], -- ]); -- -- protected inited: boolean; -- protected defaultMessages: any = {}; -- protected localeMessages: any = {}; -- -- constructor(protected systemLanguage: string, protected localesDirectory: string, -- protected getLocalesJson: (formattedLocale: string) => Promise) { -- this.systemLanguage = systemLanguage.replace('_', '-'); -+ locale: string; -+ // First locale is the default (English) -+ supportedTranslationLocales: string[] = ["en"]; -+ translationLocale: string; -+ collator: Intl.Collator; -+ localeNames = new Map([ -+ ["af", "Afrikaans"], -+ ["az", "Azərbaycanca"], -+ ["be", "Беларуская"], -+ ["bg", "български"], -+ ["ca", "català"], -+ ["cs", "čeština"], -+ ["da", "dansk"], -+ ["de", "Deutsch"], -+ ["el", "Ελληνικά"], -+ ["en", "English"], -+ ["en-GB", "English (British)"], -+ ["eo", "Esperanto"], -+ ["es", "español"], -+ ["et", "eesti"], -+ ["fa", "فارسی"], -+ ["fi", "suomi"], -+ ["fr", "français"], -+ ["he", "עברית"], -+ ["hi", "हिन्दी"], -+ ["hr", "hrvatski"], -+ ["hu", "magyar"], -+ ["id", "Bahasa Indonesia"], -+ ["it", "italiano"], -+ ["ja", "日本語"], -+ ["ko", "한국어"], -+ ["lv", "Latvietis"], -+ ["ml", "മലയാളം"], -+ ["nb", "norsk (bokmål)"], -+ ["nl", "Nederlands"], -+ ["pl", "polski"], -+ ["pt-BR", "português do Brasil"], -+ ["pt-PT", "português"], -+ ["ro", "română"], -+ ["ru", "русский"], -+ ["sk", "slovenčina"], -+ ["sr", "Српски"], -+ ["sv", "svenska"], -+ ["th", "ไทย"], -+ ["tr", "Türkçe"], -+ ["uk", "українська"], -+ ["vi", "Tiếng Việt"], -+ ["zh-CN", "中文(中国大陆)"], -+ ["zh-TW", "中文(台灣)"], -+ ]); -+ -+ protected inited: boolean; -+ protected defaultMessages: any = {}; -+ protected localeMessages: any = {}; -+ -+ constructor( -+ protected systemLanguage: string, -+ protected localesDirectory: string, -+ protected getLocalesJson: (formattedLocale: string) => Promise -+ ) { -+ this.systemLanguage = systemLanguage.replace("_", "-"); -+ } -+ -+ async init(locale?: string) { -+ if (this.inited) { -+ throw new Error("i18n already initialized."); -+ } -+ if (this.supportedTranslationLocales == null || this.supportedTranslationLocales.length === 0) { -+ throw new Error("supportedTranslationLocales not set."); - } - -- async init(locale?: string) { -- if (this.inited) { -- throw new Error('i18n already initialized.'); -- } -- if (this.supportedTranslationLocales == null || this.supportedTranslationLocales.length === 0) { -- throw new Error('supportedTranslationLocales not set.'); -- } -- -- this.inited = true; -- this.locale = this.translationLocale = locale != null ? locale : this.systemLanguage; -- -- try { -- this.collator = new Intl.Collator(this.locale, { numeric: true, sensitivity: 'base' }); -- } catch { -- this.collator = null; -- } -+ this.inited = true; -+ this.locale = this.translationLocale = locale != null ? locale : this.systemLanguage; - -- if (this.supportedTranslationLocales.indexOf(this.translationLocale) === -1) { -- this.translationLocale = this.translationLocale.slice(0, 2); -+ try { -+ this.collator = new Intl.Collator(this.locale, { numeric: true, sensitivity: "base" }); -+ } catch { -+ this.collator = null; -+ } - -- if (this.supportedTranslationLocales.indexOf(this.translationLocale) === -1) { -- this.translationLocale = this.supportedTranslationLocales[0]; -- } -- } -+ if (this.supportedTranslationLocales.indexOf(this.translationLocale) === -1) { -+ this.translationLocale = this.translationLocale.slice(0, 2); - -- if (this.localesDirectory != null) { -- await this.loadMessages(this.translationLocale, this.localeMessages); -- if (this.translationLocale !== this.supportedTranslationLocales[0]) { -- await this.loadMessages(this.supportedTranslationLocales[0], this.defaultMessages); -- } -- } -+ if (this.supportedTranslationLocales.indexOf(this.translationLocale) === -1) { -+ this.translationLocale = this.supportedTranslationLocales[0]; -+ } - } - -- t(id: string, p1?: string, p2?: string, p3?: string): string { -- return this.translate(id, p1, p2, p3); -+ if (this.localesDirectory != null) { -+ await this.loadMessages(this.translationLocale, this.localeMessages); -+ if (this.translationLocale !== this.supportedTranslationLocales[0]) { -+ await this.loadMessages(this.supportedTranslationLocales[0], this.defaultMessages); -+ } -+ } -+ } -+ -+ t(id: string, p1?: string, p2?: string, p3?: string): string { -+ return this.translate(id, p1, p2, p3); -+ } -+ -+ translate(id: string, p1?: string, p2?: string, p3?: string): string { -+ let result: string; -+ if (this.localeMessages.hasOwnProperty(id) && this.localeMessages[id]) { -+ result = this.localeMessages[id]; -+ } else if (this.defaultMessages.hasOwnProperty(id) && this.defaultMessages[id]) { -+ result = this.defaultMessages[id]; -+ } else { -+ result = ""; - } - -- translate(id: string, p1?: string, p2?: string, p3?: string): string { -- let result: string; -- if (this.localeMessages.hasOwnProperty(id) && this.localeMessages[id]) { -- result = this.localeMessages[id]; -- } else if (this.defaultMessages.hasOwnProperty(id) && this.defaultMessages[id]) { -- result = this.defaultMessages[id]; -- } else { -- result = ''; -- } -- -- if (result !== '') { -- if (p1 != null) { -- result = result.split('__$1__').join(p1); -- } -- if (p2 != null) { -- result = result.split('__$2__').join(p2); -- } -- if (p3 != null) { -- result = result.split('__$3__').join(p3); -- } -- } -- -- return result; -+ if (result !== "") { -+ if (p1 != null) { -+ result = result.split("__$1__").join(p1); -+ } -+ if (p2 != null) { -+ result = result.split("__$2__").join(p2); -+ } -+ if (p3 != null) { -+ result = result.split("__$3__").join(p3); -+ } - } - -- private async loadMessages(locale: string, messagesObj: any): Promise { -- const formattedLocale = locale.replace('-', '_'); -- const locales = await this.getLocalesJson(formattedLocale); -- for (const prop in locales) { -- if (!locales.hasOwnProperty(prop)) { -- continue; -- } -- messagesObj[prop] = locales[prop].message; -- -- if (locales[prop].placeholders) { -- for (const placeProp in locales[prop].placeholders) { -- if (!locales[prop].placeholders.hasOwnProperty(placeProp) || -- !locales[prop].placeholders[placeProp].content) { -- continue; -- } -- -- const replaceToken = '\\$' + placeProp.toUpperCase() + '\\$'; -- let replaceContent = locales[prop].placeholders[placeProp].content; -- if (replaceContent === '$1' || replaceContent === '$2' || replaceContent === '$3') { -- replaceContent = '__$' + replaceContent + '__'; -- } -- messagesObj[prop] = messagesObj[prop].replace(new RegExp(replaceToken, 'g'), replaceContent); -- } -- } -+ return result; -+ } -+ -+ private async loadMessages(locale: string, messagesObj: any): Promise { -+ const formattedLocale = locale.replace("-", "_"); -+ const locales = await this.getLocalesJson(formattedLocale); -+ for (const prop in locales) { -+ if (!locales.hasOwnProperty(prop)) { -+ continue; -+ } -+ messagesObj[prop] = locales[prop].message; -+ -+ if (locales[prop].placeholders) { -+ for (const placeProp in locales[prop].placeholders) { -+ if ( -+ !locales[prop].placeholders.hasOwnProperty(placeProp) || -+ !locales[prop].placeholders[placeProp].content -+ ) { -+ continue; -+ } -+ -+ const replaceToken = "\\$" + placeProp.toUpperCase() + "\\$"; -+ let replaceContent = locales[prop].placeholders[placeProp].content; -+ if (replaceContent === "$1" || replaceContent === "$2" || replaceContent === "$3") { -+ replaceContent = "__$" + replaceContent + "__"; -+ } -+ messagesObj[prop] = messagesObj[prop].replace( -+ new RegExp(replaceToken, "g"), -+ replaceContent -+ ); - } -+ } - } -- -+ } - } -diff --git a/jslib/common/src/services/import.service.ts b/jslib/common/src/services/import.service.ts -index d04ede50..adf014bf 100644 ---- a/jslib/common/src/services/import.service.ts -+++ b/jslib/common/src/services/import.service.ts -@@ -1,403 +1,424 @@ --import { ApiService } from '../abstractions/api.service'; --import { CipherService } from '../abstractions/cipher.service'; --import { CollectionService } from '../abstractions/collection.service'; --import { CryptoService } from '../abstractions/crypto.service'; --import { FolderService } from '../abstractions/folder.service'; --import { I18nService } from '../abstractions/i18n.service'; -+import { ApiService } from "../abstractions/api.service"; -+import { CipherService } from "../abstractions/cipher.service"; -+import { CollectionService } from "../abstractions/collection.service"; -+import { CryptoService } from "../abstractions/crypto.service"; -+import { FolderService } from "../abstractions/folder.service"; -+import { I18nService } from "../abstractions/i18n.service"; - import { -- ImportOption, -- ImportService as ImportServiceAbstraction, --} from '../abstractions/import.service'; --import { PlatformUtilsService } from '../abstractions/platformUtils.service'; -+ ImportOption, -+ ImportService as ImportServiceAbstraction, -+} from "../abstractions/import.service"; -+import { PlatformUtilsService } from "../abstractions/platformUtils.service"; - --import { ImportResult } from '../models/domain/importResult'; -+import { ImportResult } from "../models/domain/importResult"; - --import { CipherType } from '../enums/cipherType'; -+import { CipherType } from "../enums/cipherType"; - --import { Utils } from '../misc/utils'; -+import { Utils } from "../misc/utils"; - --import { CipherRequest } from '../models/request/cipherRequest'; --import { CollectionRequest } from '../models/request/collectionRequest'; --import { FolderRequest } from '../models/request/folderRequest'; --import { ImportCiphersRequest } from '../models/request/importCiphersRequest'; --import { ImportOrganizationCiphersRequest } from '../models/request/importOrganizationCiphersRequest'; --import { KvpRequest } from '../models/request/kvpRequest'; -+import { CipherRequest } from "../models/request/cipherRequest"; -+import { CollectionRequest } from "../models/request/collectionRequest"; -+import { FolderRequest } from "../models/request/folderRequest"; -+import { ImportCiphersRequest } from "../models/request/importCiphersRequest"; -+import { ImportOrganizationCiphersRequest } from "../models/request/importOrganizationCiphersRequest"; -+import { KvpRequest } from "../models/request/kvpRequest"; - --import { ErrorResponse } from '../models/response/errorResponse'; --import { CipherView } from '../models/view/cipherView'; -+import { ErrorResponse } from "../models/response/errorResponse"; -+import { CipherView } from "../models/view/cipherView"; - --import { AscendoCsvImporter } from '../importers/ascendoCsvImporter'; --import { AvastCsvImporter } from '../importers/avastCsvImporter'; --import { AvastJsonImporter } from '../importers/avastJsonImporter'; --import { AviraCsvImporter } from '../importers/aviraCsvImporter'; --import { BitwardenCsvImporter } from '../importers/bitwardenCsvImporter'; --import { BitwardenJsonImporter } from '../importers/bitwardenJsonImporter'; --import { BlackBerryCsvImporter } from '../importers/blackBerryCsvImporter'; --import { BlurCsvImporter } from '../importers/blurCsvImporter'; --import { ButtercupCsvImporter } from '../importers/buttercupCsvImporter'; --import { ChromeCsvImporter } from '../importers/chromeCsvImporter'; --import { ClipperzHtmlImporter } from '../importers/clipperzHtmlImporter'; --import { CodebookCsvImporter } from '../importers/codebookCsvImporter'; --import { DashlaneJsonImporter } from '../importers/dashlaneJsonImporter'; --import { EncryptrCsvImporter } from '../importers/encryptrCsvImporter'; --import { EnpassCsvImporter } from '../importers/enpassCsvImporter'; --import { EnpassJsonImporter } from '../importers/enpassJsonImporter'; --import { FirefoxCsvImporter } from '../importers/firefoxCsvImporter'; --import { FSecureFskImporter } from '../importers/fsecureFskImporter'; --import { GnomeJsonImporter } from '../importers/gnomeJsonImporter'; --import { Importer } from '../importers/importer'; --import { KasperskyTxtImporter } from '../importers/kasperskyTxtImporter'; --import { KeePass2XmlImporter } from '../importers/keepass2XmlImporter'; --import { KeePassXCsvImporter } from '../importers/keepassxCsvImporter'; --import { KeeperCsvImporter } from '../importers/keeperCsvImporter'; --import { LastPassCsvImporter } from '../importers/lastpassCsvImporter'; --import { LogMeOnceCsvImporter } from '../importers/logMeOnceCsvImporter'; --import { MeldiumCsvImporter } from '../importers/meldiumCsvImporter'; --import { MSecureCsvImporter } from '../importers/msecureCsvImporter'; --import { MykiCsvImporter } from '../importers/mykiCsvImporter'; --import { NordPassCsvImporter } from '../importers/nordpassCsvImporter'; --import { OnePassword1PifImporter } from '../importers/onepasswordImporters/onepassword1PifImporter'; --import { OnePasswordMacCsvImporter } from '../importers/onepasswordImporters/onepasswordMacCsvImporter'; --import { OnePasswordWinCsvImporter } from '../importers/onepasswordImporters/onepasswordWinCsvImporter'; --import { PadlockCsvImporter } from '../importers/padlockCsvImporter'; --import { PassKeepCsvImporter } from '../importers/passkeepCsvImporter'; --import { PassmanJsonImporter } from '../importers/passmanJsonImporter'; --import { PasspackCsvImporter } from '../importers/passpackCsvImporter'; --import { PasswordAgentCsvImporter } from '../importers/passwordAgentCsvImporter'; --import { PasswordBossJsonImporter } from '../importers/passwordBossJsonImporter'; --import { PasswordDragonXmlImporter } from '../importers/passwordDragonXmlImporter'; --import { PasswordSafeXmlImporter } from '../importers/passwordSafeXmlImporter'; --import { PasswordWalletTxtImporter } from '../importers/passwordWalletTxtImporter'; --import { RememBearCsvImporter } from '../importers/rememBearCsvImporter'; --import { RoboFormCsvImporter } from '../importers/roboformCsvImporter'; --import { SafariCsvImporter } from '../importers/safariCsvImporter'; --import { SafeInCloudXmlImporter } from '../importers/safeInCloudXmlImporter'; --import { SaferPassCsvImporter } from '../importers/saferpassCsvImport'; --import { SecureSafeCsvImporter } from '../importers/secureSafeCsvImporter'; --import { SplashIdCsvImporter } from '../importers/splashIdCsvImporter'; --import { StickyPasswordXmlImporter } from '../importers/stickyPasswordXmlImporter'; --import { TrueKeyCsvImporter } from '../importers/truekeyCsvImporter'; --import { UpmCsvImporter } from '../importers/upmCsvImporter'; --import { YotiCsvImporter } from '../importers/yotiCsvImporter'; --import { ZohoVaultCsvImporter } from '../importers/zohoVaultCsvImporter'; -+import { AscendoCsvImporter } from "../importers/ascendoCsvImporter"; -+import { AvastCsvImporter } from "../importers/avastCsvImporter"; -+import { AvastJsonImporter } from "../importers/avastJsonImporter"; -+import { AviraCsvImporter } from "../importers/aviraCsvImporter"; -+import { BitwardenCsvImporter } from "../importers/bitwardenCsvImporter"; -+import { BitwardenJsonImporter } from "../importers/bitwardenJsonImporter"; -+import { BlackBerryCsvImporter } from "../importers/blackBerryCsvImporter"; -+import { BlurCsvImporter } from "../importers/blurCsvImporter"; -+import { ButtercupCsvImporter } from "../importers/buttercupCsvImporter"; -+import { ChromeCsvImporter } from "../importers/chromeCsvImporter"; -+import { ClipperzHtmlImporter } from "../importers/clipperzHtmlImporter"; -+import { CodebookCsvImporter } from "../importers/codebookCsvImporter"; -+import { DashlaneJsonImporter } from "../importers/dashlaneJsonImporter"; -+import { EncryptrCsvImporter } from "../importers/encryptrCsvImporter"; -+import { EnpassCsvImporter } from "../importers/enpassCsvImporter"; -+import { EnpassJsonImporter } from "../importers/enpassJsonImporter"; -+import { FirefoxCsvImporter } from "../importers/firefoxCsvImporter"; -+import { FSecureFskImporter } from "../importers/fsecureFskImporter"; -+import { GnomeJsonImporter } from "../importers/gnomeJsonImporter"; -+import { Importer } from "../importers/importer"; -+import { KasperskyTxtImporter } from "../importers/kasperskyTxtImporter"; -+import { KeePass2XmlImporter } from "../importers/keepass2XmlImporter"; -+import { KeePassXCsvImporter } from "../importers/keepassxCsvImporter"; -+import { KeeperCsvImporter } from "../importers/keeperImporters/keeperCsvImporter"; -+import { KeeperJsonImporter } from "../importers/keeperImporters/keeperJsonImporter"; -+import { LastPassCsvImporter } from "../importers/lastpassCsvImporter"; -+import { LogMeOnceCsvImporter } from "../importers/logMeOnceCsvImporter"; -+import { MeldiumCsvImporter } from "../importers/meldiumCsvImporter"; -+import { MSecureCsvImporter } from "../importers/msecureCsvImporter"; -+import { MykiCsvImporter } from "../importers/mykiCsvImporter"; -+import { NordPassCsvImporter } from "../importers/nordpassCsvImporter"; -+import { OnePassword1PifImporter } from "../importers/onepasswordImporters/onepassword1PifImporter"; -+import { OnePasswordMacCsvImporter } from "../importers/onepasswordImporters/onepasswordMacCsvImporter"; -+import { OnePasswordWinCsvImporter } from "../importers/onepasswordImporters/onepasswordWinCsvImporter"; -+import { PadlockCsvImporter } from "../importers/padlockCsvImporter"; -+import { PassKeepCsvImporter } from "../importers/passkeepCsvImporter"; -+import { PassmanJsonImporter } from "../importers/passmanJsonImporter"; -+import { PasspackCsvImporter } from "../importers/passpackCsvImporter"; -+import { PasswordAgentCsvImporter } from "../importers/passwordAgentCsvImporter"; -+import { PasswordBossJsonImporter } from "../importers/passwordBossJsonImporter"; -+import { PasswordDragonXmlImporter } from "../importers/passwordDragonXmlImporter"; -+import { PasswordSafeXmlImporter } from "../importers/passwordSafeXmlImporter"; -+import { PasswordWalletTxtImporter } from "../importers/passwordWalletTxtImporter"; -+import { RememBearCsvImporter } from "../importers/rememBearCsvImporter"; -+import { RoboFormCsvImporter } from "../importers/roboformCsvImporter"; -+import { SafariCsvImporter } from "../importers/safariCsvImporter"; -+import { SafeInCloudXmlImporter } from "../importers/safeInCloudXmlImporter"; -+import { SaferPassCsvImporter } from "../importers/saferpassCsvImport"; -+import { SecureSafeCsvImporter } from "../importers/secureSafeCsvImporter"; -+import { SplashIdCsvImporter } from "../importers/splashIdCsvImporter"; -+import { StickyPasswordXmlImporter } from "../importers/stickyPasswordXmlImporter"; -+import { TrueKeyCsvImporter } from "../importers/truekeyCsvImporter"; -+import { UpmCsvImporter } from "../importers/upmCsvImporter"; -+import { YotiCsvImporter } from "../importers/yotiCsvImporter"; -+import { ZohoVaultCsvImporter } from "../importers/zohoVaultCsvImporter"; - - export class ImportService implements ImportServiceAbstraction { -- featuredImportOptions = [ -- { id: 'bitwardenjson', name: 'Bitwarden (json)' }, -- { id: 'bitwardencsv', name: 'Bitwarden (csv)' }, -- { id: 'chromecsv', name: 'Chrome (csv)' }, -- { id: 'dashlanejson', name: 'Dashlane (json)' }, -- { id: 'firefoxcsv', name: 'Firefox (csv)' }, -- { id: 'keepass2xml', name: 'KeePass 2 (xml)' }, -- { id: 'lastpasscsv', name: 'LastPass (csv)' }, -- { id: 'safaricsv', name: 'Safari and macOS (csv)' }, -- { id: '1password1pif', name: '1Password (1pif)' }, -- ]; -+ featuredImportOptions = [ -+ { id: "bitwardenjson", name: "Bitwarden (json)" }, -+ { id: "bitwardencsv", name: "Bitwarden (csv)" }, -+ { id: "chromecsv", name: "Chrome (csv)" }, -+ { id: "dashlanejson", name: "Dashlane (json)" }, -+ { id: "firefoxcsv", name: "Firefox (csv)" }, -+ { id: "keepass2xml", name: "KeePass 2 (xml)" }, -+ { id: "lastpasscsv", name: "LastPass (csv)" }, -+ { id: "safaricsv", name: "Safari and macOS (csv)" }, -+ { id: "1password1pif", name: "1Password (1pif)" }, -+ ]; - -- regularImportOptions: ImportOption[] = [ -- { id: 'keepassxcsv', name: 'KeePassX (csv)' }, -- { id: '1passwordwincsv', name: '1Password 6 and 7 Windows (csv)' }, -- { id: '1passwordmaccsv', name: '1Password 6 and 7 Mac (csv)' }, -- { id: 'roboformcsv', name: 'RoboForm (csv)' }, -- { id: 'keepercsv', name: 'Keeper (csv)' }, -- { id: 'enpasscsv', name: 'Enpass (csv)' }, -- { id: 'enpassjson', name: 'Enpass (json)' }, -- { id: 'safeincloudxml', name: 'SafeInCloud (xml)' }, -- { id: 'pwsafexml', name: 'Password Safe (xml)' }, -- { id: 'stickypasswordxml', name: 'Sticky Password (xml)' }, -- { id: 'msecurecsv', name: 'mSecure (csv)' }, -- { id: 'truekeycsv', name: 'True Key (csv)' }, -- { id: 'passwordbossjson', name: 'Password Boss (json)' }, -- { id: 'zohovaultcsv', name: 'Zoho Vault (csv)' }, -- { id: 'splashidcsv', name: 'SplashID (csv)' }, -- { id: 'passworddragonxml', name: 'Password Dragon (xml)' }, -- { id: 'padlockcsv', name: 'Padlock (csv)' }, -- { id: 'passboltcsv', name: 'Passbolt (csv)' }, -- { id: 'clipperzhtml', name: 'Clipperz (html)' }, -- { id: 'aviracsv', name: 'Avira (csv)' }, -- { id: 'saferpasscsv', name: 'SaferPass (csv)' }, -- { id: 'upmcsv', name: 'Universal Password Manager (csv)' }, -- { id: 'ascendocsv', name: 'Ascendo DataVault (csv)' }, -- { id: 'meldiumcsv', name: 'Meldium (csv)' }, -- { id: 'passkeepcsv', name: 'PassKeep (csv)' }, -- { id: 'operacsv', name: 'Opera (csv)' }, -- { id: 'vivaldicsv', name: 'Vivaldi (csv)' }, -- { id: 'gnomejson', name: 'GNOME Passwords and Keys/Seahorse (json)' }, -- { id: 'blurcsv', name: 'Blur (csv)' }, -- { id: 'passwordagentcsv', name: 'Password Agent (csv)' }, -- { id: 'passpackcsv', name: 'Passpack (csv)' }, -- { id: 'passmanjson', name: 'Passman (json)' }, -- { id: 'avastcsv', name: 'Avast Passwords (csv)' }, -- { id: 'avastjson', name: 'Avast Passwords (json)' }, -- { id: 'fsecurefsk', name: 'F-Secure KEY (fsk)' }, -- { id: 'kasperskytxt', name: 'Kaspersky Password Manager (txt)' }, -- { id: 'remembearcsv', name: 'RememBear (csv)' }, -- { id: 'passwordwallettxt', name: 'PasswordWallet (txt)' }, -- { id: 'mykicsv', name: 'Myki (csv)' }, -- { id: 'securesafecsv', name: 'SecureSafe (csv)' }, -- { id: 'logmeoncecsv', name: 'LogMeOnce (csv)' }, -- { id: 'blackberrycsv', name: 'BlackBerry Password Keeper (csv)' }, -- { id: 'buttercupcsv', name: 'Buttercup (csv)' }, -- { id: 'codebookcsv', name: 'Codebook (csv)' }, -- { id: 'encryptrcsv', name: 'Encryptr (csv)' }, -- { id: 'yoticsv', name: 'Yoti (csv)' }, -- { id: 'nordpasscsv', name: 'Nordpass (csv)' }, -- ]; -+ regularImportOptions: ImportOption[] = [ -+ { id: "keepassxcsv", name: "KeePassX (csv)" }, -+ { id: "1passwordwincsv", name: "1Password 6 and 7 Windows (csv)" }, -+ { id: "1passwordmaccsv", name: "1Password 6 and 7 Mac (csv)" }, -+ { id: "roboformcsv", name: "RoboForm (csv)" }, -+ { id: "keepercsv", name: "Keeper (csv)" }, -+ { id: "keeperjson", name: "Keeper (json)" }, -+ { id: "enpasscsv", name: "Enpass (csv)" }, -+ { id: "enpassjson", name: "Enpass (json)" }, -+ { id: "safeincloudxml", name: "SafeInCloud (xml)" }, -+ { id: "pwsafexml", name: "Password Safe (xml)" }, -+ { id: "stickypasswordxml", name: "Sticky Password (xml)" }, -+ { id: "msecurecsv", name: "mSecure (csv)" }, -+ { id: "truekeycsv", name: "True Key (csv)" }, -+ { id: "passwordbossjson", name: "Password Boss (json)" }, -+ { id: "zohovaultcsv", name: "Zoho Vault (csv)" }, -+ { id: "splashidcsv", name: "SplashID (csv)" }, -+ { id: "passworddragonxml", name: "Password Dragon (xml)" }, -+ { id: "padlockcsv", name: "Padlock (csv)" }, -+ { id: "passboltcsv", name: "Passbolt (csv)" }, -+ { id: "clipperzhtml", name: "Clipperz (html)" }, -+ { id: "aviracsv", name: "Avira (csv)" }, -+ { id: "saferpasscsv", name: "SaferPass (csv)" }, -+ { id: "upmcsv", name: "Universal Password Manager (csv)" }, -+ { id: "ascendocsv", name: "Ascendo DataVault (csv)" }, -+ { id: "meldiumcsv", name: "Meldium (csv)" }, -+ { id: "passkeepcsv", name: "PassKeep (csv)" }, -+ { id: "operacsv", name: "Opera (csv)" }, -+ { id: "vivaldicsv", name: "Vivaldi (csv)" }, -+ { id: "gnomejson", name: "GNOME Passwords and Keys/Seahorse (json)" }, -+ { id: "blurcsv", name: "Blur (csv)" }, -+ { id: "passwordagentcsv", name: "Password Agent (csv)" }, -+ { id: "passpackcsv", name: "Passpack (csv)" }, -+ { id: "passmanjson", name: "Passman (json)" }, -+ { id: "avastcsv", name: "Avast Passwords (csv)" }, -+ { id: "avastjson", name: "Avast Passwords (json)" }, -+ { id: "fsecurefsk", name: "F-Secure KEY (fsk)" }, -+ { id: "kasperskytxt", name: "Kaspersky Password Manager (txt)" }, -+ { id: "remembearcsv", name: "RememBear (csv)" }, -+ { id: "passwordwallettxt", name: "PasswordWallet (txt)" }, -+ { id: "mykicsv", name: "Myki (csv)" }, -+ { id: "securesafecsv", name: "SecureSafe (csv)" }, -+ { id: "logmeoncecsv", name: "LogMeOnce (csv)" }, -+ { id: "blackberrycsv", name: "BlackBerry Password Keeper (csv)" }, -+ { id: "buttercupcsv", name: "Buttercup (csv)" }, -+ { id: "codebookcsv", name: "Codebook (csv)" }, -+ { id: "encryptrcsv", name: "Encryptr (csv)" }, -+ { id: "yoticsv", name: "Yoti (csv)" }, -+ { id: "nordpasscsv", name: "Nordpass (csv)" }, -+ ]; - -- constructor(private cipherService: CipherService, private folderService: FolderService, -- private apiService: ApiService, private i18nService: I18nService, -- private collectionService: CollectionService, private platformUtilsService: PlatformUtilsService, -- private cryptoService: CryptoService) { } -+ constructor( -+ private cipherService: CipherService, -+ private folderService: FolderService, -+ private apiService: ApiService, -+ private i18nService: I18nService, -+ private collectionService: CollectionService, -+ private platformUtilsService: PlatformUtilsService, -+ private cryptoService: CryptoService -+ ) {} - -- getImportOptions(): ImportOption[] { -- return this.featuredImportOptions.concat(this.regularImportOptions); -- } -+ getImportOptions(): ImportOption[] { -+ return this.featuredImportOptions.concat(this.regularImportOptions); -+ } - -- async import(importer: Importer, fileContents: string, organizationId: string = null): Promise { -- const importResult = await importer.parse(fileContents); -- if (importResult.success) { -- if (importResult.folders.length === 0 && importResult.ciphers.length === 0) { -- return new Error(this.i18nService.t('importNothingError')); -- } else if (importResult.ciphers.length > 0) { -- const halfway = Math.floor(importResult.ciphers.length / 2); -- const last = importResult.ciphers.length - 1; -+ async import( -+ importer: Importer, -+ fileContents: string, -+ organizationId: string = null -+ ): Promise { -+ const importResult = await importer.parse(fileContents); -+ if (importResult.success) { -+ if (importResult.folders.length === 0 && importResult.ciphers.length === 0) { -+ return new Error(this.i18nService.t("importNothingError")); -+ } else if (importResult.ciphers.length > 0) { -+ const halfway = Math.floor(importResult.ciphers.length / 2); -+ const last = importResult.ciphers.length - 1; - -- if (this.badData(importResult.ciphers[0]) && -- this.badData(importResult.ciphers[halfway]) && -- this.badData(importResult.ciphers[last])) { -- return new Error(this.i18nService.t('importFormatError')); -- } -- } -- try { -- await this.postImport(importResult, organizationId); -- } catch (error) { -- const errorResponse = new ErrorResponse(error, 400); -- return this.handleServerError(errorResponse, importResult); -- } -- return null; -- } else { -- if (!Utils.isNullOrWhitespace(importResult.errorMessage)) { -- return new Error(importResult.errorMessage); -- } else { -- return new Error(this.i18nService.t('importFormatError')); -- } -+ if ( -+ this.badData(importResult.ciphers[0]) && -+ this.badData(importResult.ciphers[halfway]) && -+ this.badData(importResult.ciphers[last]) -+ ) { -+ return new Error(this.i18nService.t("importFormatError")); - } -+ } -+ try { -+ await this.postImport(importResult, organizationId); -+ } catch (error) { -+ const errorResponse = new ErrorResponse(error, 400); -+ return this.handleServerError(errorResponse, importResult); -+ } -+ return null; -+ } else { -+ if (!Utils.isNullOrWhitespace(importResult.errorMessage)) { -+ return new Error(importResult.errorMessage); -+ } else { -+ return new Error(this.i18nService.t("importFormatError")); -+ } - } -+ } - -- getImporter(format: string, organizationId: string = null): Importer { -- const importer = this.getImporterInstance(format); -- if (importer == null) { -- return null; -- } -- importer.organizationId = organizationId; -- return importer; -+ getImporter(format: string, organizationId: string = null): Importer { -+ const importer = this.getImporterInstance(format); -+ if (importer == null) { -+ return null; - } -+ importer.organizationId = organizationId; -+ return importer; -+ } - -- private getImporterInstance(format: string) { -- if (format == null || format === '') { -- return null; -- } -+ private getImporterInstance(format: string) { -+ if (format == null || format === "") { -+ return null; -+ } - -- switch (format) { -- case 'bitwardencsv': -- return new BitwardenCsvImporter(); -- case 'bitwardenjson': -- return new BitwardenJsonImporter(this.cryptoService, this.i18nService); -- case 'lastpasscsv': -- case 'passboltcsv': -- return new LastPassCsvImporter(); -- case 'keepassxcsv': -- return new KeePassXCsvImporter(); -- case 'aviracsv': -- return new AviraCsvImporter(); -- case 'blurcsv': -- return new BlurCsvImporter(); -- case 'safeincloudxml': -- return new SafeInCloudXmlImporter(); -- case 'padlockcsv': -- return new PadlockCsvImporter(); -- case 'keepass2xml': -- return new KeePass2XmlImporter(); -- case 'chromecsv': -- case 'operacsv': -- case 'vivaldicsv': -- return new ChromeCsvImporter(); -- case 'firefoxcsv': -- return new FirefoxCsvImporter(); -- case 'upmcsv': -- return new UpmCsvImporter(); -- case 'saferpasscsv': -- return new SaferPassCsvImporter(); -- case 'safaricsv': -- return new SafariCsvImporter(); -- case 'meldiumcsv': -- return new MeldiumCsvImporter(); -- case '1password1pif': -- return new OnePassword1PifImporter(); -- case '1passwordwincsv': -- return new OnePasswordWinCsvImporter(); -- case '1passwordmaccsv': -- return new OnePasswordMacCsvImporter(); -- case 'keepercsv': -- return new KeeperCsvImporter(); -- case 'passworddragonxml': -- return new PasswordDragonXmlImporter(); -- case 'enpasscsv': -- return new EnpassCsvImporter(); -- case 'enpassjson': -- return new EnpassJsonImporter(); -- case 'pwsafexml': -- return new PasswordSafeXmlImporter(); -- case 'dashlanejson': -- return new DashlaneJsonImporter(); -- case 'msecurecsv': -- return new MSecureCsvImporter(); -- case 'stickypasswordxml': -- return new StickyPasswordXmlImporter(); -- case 'truekeycsv': -- return new TrueKeyCsvImporter(); -- case 'clipperzhtml': -- return new ClipperzHtmlImporter(); -- case 'roboformcsv': -- return new RoboFormCsvImporter(); -- case 'ascendocsv': -- return new AscendoCsvImporter(); -- case 'passwordbossjson': -- return new PasswordBossJsonImporter(); -- case 'zohovaultcsv': -- return new ZohoVaultCsvImporter(); -- case 'splashidcsv': -- return new SplashIdCsvImporter(); -- case 'passkeepcsv': -- return new PassKeepCsvImporter(); -- case 'gnomejson': -- return new GnomeJsonImporter(); -- case 'passwordagentcsv': -- return new PasswordAgentCsvImporter(); -- case 'passpackcsv': -- return new PasspackCsvImporter(); -- case 'passmanjson': -- return new PassmanJsonImporter(); -- case 'avastcsv': -- return new AvastCsvImporter(); -- case 'avastjson': -- return new AvastJsonImporter(); -- case 'fsecurefsk': -- return new FSecureFskImporter(); -- case 'kasperskytxt': -- return new KasperskyTxtImporter(); -- case 'remembearcsv': -- return new RememBearCsvImporter(); -- case 'passwordwallettxt': -- return new PasswordWalletTxtImporter(); -- case 'mykicsv': -- return new MykiCsvImporter(); -- case 'securesafecsv': -- return new SecureSafeCsvImporter(); -- case 'logmeoncecsv': -- return new LogMeOnceCsvImporter(); -- case 'blackberrycsv': -- return new BlackBerryCsvImporter(); -- case 'buttercupcsv': -- return new ButtercupCsvImporter(); -- case 'codebookcsv': -- return new CodebookCsvImporter(); -- case 'encryptrcsv': -- return new EncryptrCsvImporter(); -- case 'yoticsv': -- return new YotiCsvImporter(); -- case 'nordpasscsv': -- return new NordPassCsvImporter(); -- default: -- return null; -- } -+ switch (format) { -+ case "bitwardencsv": -+ return new BitwardenCsvImporter(); -+ case "bitwardenjson": -+ return new BitwardenJsonImporter(this.cryptoService, this.i18nService); -+ case "lastpasscsv": -+ case "passboltcsv": -+ return new LastPassCsvImporter(); -+ case "keepassxcsv": -+ return new KeePassXCsvImporter(); -+ case "aviracsv": -+ return new AviraCsvImporter(); -+ case "blurcsv": -+ return new BlurCsvImporter(); -+ case "safeincloudxml": -+ return new SafeInCloudXmlImporter(); -+ case "padlockcsv": -+ return new PadlockCsvImporter(); -+ case "keepass2xml": -+ return new KeePass2XmlImporter(); -+ case "chromecsv": -+ case "operacsv": -+ case "vivaldicsv": -+ return new ChromeCsvImporter(); -+ case "firefoxcsv": -+ return new FirefoxCsvImporter(); -+ case "upmcsv": -+ return new UpmCsvImporter(); -+ case "saferpasscsv": -+ return new SaferPassCsvImporter(); -+ case "safaricsv": -+ return new SafariCsvImporter(); -+ case "meldiumcsv": -+ return new MeldiumCsvImporter(); -+ case "1password1pif": -+ return new OnePassword1PifImporter(); -+ case "1passwordwincsv": -+ return new OnePasswordWinCsvImporter(); -+ case "1passwordmaccsv": -+ return new OnePasswordMacCsvImporter(); -+ case "keepercsv": -+ return new KeeperCsvImporter(); -+ case "keeperjson": -+ return new KeeperJsonImporter(); -+ case "passworddragonxml": -+ return new PasswordDragonXmlImporter(); -+ case "enpasscsv": -+ return new EnpassCsvImporter(); -+ case "enpassjson": -+ return new EnpassJsonImporter(); -+ case "pwsafexml": -+ return new PasswordSafeXmlImporter(); -+ case "dashlanejson": -+ return new DashlaneJsonImporter(); -+ case "msecurecsv": -+ return new MSecureCsvImporter(); -+ case "stickypasswordxml": -+ return new StickyPasswordXmlImporter(); -+ case "truekeycsv": -+ return new TrueKeyCsvImporter(); -+ case "clipperzhtml": -+ return new ClipperzHtmlImporter(); -+ case "roboformcsv": -+ return new RoboFormCsvImporter(); -+ case "ascendocsv": -+ return new AscendoCsvImporter(); -+ case "passwordbossjson": -+ return new PasswordBossJsonImporter(); -+ case "zohovaultcsv": -+ return new ZohoVaultCsvImporter(); -+ case "splashidcsv": -+ return new SplashIdCsvImporter(); -+ case "passkeepcsv": -+ return new PassKeepCsvImporter(); -+ case "gnomejson": -+ return new GnomeJsonImporter(); -+ case "passwordagentcsv": -+ return new PasswordAgentCsvImporter(); -+ case "passpackcsv": -+ return new PasspackCsvImporter(); -+ case "passmanjson": -+ return new PassmanJsonImporter(); -+ case "avastcsv": -+ return new AvastCsvImporter(); -+ case "avastjson": -+ return new AvastJsonImporter(); -+ case "fsecurefsk": -+ return new FSecureFskImporter(); -+ case "kasperskytxt": -+ return new KasperskyTxtImporter(); -+ case "remembearcsv": -+ return new RememBearCsvImporter(); -+ case "passwordwallettxt": -+ return new PasswordWalletTxtImporter(); -+ case "mykicsv": -+ return new MykiCsvImporter(); -+ case "securesafecsv": -+ return new SecureSafeCsvImporter(); -+ case "logmeoncecsv": -+ return new LogMeOnceCsvImporter(); -+ case "blackberrycsv": -+ return new BlackBerryCsvImporter(); -+ case "buttercupcsv": -+ return new ButtercupCsvImporter(); -+ case "codebookcsv": -+ return new CodebookCsvImporter(); -+ case "encryptrcsv": -+ return new EncryptrCsvImporter(); -+ case "yoticsv": -+ return new YotiCsvImporter(); -+ case "nordpasscsv": -+ return new NordPassCsvImporter(); -+ default: -+ return null; - } -+ } - -- private async postImport(importResult: ImportResult, organizationId: string = null) { -- if (organizationId == null) { -- const request = new ImportCiphersRequest(); -- for (let i = 0; i < importResult.ciphers.length; i++) { -- const c = await this.cipherService.encrypt(importResult.ciphers[i]); -- request.ciphers.push(new CipherRequest(c)); -- } -- if (importResult.folders != null) { -- for (let i = 0; i < importResult.folders.length; i++) { -- const f = await this.folderService.encrypt(importResult.folders[i]); -- request.folders.push(new FolderRequest(f)); -- } -- } -- if (importResult.folderRelationships != null) { -- importResult.folderRelationships.forEach(r => -- request.folderRelationships.push(new KvpRequest(r[0], r[1]))); -- } -- return await this.apiService.postImportCiphers(request); -- } else { -- const request = new ImportOrganizationCiphersRequest(); -- for (let i = 0; i < importResult.ciphers.length; i++) { -- importResult.ciphers[i].organizationId = organizationId; -- const c = await this.cipherService.encrypt(importResult.ciphers[i]); -- request.ciphers.push(new CipherRequest(c)); -- } -- if (importResult.collections != null) { -- for (let i = 0; i < importResult.collections.length; i++) { -- importResult.collections[i].organizationId = organizationId; -- const c = await this.collectionService.encrypt(importResult.collections[i]); -- request.collections.push(new CollectionRequest(c)); -- } -- } -- if (importResult.collectionRelationships != null) { -- importResult.collectionRelationships.forEach(r => -- request.collectionRelationships.push(new KvpRequest(r[0], r[1]))); -- } -- return await this.apiService.postImportOrganizationCiphers(organizationId, request); -+ private async postImport(importResult: ImportResult, organizationId: string = null) { -+ if (organizationId == null) { -+ const request = new ImportCiphersRequest(); -+ for (let i = 0; i < importResult.ciphers.length; i++) { -+ const c = await this.cipherService.encrypt(importResult.ciphers[i]); -+ request.ciphers.push(new CipherRequest(c)); -+ } -+ if (importResult.folders != null) { -+ for (let i = 0; i < importResult.folders.length; i++) { -+ const f = await this.folderService.encrypt(importResult.folders[i]); -+ request.folders.push(new FolderRequest(f)); - } -+ } -+ if (importResult.folderRelationships != null) { -+ importResult.folderRelationships.forEach((r) => -+ request.folderRelationships.push(new KvpRequest(r[0], r[1])) -+ ); -+ } -+ return await this.apiService.postImportCiphers(request); -+ } else { -+ const request = new ImportOrganizationCiphersRequest(); -+ for (let i = 0; i < importResult.ciphers.length; i++) { -+ importResult.ciphers[i].organizationId = organizationId; -+ const c = await this.cipherService.encrypt(importResult.ciphers[i]); -+ request.ciphers.push(new CipherRequest(c)); -+ } -+ if (importResult.collections != null) { -+ for (let i = 0; i < importResult.collections.length; i++) { -+ importResult.collections[i].organizationId = organizationId; -+ const c = await this.collectionService.encrypt(importResult.collections[i]); -+ request.collections.push(new CollectionRequest(c)); -+ } -+ } -+ if (importResult.collectionRelationships != null) { -+ importResult.collectionRelationships.forEach((r) => -+ request.collectionRelationships.push(new KvpRequest(r[0], r[1])) -+ ); -+ } -+ return await this.apiService.postImportOrganizationCiphers(organizationId, request); - } -+ } - -- private badData(c: CipherView) { -- return (c.name == null || c.name === '--') && -- (c.type === CipherType.Login && c.login != null && Utils.isNullOrWhitespace(c.login.password)); -- } -+ private badData(c: CipherView) { -+ return ( -+ (c.name == null || c.name === "--") && -+ c.type === CipherType.Login && -+ c.login != null && -+ Utils.isNullOrWhitespace(c.login.password) -+ ); -+ } - -- private handleServerError(errorResponse: ErrorResponse, importResult: ImportResult): Error { -- if (errorResponse.validationErrors == null) { -- return new Error(errorResponse.message); -- } -+ private handleServerError(errorResponse: ErrorResponse, importResult: ImportResult): Error { -+ if (errorResponse.validationErrors == null) { -+ return new Error(errorResponse.message); -+ } - -- let errorMessage = ''; -+ let errorMessage = ""; - -- Object.entries(errorResponse.validationErrors).forEach(([key, value], index) => { -- let item; -- let itemType; -- const i = Number(key.match(/[0-9]+/)[0]); -+ Object.entries(errorResponse.validationErrors).forEach(([key, value], index) => { -+ let item; -+ let itemType; -+ const i = Number(key.match(/[0-9]+/)[0]); - -- switch (key.match(/^\w+/)[0]) { -- case 'Ciphers': -- item = importResult.ciphers[i]; -- itemType = CipherType[item.type]; -- break; -- case 'Folders': -- item = importResult.folders[i]; -- itemType = 'Folder'; -- break; -- case 'Collections': -- item = importResult.collections[i]; -- itemType = 'Collection'; -- break; -- default: -- return; -- } -+ switch (key.match(/^\w+/)[0]) { -+ case "Ciphers": -+ item = importResult.ciphers[i]; -+ itemType = CipherType[item.type]; -+ break; -+ case "Folders": -+ item = importResult.folders[i]; -+ itemType = "Folder"; -+ break; -+ case "Collections": -+ item = importResult.collections[i]; -+ itemType = "Collection"; -+ break; -+ default: -+ return; -+ } - -- if (index > 0) { -- errorMessage += '\n\n'; -- } -+ if (index > 0) { -+ errorMessage += "\n\n"; -+ } - -- if (itemType !== 'Folder' && itemType !== 'Collection') { -- errorMessage += '[' + (i + 1) + '] '; -- } -+ if (itemType !== "Folder" && itemType !== "Collection") { -+ errorMessage += "[" + (i + 1) + "] "; -+ } - -- errorMessage += '[' + itemType + '] "' + item.name + '": ' + value; -- }); -+ errorMessage += "[" + itemType + '] "' + item.name + '": ' + value; -+ }); - -- return new Error(errorMessage); -- } -+ return new Error(errorMessage); -+ } - } -diff --git a/jslib/common/src/services/keyConnector.service.ts b/jslib/common/src/services/keyConnector.service.ts -index d597469f..7296d9b1 100644 ---- a/jslib/common/src/services/keyConnector.service.ts -+++ b/jslib/common/src/services/keyConnector.service.ts -@@ -1,96 +1,98 @@ --import { ApiService } from '../abstractions/api.service'; --import { CryptoService } from '../abstractions/crypto.service'; --import { KeyConnectorService as KeyConnectorServiceAbstraction } from '../abstractions/keyConnector.service'; --import { LogService } from '../abstractions/log.service'; --import { StorageService } from '../abstractions/storage.service'; --import { TokenService } from '../abstractions/token.service'; --import { UserService } from '../abstractions/user.service'; -+import { ApiService } from "../abstractions/api.service"; -+import { CryptoService } from "../abstractions/crypto.service"; -+import { KeyConnectorService as KeyConnectorServiceAbstraction } from "../abstractions/keyConnector.service"; -+import { LogService } from "../abstractions/log.service"; -+import { OrganizationService } from "../abstractions/organization.service"; -+import { StateService } from "../abstractions/state.service"; -+import { TokenService } from "../abstractions/token.service"; - --import { OrganizationUserType } from '../enums/organizationUserType'; -+import { OrganizationUserType } from "../enums/organizationUserType"; - --import { Utils } from '../misc/utils'; -+import { Utils } from "../misc/utils"; - --import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; -+import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; - --import { KeyConnectorUserKeyRequest } from '../models/request/keyConnectorUserKeyRequest'; -- --const Keys = { -- usesKeyConnector: 'usesKeyConnector', -- convertAccountToKeyConnector: 'convertAccountToKeyConnector', --}; -+import { KeyConnectorUserKeyRequest } from "../models/request/keyConnectorUserKeyRequest"; - - export class KeyConnectorService implements KeyConnectorServiceAbstraction { -- private usesKeyConnector?: boolean = null; -- -- constructor(private storageService: StorageService, private userService: UserService, -- private cryptoService: CryptoService, private apiService: ApiService, -- private tokenService: TokenService, private logService: LogService) { } -- -- setUsesKeyConnector(usesKeyConnector: boolean) { -- this.usesKeyConnector = usesKeyConnector; -- return this.storageService.save(Keys.usesKeyConnector, usesKeyConnector); -- } -- -- async getUsesKeyConnector(): Promise { -- return this.usesKeyConnector ??= await this.storageService.get(Keys.usesKeyConnector); -- } -- -- async userNeedsMigration() { -- const loggedInUsingSso = this.tokenService.getIsExternal(); -- const requiredByOrganization = await this.getManagingOrganization() != null; -- const userIsNotUsingKeyConnector = !await this.getUsesKeyConnector(); -- -- return loggedInUsingSso && requiredByOrganization && userIsNotUsingKeyConnector; -- } -- -- async migrateUser() { -- const organization = await this.getManagingOrganization(); -- const key = await this.cryptoService.getKey(); -- -- try { -- const keyConnectorRequest = new KeyConnectorUserKeyRequest(key.encKeyB64); -- await this.apiService.postUserKeyToKeyConnector(organization.keyConnectorUrl, keyConnectorRequest); -- } catch (e) { -- throw new Error('Unable to reach key connector'); -- } -- -- await this.apiService.postConvertToKeyConnector(); -- } -- -- async getAndSetKey(url: string) { -- try { -- const userKeyResponse = await this.apiService.getUserKeyFromKeyConnector(url); -- const keyArr = Utils.fromB64ToArray(userKeyResponse.key); -- const k = new SymmetricCryptoKey(keyArr); -- await this.cryptoService.setKey(k); -- } catch (e) { -- this.logService.error(e); -- throw new Error('Unable to reach key connector'); -- } -- } -- -- async getManagingOrganization() { -- const orgs = await this.userService.getAllOrganizations(); -- return orgs.find(o => -- o.keyConnectorEnabled && -- o.type !== OrganizationUserType.Admin && -- o.type !== OrganizationUserType.Owner && -- !o.isProviderUser); -- } -- -- async setConvertAccountRequired(status: boolean) { -- await this.storageService.save(Keys.convertAccountToKeyConnector, status); -- } -- -- async getConvertAccountRequired(): Promise { -- return await this.storageService.get(Keys.convertAccountToKeyConnector); -- } -- -- async removeConvertAccountRequired() { -- await this.storageService.remove(Keys.convertAccountToKeyConnector); -+ constructor( -+ private stateService: StateService, -+ private cryptoService: CryptoService, -+ private apiService: ApiService, -+ private tokenService: TokenService, -+ private logService: LogService, -+ private organizationService: OrganizationService -+ ) {} -+ -+ setUsesKeyConnector(usesKeyConnector: boolean) { -+ return this.stateService.setUsesKeyConnector(usesKeyConnector); -+ } -+ -+ async getUsesKeyConnector(): Promise { -+ return await this.stateService.getUsesKeyConnector(); -+ } -+ -+ async userNeedsMigration() { -+ const loggedInUsingSso = await this.tokenService.getIsExternal(); -+ const requiredByOrganization = (await this.getManagingOrganization()) != null; -+ const userIsNotUsingKeyConnector = !(await this.getUsesKeyConnector()); -+ -+ return loggedInUsingSso && requiredByOrganization && userIsNotUsingKeyConnector; -+ } -+ -+ async migrateUser() { -+ const organization = await this.getManagingOrganization(); -+ const key = await this.cryptoService.getKey(); -+ const keyConnectorRequest = new KeyConnectorUserKeyRequest(key.encKeyB64); -+ -+ try { -+ await this.apiService.postUserKeyToKeyConnector( -+ organization.keyConnectorUrl, -+ keyConnectorRequest -+ ); -+ } catch (e) { -+ throw new Error("Unable to reach key connector"); - } - -- async clear() { -- await this.removeConvertAccountRequired(); -+ await this.apiService.postConvertToKeyConnector(); -+ } -+ -+ async getAndSetKey(url: string) { -+ try { -+ const userKeyResponse = await this.apiService.getUserKeyFromKeyConnector(url); -+ const keyArr = Utils.fromB64ToArray(userKeyResponse.key); -+ const k = new SymmetricCryptoKey(keyArr); -+ await this.cryptoService.setKey(k); -+ } catch (e) { -+ this.logService.error(e); -+ throw new Error("Unable to reach key connector"); - } -+ } -+ -+ async getManagingOrganization() { -+ const orgs = await this.organizationService.getAll(); -+ return orgs.find( -+ (o) => -+ o.keyConnectorEnabled && -+ o.type !== OrganizationUserType.Admin && -+ o.type !== OrganizationUserType.Owner && -+ !o.isProviderUser -+ ); -+ } -+ -+ async setConvertAccountRequired(status: boolean) { -+ await this.stateService.setConvertAccountToKeyConnector(status); -+ } -+ -+ async getConvertAccountRequired(): Promise { -+ return await this.stateService.getConvertAccountToKeyConnector(); -+ } -+ -+ async removeConvertAccountRequired() { -+ await this.stateService.setConvertAccountToKeyConnector(null); -+ } -+ -+ async clear() { -+ await this.removeConvertAccountRequired(); -+ } - } -diff --git a/jslib/common/src/services/noopMessaging.service.ts b/jslib/common/src/services/noopMessaging.service.ts -index c1a1443c..d1a60bc5 100644 ---- a/jslib/common/src/services/noopMessaging.service.ts -+++ b/jslib/common/src/services/noopMessaging.service.ts -@@ -1,7 +1,7 @@ --import { MessagingService } from '../abstractions/messaging.service'; -+import { MessagingService } from "../abstractions/messaging.service"; - - export class NoopMessagingService implements MessagingService { -- send(subscriber: string, arg: any = {}) { -- // Do nothing... -- } -+ send(subscriber: string, arg: any = {}) { -+ // Do nothing... -+ } - } -diff --git a/jslib/common/src/services/notifications.service.ts b/jslib/common/src/services/notifications.service.ts -index b1d5723b..4974b282 100644 ---- a/jslib/common/src/services/notifications.service.ts -+++ b/jslib/common/src/services/notifications.service.ts -@@ -1,217 +1,231 @@ --import * as signalR from '@microsoft/signalr'; --import * as signalRMsgPack from '@microsoft/signalr-protocol-msgpack'; -+import * as signalR from "@microsoft/signalr"; -+import * as signalRMsgPack from "@microsoft/signalr-protocol-msgpack"; - --import { NotificationType } from '../enums/notificationType'; -+import { NotificationType } from "../enums/notificationType"; - --import { ApiService } from '../abstractions/api.service'; --import { AppIdService } from '../abstractions/appId.service'; --import { EnvironmentService } from '../abstractions/environment.service'; --import { LogService } from '../abstractions/log.service'; --import { NotificationsService as NotificationsServiceAbstraction } from '../abstractions/notifications.service'; --import { SyncService } from '../abstractions/sync.service'; --import { UserService } from '../abstractions/user.service'; --import { VaultTimeoutService } from '../abstractions/vaultTimeout.service'; -+import { ApiService } from "../abstractions/api.service"; -+import { AppIdService } from "../abstractions/appId.service"; -+import { EnvironmentService } from "../abstractions/environment.service"; -+import { LogService } from "../abstractions/log.service"; -+import { NotificationsService as NotificationsServiceAbstraction } from "../abstractions/notifications.service"; -+import { StateService } from "../abstractions/state.service"; -+import { SyncService } from "../abstractions/sync.service"; -+import { VaultTimeoutService } from "../abstractions/vaultTimeout.service"; - - import { -- NotificationResponse, -- SyncCipherNotification, -- SyncFolderNotification, -- SyncSendNotification, --} from '../models/response/notificationResponse'; -+ NotificationResponse, -+ SyncCipherNotification, -+ SyncFolderNotification, -+ SyncSendNotification, -+} from "../models/response/notificationResponse"; - - export class NotificationsService implements NotificationsServiceAbstraction { -- private signalrConnection: signalR.HubConnection; -- private url: string; -- private connected = false; -- private inited = false; -- private inactive = false; -- private reconnectTimer: any = null; -- -- constructor(private userService: UserService, private syncService: SyncService, -- private appIdService: AppIdService, private apiService: ApiService, -- private vaultTimeoutService: VaultTimeoutService, private environmentService: EnvironmentService, -- private logoutCallback: () => Promise, private logService: LogService) { -- this.environmentService.urls.subscribe(() => { -- if (!this.inited) { -- return; -- } -- -- this.init(); -- }); -- } -- -- async init(): Promise { -- this.inited = false; -- this.url = this.environmentService.getNotificationsUrl(); -- -- // Set notifications server URL to `https://-` to effectively disable communication -- // with the notifications server from the client app -- if (this.url === 'https://-') { -- return; -- } -- -- if (this.signalrConnection != null) { -- this.signalrConnection.off('ReceiveMessage'); -- this.signalrConnection.off('Heartbeat'); -- await this.signalrConnection.stop(); -- this.connected = false; -- this.signalrConnection = null; -- } -+ private signalrConnection: signalR.HubConnection; -+ private url: string; -+ private connected = false; -+ private inited = false; -+ private inactive = false; -+ private reconnectTimer: any = null; -+ -+ constructor( -+ private syncService: SyncService, -+ private appIdService: AppIdService, -+ private apiService: ApiService, -+ private vaultTimeoutService: VaultTimeoutService, -+ private environmentService: EnvironmentService, -+ private logoutCallback: () => Promise, -+ private logService: LogService, -+ private stateService: StateService -+ ) { -+ this.environmentService.urls.subscribe(() => { -+ if (!this.inited) { -+ return; -+ } -+ -+ this.init(); -+ }); -+ } -+ -+ async init(): Promise { -+ this.inited = false; -+ this.url = this.environmentService.getNotificationsUrl(); -+ -+ // Set notifications server URL to `https://-` to effectively disable communication -+ // with the notifications server from the client app -+ if (this.url === "https://-") { -+ return; -+ } - -- this.signalrConnection = new signalR.HubConnectionBuilder() -- .withUrl(this.url + '/hub', { -- accessTokenFactory: () => this.apiService.getActiveBearerToken(), -- skipNegotiation: true, -- transport: signalR.HttpTransportType.WebSockets, -- }) -- .withHubProtocol(new signalRMsgPack.MessagePackHubProtocol() as signalR.IHubProtocol) -- // .configureLogging(signalR.LogLevel.Trace) -- .build(); -- -- this.signalrConnection.on('ReceiveMessage', -- (data: any) => this.processNotification(new NotificationResponse(data))); -- this.signalrConnection.on('Heartbeat', -- (data: any) => { /*console.log('Heartbeat!');*/ }); -- this.signalrConnection.onclose(() => { -- this.connected = false; -- this.reconnect(true); -- }); -- this.inited = true; -- if (await this.isAuthedAndUnlocked()) { -- await this.reconnect(false); -- } -+ if (this.signalrConnection != null) { -+ this.signalrConnection.off("ReceiveMessage"); -+ this.signalrConnection.off("Heartbeat"); -+ await this.signalrConnection.stop(); -+ this.connected = false; -+ this.signalrConnection = null; - } - -- async updateConnection(sync = false): Promise { -- if (!this.inited) { -- return; -- } -- try { -- if (await this.isAuthedAndUnlocked()) { -- await this.reconnect(sync); -- } else { -- await this.signalrConnection.stop(); -- } -- } catch (e) { -- this.logService.error(e.toString()); -- } -+ this.signalrConnection = new signalR.HubConnectionBuilder() -+ .withUrl(this.url + "/hub", { -+ accessTokenFactory: () => this.apiService.getActiveBearerToken(), -+ skipNegotiation: true, -+ transport: signalR.HttpTransportType.WebSockets, -+ }) -+ .withHubProtocol(new signalRMsgPack.MessagePackHubProtocol() as signalR.IHubProtocol) -+ // .configureLogging(signalR.LogLevel.Trace) -+ .build(); -+ -+ this.signalrConnection.on("ReceiveMessage", (data: any) => -+ this.processNotification(new NotificationResponse(data)) -+ ); -+ this.signalrConnection.on("Heartbeat", (data: any) => { -+ /*console.log('Heartbeat!');*/ -+ }); -+ this.signalrConnection.onclose(() => { -+ this.connected = false; -+ this.reconnect(true); -+ }); -+ this.inited = true; -+ if (await this.isAuthedAndUnlocked()) { -+ await this.reconnect(false); - } -+ } - -- async reconnectFromActivity(): Promise { -- this.inactive = false; -- if (this.inited && !this.connected) { -- await this.reconnect(true); -- } -+ async updateConnection(sync = false): Promise { -+ if (!this.inited) { -+ return; -+ } -+ try { -+ if (await this.isAuthedAndUnlocked()) { -+ await this.reconnect(sync); -+ } else { -+ await this.signalrConnection.stop(); -+ } -+ } catch (e) { -+ this.logService.error(e.toString()); - } -+ } - -- async disconnectFromInactivity(): Promise { -- this.inactive = true; -- if (this.inited && this.connected) { -- await this.signalrConnection.stop(); -- } -+ async reconnectFromActivity(): Promise { -+ this.inactive = false; -+ if (this.inited && !this.connected) { -+ await this.reconnect(true); - } -+ } - -- private async processNotification(notification: NotificationResponse) { -- const appId = await this.appIdService.getAppId(); -- if (notification == null || notification.contextId === appId) { -- return; -- } -+ async disconnectFromInactivity(): Promise { -+ this.inactive = true; -+ if (this.inited && this.connected) { -+ await this.signalrConnection.stop(); -+ } -+ } - -- const isAuthenticated = await this.userService.isAuthenticated(); -- const payloadUserId = notification.payload.userId || notification.payload.UserId; -- const myUserId = await this.userService.getUserId(); -- if (isAuthenticated && payloadUserId != null && payloadUserId !== myUserId) { -- return; -- } -+ private async processNotification(notification: NotificationResponse) { -+ const appId = await this.appIdService.getAppId(); -+ if (notification == null || notification.contextId === appId) { -+ return; -+ } - -- switch (notification.type) { -- case NotificationType.SyncCipherCreate: -- case NotificationType.SyncCipherUpdate: -- await this.syncService.syncUpsertCipher(notification.payload as SyncCipherNotification, -- notification.type === NotificationType.SyncCipherUpdate); -- break; -- case NotificationType.SyncCipherDelete: -- case NotificationType.SyncLoginDelete: -- await this.syncService.syncDeleteCipher(notification.payload as SyncCipherNotification); -- break; -- case NotificationType.SyncFolderCreate: -- case NotificationType.SyncFolderUpdate: -- await this.syncService.syncUpsertFolder(notification.payload as SyncFolderNotification, -- notification.type === NotificationType.SyncFolderUpdate); -- break; -- case NotificationType.SyncFolderDelete: -- await this.syncService.syncDeleteFolder(notification.payload as SyncFolderNotification); -- break; -- case NotificationType.SyncVault: -- case NotificationType.SyncCiphers: -- case NotificationType.SyncSettings: -- if (isAuthenticated) { -- await this.syncService.fullSync(false); -- } -- break; -- case NotificationType.SyncOrgKeys: -- if (isAuthenticated) { -- await this.syncService.fullSync(true); -- // Stop so a reconnect can be made -- await this.signalrConnection.stop(); -- } -- break; -- case NotificationType.LogOut: -- if (isAuthenticated) { -- this.logoutCallback(); -- } -- break; -- case NotificationType.SyncSendCreate: -- case NotificationType.SyncSendUpdate: -- await this.syncService.syncUpsertSend(notification.payload as SyncSendNotification, -- notification.type === NotificationType.SyncSendUpdate); -- break; -- case NotificationType.SyncSendDelete: -- await this.syncService.syncDeleteSend(notification.payload as SyncSendNotification); -- default: -- break; -- } -+ const isAuthenticated = await this.stateService.getIsAuthenticated(); -+ const payloadUserId = notification.payload.userId || notification.payload.UserId; -+ const myUserId = await this.stateService.getUserId(); -+ if (isAuthenticated && payloadUserId != null && payloadUserId !== myUserId) { -+ return; - } - -- private async reconnect(sync: boolean) { -- if (this.reconnectTimer != null) { -- clearTimeout(this.reconnectTimer); -- this.reconnectTimer = null; -- } -- if (this.connected || !this.inited || this.inactive) { -- return; -- } -- const authedAndUnlocked = await this.isAuthedAndUnlocked(); -- if (!authedAndUnlocked) { -- return; -- } -+ switch (notification.type) { -+ case NotificationType.SyncCipherCreate: -+ case NotificationType.SyncCipherUpdate: -+ await this.syncService.syncUpsertCipher( -+ notification.payload as SyncCipherNotification, -+ notification.type === NotificationType.SyncCipherUpdate -+ ); -+ break; -+ case NotificationType.SyncCipherDelete: -+ case NotificationType.SyncLoginDelete: -+ await this.syncService.syncDeleteCipher(notification.payload as SyncCipherNotification); -+ break; -+ case NotificationType.SyncFolderCreate: -+ case NotificationType.SyncFolderUpdate: -+ await this.syncService.syncUpsertFolder( -+ notification.payload as SyncFolderNotification, -+ notification.type === NotificationType.SyncFolderUpdate -+ ); -+ break; -+ case NotificationType.SyncFolderDelete: -+ await this.syncService.syncDeleteFolder(notification.payload as SyncFolderNotification); -+ break; -+ case NotificationType.SyncVault: -+ case NotificationType.SyncCiphers: -+ case NotificationType.SyncSettings: -+ if (isAuthenticated) { -+ await this.syncService.fullSync(false); -+ } -+ break; -+ case NotificationType.SyncOrgKeys: -+ if (isAuthenticated) { -+ await this.syncService.fullSync(true); -+ // Stop so a reconnect can be made -+ await this.signalrConnection.stop(); -+ } -+ break; -+ case NotificationType.LogOut: -+ if (isAuthenticated) { -+ this.logoutCallback(); -+ } -+ break; -+ case NotificationType.SyncSendCreate: -+ case NotificationType.SyncSendUpdate: -+ await this.syncService.syncUpsertSend( -+ notification.payload as SyncSendNotification, -+ notification.type === NotificationType.SyncSendUpdate -+ ); -+ break; -+ case NotificationType.SyncSendDelete: -+ await this.syncService.syncDeleteSend(notification.payload as SyncSendNotification); -+ default: -+ break; -+ } -+ } - -- try { -- await this.signalrConnection.start(); -- this.connected = true; -- if (sync) { -- await this.syncService.fullSync(false); -- } -- } catch (e) { -- this.logService.error(e); -- } -+ private async reconnect(sync: boolean) { -+ if (this.reconnectTimer != null) { -+ clearTimeout(this.reconnectTimer); -+ this.reconnectTimer = null; -+ } -+ if (this.connected || !this.inited || this.inactive) { -+ return; -+ } -+ const authedAndUnlocked = await this.isAuthedAndUnlocked(); -+ if (!authedAndUnlocked) { -+ return; -+ } - -- if (!this.connected) { -- this.reconnectTimer = setTimeout(() => this.reconnect(sync), this.random(120000, 300000)); -- } -+ try { -+ await this.signalrConnection.start(); -+ this.connected = true; -+ if (sync) { -+ await this.syncService.fullSync(false); -+ } -+ } catch (e) { -+ this.logService.error(e); - } - -- private async isAuthedAndUnlocked() { -- if (await this.userService.isAuthenticated()) { -- const locked = await this.vaultTimeoutService.isLocked(); -- return !locked; -- } -- return false; -+ if (!this.connected) { -+ this.reconnectTimer = setTimeout(() => this.reconnect(sync), this.random(120000, 300000)); - } -+ } - -- private random(min: number, max: number) { -- min = Math.ceil(min); -- max = Math.floor(max); -- return Math.floor(Math.random() * (max - min + 1)) + min; -+ private async isAuthedAndUnlocked() { -+ if (await this.stateService.getIsAuthenticated()) { -+ const locked = await this.vaultTimeoutService.isLocked(); -+ return !locked; - } -+ return false; -+ } -+ -+ private random(min: number, max: number) { -+ min = Math.ceil(min); -+ max = Math.floor(max); -+ return Math.floor(Math.random() * (max - min + 1)) + min; -+ } - } -diff --git a/jslib/common/src/services/organization.service.ts b/jslib/common/src/services/organization.service.ts -new file mode 100644 -index 00000000..10a86632 ---- /dev/null -+++ b/jslib/common/src/services/organization.service.ts -@@ -0,0 +1,50 @@ -+import { OrganizationService as OrganizationServiceAbstraction } from "../abstractions/organization.service"; -+import { StateService } from "../abstractions/state.service"; -+ -+import { OrganizationData } from "../models/data/organizationData"; -+ -+import { Organization } from "../models/domain/organization"; -+ -+export class OrganizationService implements OrganizationServiceAbstraction { -+ constructor(private stateService: StateService) {} -+ -+ async get(id: string): Promise { -+ const organizations = await this.stateService.getOrganizations(); -+ if (organizations == null || !organizations.hasOwnProperty(id)) { -+ return null; -+ } -+ -+ return new Organization(organizations[id]); -+ } -+ -+ async getByIdentifier(identifier: string): Promise { -+ const organizations = await this.getAll(); -+ if (organizations == null || organizations.length === 0) { -+ return null; -+ } -+ -+ return organizations.find((o) => o.identifier === identifier); -+ } -+ -+ async getAll(userId?: string): Promise { -+ const organizations = await this.stateService.getOrganizations({ userId: userId }); -+ const response: Organization[] = []; -+ for (const id in organizations) { -+ if (organizations.hasOwnProperty(id) && !organizations[id].isProviderUser) { -+ response.push(new Organization(organizations[id])); -+ } -+ } -+ return response; -+ } -+ -+ async save(organizations: { [id: string]: OrganizationData }) { -+ return await this.stateService.setOrganizations(organizations); -+ } -+ -+ async canManageSponsorships(): Promise { -+ const orgs = await this.getAll(); -+ return orgs.some( -+ (o) => o.familySponsorshipAvailable || o.familySponsorshipFriendlyName !== null -+ ); -+ } -+} -diff --git a/jslib/common/src/services/passwordGeneration.service.ts b/jslib/common/src/services/passwordGeneration.service.ts -index ab4af5b9..4e5d5872 100644 ---- a/jslib/common/src/services/passwordGeneration.service.ts -+++ b/jslib/common/src/services/passwordGeneration.service.ts -@@ -1,560 +1,575 @@ --import * as zxcvbn from 'zxcvbn'; -+import * as zxcvbn from "zxcvbn"; - --import { EncString } from '../models/domain/encString'; --import { GeneratedPasswordHistory } from '../models/domain/generatedPasswordHistory'; --import { PasswordGeneratorPolicyOptions } from '../models/domain/passwordGeneratorPolicyOptions'; --import { Policy } from '../models/domain/policy'; -+import { EncString } from "../models/domain/encString"; -+import { GeneratedPasswordHistory } from "../models/domain/generatedPasswordHistory"; -+import { PasswordGeneratorPolicyOptions } from "../models/domain/passwordGeneratorPolicyOptions"; -+import { Policy } from "../models/domain/policy"; - --import { CryptoService } from '../abstractions/crypto.service'; --import { -- PasswordGenerationService as PasswordGenerationServiceAbstraction, --} from '../abstractions/passwordGeneration.service'; --import { PolicyService } from '../abstractions/policy.service'; --import { StorageService } from '../abstractions/storage.service'; -+import { CryptoService } from "../abstractions/crypto.service"; -+import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "../abstractions/passwordGeneration.service"; -+import { PolicyService } from "../abstractions/policy.service"; -+import { StateService } from "../abstractions/state.service"; - --import { EEFLongWordList } from '../misc/wordlist'; -+import { EEFLongWordList } from "../misc/wordlist"; - --import { PolicyType } from '../enums/policyType'; -+import { PolicyType } from "../enums/policyType"; - - const DefaultOptions = { -- length: 14, -- ambiguous: false, -- number: true, -- minNumber: 1, -- uppercase: true, -- minUppercase: 0, -- lowercase: true, -- minLowercase: 0, -- special: false, -- minSpecial: 1, -- type: 'password', -- numWords: 3, -- wordSeparator: '-', -- capitalize: false, -- includeNumber: false, --}; -- --const Keys = { -- options: 'passwordGenerationOptions', -- history: 'generatedPasswordHistory', -+ length: 14, -+ ambiguous: false, -+ number: true, -+ minNumber: 1, -+ uppercase: true, -+ minUppercase: 0, -+ lowercase: true, -+ minLowercase: 0, -+ special: false, -+ minSpecial: 1, -+ type: "password", -+ numWords: 3, -+ wordSeparator: "-", -+ capitalize: false, -+ includeNumber: false, - }; - - const MaxPasswordsInHistory = 100; - - export class PasswordGenerationService implements PasswordGenerationServiceAbstraction { -- private optionsCache: any; -- private history: GeneratedPasswordHistory[]; -- -- constructor(private cryptoService: CryptoService, private storageService: StorageService, -- private policyService: PolicyService) { } -- -- async generatePassword(options: any): Promise { -- // overload defaults with given options -- const o = Object.assign({}, DefaultOptions, options); -- -- if (o.type === 'passphrase') { -- return this.generatePassphrase(options); -- } -- -- // sanitize -- this.sanitizePasswordLength(o, true); -- -- const minLength: number = o.minUppercase + o.minLowercase + o.minNumber + o.minSpecial; -- if (o.length < minLength) { -- o.length = minLength; -- } -- -- const positions: string[] = []; -- if (o.lowercase && o.minLowercase > 0) { -- for (let i = 0; i < o.minLowercase; i++) { -- positions.push('l'); -- } -- } -- if (o.uppercase && o.minUppercase > 0) { -- for (let i = 0; i < o.minUppercase; i++) { -- positions.push('u'); -- } -- } -- if (o.number && o.minNumber > 0) { -- for (let i = 0; i < o.minNumber; i++) { -- positions.push('n'); -- } -- } -- if (o.special && o.minSpecial > 0) { -- for (let i = 0; i < o.minSpecial; i++) { -- positions.push('s'); -- } -- } -- while (positions.length < o.length) { -- positions.push('a'); -- } -- -- // shuffle -- await this.shuffleArray(positions); -- -- // build out the char sets -- let allCharSet = ''; -- -- let lowercaseCharSet = 'abcdefghijkmnopqrstuvwxyz'; -- if (o.ambiguous) { -- lowercaseCharSet += 'l'; -- } -- if (o.lowercase) { -- allCharSet += lowercaseCharSet; -- } -- -- let uppercaseCharSet = 'ABCDEFGHJKLMNPQRSTUVWXYZ'; -- if (o.ambiguous) { -- uppercaseCharSet += 'IO'; -- } -- if (o.uppercase) { -- allCharSet += uppercaseCharSet; -- } -- -- let numberCharSet = '23456789'; -- if (o.ambiguous) { -- numberCharSet += '01'; -- } -- if (o.number) { -- allCharSet += numberCharSet; -- } -- -- const specialCharSet = '!@#$%^&*'; -- if (o.special) { -- allCharSet += specialCharSet; -- } -- -- let password = ''; -- for (let i = 0; i < o.length; i++) { -- let positionChars: string; -- switch (positions[i]) { -- case 'l': -- positionChars = lowercaseCharSet; -- break; -- case 'u': -- positionChars = uppercaseCharSet; -- break; -- case 'n': -- positionChars = numberCharSet; -- break; -- case 's': -- positionChars = specialCharSet; -- break; -- case 'a': -- positionChars = allCharSet; -- break; -- default: -- break; -- } -- -- const randomCharIndex = await this.cryptoService.randomNumber(0, positionChars.length - 1); -- password += positionChars.charAt(randomCharIndex); -- } -- -- return password; -- } -- -- async generatePassphrase(options: any): Promise { -- const o = Object.assign({}, DefaultOptions, options); -- -- if (o.numWords == null || o.numWords <= 2) { -- o.numWords = DefaultOptions.numWords; -- } -- if (o.wordSeparator == null || o.wordSeparator.length === 0 || o.wordSeparator.length > 1) { -- o.wordSeparator = ' '; -- } -- if (o.capitalize == null) { -- o.capitalize = false; -- } -- if (o.includeNumber == null) { -- o.includeNumber = false; -- } -- -- const listLength = EEFLongWordList.length - 1; -- const wordList = new Array(o.numWords); -- for (let i = 0; i < o.numWords; i++) { -- const wordIndex = await this.cryptoService.randomNumber(0, listLength); -- if (o.capitalize) { -- wordList[i] = this.capitalize(EEFLongWordList[wordIndex]); -- } else { -- wordList[i] = EEFLongWordList[wordIndex]; -- } -- } -- -- if (o.includeNumber) { -- await this.appendRandomNumberToRandomWord(wordList); -- } -- return wordList.join(o.wordSeparator); -- } -- -- async getOptions(): Promise<[any, PasswordGeneratorPolicyOptions]> { -- if (this.optionsCache == null) { -- const options = await this.storageService.get(Keys.options); -- if (options == null) { -- this.optionsCache = DefaultOptions; -- } else { -- this.optionsCache = Object.assign({}, DefaultOptions, options); -- } -- } -- const enforcedOptions = await this.enforcePasswordGeneratorPoliciesOnOptions(this.optionsCache); -- this.optionsCache = enforcedOptions[0]; -- return [this.optionsCache, enforcedOptions[1]]; -- } -- -- async enforcePasswordGeneratorPoliciesOnOptions(options: any): Promise<[any, PasswordGeneratorPolicyOptions]> { -- let enforcedPolicyOptions = await this.getPasswordGeneratorPolicyOptions(); -- if (enforcedPolicyOptions != null) { -- if (options.length < enforcedPolicyOptions.minLength) { -- options.length = enforcedPolicyOptions.minLength; -- } -- -- if (enforcedPolicyOptions.useUppercase) { -- options.uppercase = true; -- } -- -- if (enforcedPolicyOptions.useLowercase) { -- options.lowercase = true; -- } -- -- if (enforcedPolicyOptions.useNumbers) { -- options.number = true; -- } -- -- if (options.minNumber < enforcedPolicyOptions.numberCount) { -- options.minNumber = enforcedPolicyOptions.numberCount; -- } -- -- if (enforcedPolicyOptions.useSpecial) { -- options.special = true; -- } -- -- if (options.minSpecial < enforcedPolicyOptions.specialCount) { -- options.minSpecial = enforcedPolicyOptions.specialCount; -- } -- -- // Must normalize these fields because the receiving call expects all options to pass the current rules -- if (options.minSpecial + options.minNumber > options.length) { -- options.minSpecial = options.length - options.minNumber; -- } -- -- if (options.numWords < enforcedPolicyOptions.minNumberWords) { -- options.numWords = enforcedPolicyOptions.minNumberWords; -- } -- -- if (enforcedPolicyOptions.capitalize) { -- options.capitalize = true; -- } -- -- if (enforcedPolicyOptions.includeNumber) { -- options.includeNumber = true; -- } -- -- // Force default type if password/passphrase selected via policy -- if (enforcedPolicyOptions.defaultType === 'password' || -- enforcedPolicyOptions.defaultType === 'passphrase') { -- options.type = enforcedPolicyOptions.defaultType; -- } -- } else { // UI layer expects an instantiated object to prevent more explicit null checks -- enforcedPolicyOptions = new PasswordGeneratorPolicyOptions(); -- } -- return [options, enforcedPolicyOptions]; -- } -- -- async getPasswordGeneratorPolicyOptions(): Promise { -- const policies: Policy[] = this.policyService == null ? null : -- await this.policyService.getAll(PolicyType.PasswordGenerator); -- let enforcedOptions: PasswordGeneratorPolicyOptions = null; -- -- if (policies == null || policies.length === 0) { -- return enforcedOptions; -- } -- -- policies.forEach(currentPolicy => { -- if (!currentPolicy.enabled || currentPolicy.data == null) { -- return; -- } -- -- if (enforcedOptions == null) { -- enforcedOptions = new PasswordGeneratorPolicyOptions(); -- } -- -- // Password wins in multi-org collisions -- if (currentPolicy.data.defaultType != null && enforcedOptions.defaultType !== 'password') { -- enforcedOptions.defaultType = currentPolicy.data.defaultType; -- } -- -- if (currentPolicy.data.minLength != null -- && currentPolicy.data.minLength > enforcedOptions.minLength) { -- enforcedOptions.minLength = currentPolicy.data.minLength; -- } -- -- if (currentPolicy.data.useUpper) { -- enforcedOptions.useUppercase = true; -- } -- -- if (currentPolicy.data.useLower) { -- enforcedOptions.useLowercase = true; -- } -- -- if (currentPolicy.data.useNumbers) { -- enforcedOptions.useNumbers = true; -- } -+ constructor( -+ private cryptoService: CryptoService, -+ private policyService: PolicyService, -+ private stateService: StateService -+ ) {} -+ -+ async generatePassword(options: any): Promise { -+ // overload defaults with given options -+ const o = Object.assign({}, DefaultOptions, options); -+ -+ if (o.type === "passphrase") { -+ return this.generatePassphrase(options); -+ } -+ -+ // sanitize -+ this.sanitizePasswordLength(o, true); -+ -+ const minLength: number = o.minUppercase + o.minLowercase + o.minNumber + o.minSpecial; -+ if (o.length < minLength) { -+ o.length = minLength; -+ } -+ -+ const positions: string[] = []; -+ if (o.lowercase && o.minLowercase > 0) { -+ for (let i = 0; i < o.minLowercase; i++) { -+ positions.push("l"); -+ } -+ } -+ if (o.uppercase && o.minUppercase > 0) { -+ for (let i = 0; i < o.minUppercase; i++) { -+ positions.push("u"); -+ } -+ } -+ if (o.number && o.minNumber > 0) { -+ for (let i = 0; i < o.minNumber; i++) { -+ positions.push("n"); -+ } -+ } -+ if (o.special && o.minSpecial > 0) { -+ for (let i = 0; i < o.minSpecial; i++) { -+ positions.push("s"); -+ } -+ } -+ while (positions.length < o.length) { -+ positions.push("a"); -+ } - -- if (currentPolicy.data.minNumbers != null -- && currentPolicy.data.minNumbers > enforcedOptions.numberCount) { -- enforcedOptions.numberCount = currentPolicy.data.minNumbers; -- } -- -- if (currentPolicy.data.useSpecial) { -- enforcedOptions.useSpecial = true; -- } -- -- if (currentPolicy.data.minSpecial != null -- && currentPolicy.data.minSpecial > enforcedOptions.specialCount) { -- enforcedOptions.specialCount = currentPolicy.data.minSpecial; -- } -- -- if (currentPolicy.data.minNumberWords != null -- && currentPolicy.data.minNumberWords > enforcedOptions.minNumberWords) { -- enforcedOptions.minNumberWords = currentPolicy.data.minNumberWords; -- } -- -- if (currentPolicy.data.capitalize) { -- enforcedOptions.capitalize = true; -- } -- -- if (currentPolicy.data.includeNumber) { -- enforcedOptions.includeNumber = true; -- } -- }); -- -- return enforcedOptions; -- } -- -- async saveOptions(options: any) { -- await this.storageService.save(Keys.options, options); -- this.optionsCache = options; -- } -- -- async getHistory(): Promise { -- const hasKey = await this.cryptoService.hasKey(); -- if (!hasKey) { -- return new Array(); -- } -- -- if (!this.history) { -- const encrypted = await this.storageService.get(Keys.history); -- this.history = await this.decryptHistory(encrypted); -- } -- -- return this.history || new Array(); -- } -- -- async addHistory(password: string): Promise { -- // Cannot add new history if no key is available -- const hasKey = await this.cryptoService.hasKey(); -- if (!hasKey) { -- return; -- } -+ // shuffle -+ await this.shuffleArray(positions); - -- const currentHistory = await this.getHistory(); -+ // build out the char sets -+ let allCharSet = ""; - -- // Prevent duplicates -- if (this.matchesPrevious(password, currentHistory)) { -- return; -- } -- -- currentHistory.unshift(new GeneratedPasswordHistory(password, Date.now())); -+ let lowercaseCharSet = "abcdefghijkmnopqrstuvwxyz"; -+ if (o.ambiguous) { -+ lowercaseCharSet += "l"; -+ } -+ if (o.lowercase) { -+ allCharSet += lowercaseCharSet; -+ } - -- // Remove old items. -- if (currentHistory.length > MaxPasswordsInHistory) { -- currentHistory.pop(); -- } -+ let uppercaseCharSet = "ABCDEFGHJKLMNPQRSTUVWXYZ"; -+ if (o.ambiguous) { -+ uppercaseCharSet += "IO"; -+ } -+ if (o.uppercase) { -+ allCharSet += uppercaseCharSet; -+ } - -- const newHistory = await this.encryptHistory(currentHistory); -- return await this.storageService.save(Keys.history, newHistory); -+ let numberCharSet = "23456789"; -+ if (o.ambiguous) { -+ numberCharSet += "01"; -+ } -+ if (o.number) { -+ allCharSet += numberCharSet; - } -- -- async clear(): Promise { -- this.history = []; -- return await this.storageService.remove(Keys.history); -+ -+ const specialCharSet = "!@#$%^&*"; -+ if (o.special) { -+ allCharSet += specialCharSet; - } - -- passwordStrength(password: string, userInputs: string[] = null): zxcvbn.ZXCVBNResult { -- if (password == null || password.length === 0) { -- return null; -- } -- let globalUserInputs = ['bitwarden', 'bit', 'warden']; -- if (userInputs != null && userInputs.length > 0) { -- globalUserInputs = globalUserInputs.concat(userInputs); -- } -- // Use a hash set to get rid of any duplicate user inputs -- const finalUserInputs = Array.from(new Set(globalUserInputs)); -- const result = zxcvbn(password, finalUserInputs); -- return result; -+ let password = ""; -+ for (let i = 0; i < o.length; i++) { -+ let positionChars: string; -+ switch (positions[i]) { -+ case "l": -+ positionChars = lowercaseCharSet; -+ break; -+ case "u": -+ positionChars = uppercaseCharSet; -+ break; -+ case "n": -+ positionChars = numberCharSet; -+ break; -+ case "s": -+ positionChars = specialCharSet; -+ break; -+ case "a": -+ positionChars = allCharSet; -+ break; -+ default: -+ break; -+ } -+ -+ const randomCharIndex = await this.cryptoService.randomNumber(0, positionChars.length - 1); -+ password += positionChars.charAt(randomCharIndex); - } - -- normalizeOptions(options: any, enforcedPolicyOptions: PasswordGeneratorPolicyOptions) { -- options.minLowercase = 0; -- options.minUppercase = 0; -+ return password; -+ } -+ -+ async generatePassphrase(options: any): Promise { -+ const o = Object.assign({}, DefaultOptions, options); -+ -+ if (o.numWords == null || o.numWords <= 2) { -+ o.numWords = DefaultOptions.numWords; -+ } -+ if (o.wordSeparator == null || o.wordSeparator.length === 0 || o.wordSeparator.length > 1) { -+ o.wordSeparator = " "; -+ } -+ if (o.capitalize == null) { -+ o.capitalize = false; -+ } -+ if (o.includeNumber == null) { -+ o.includeNumber = false; -+ } - -- if (!options.length || options.length < 5) { -- options.length = 5; -- } else if (options.length > 128) { -- options.length = 128; -- } -+ const listLength = EEFLongWordList.length - 1; -+ const wordList = new Array(o.numWords); -+ for (let i = 0; i < o.numWords; i++) { -+ const wordIndex = await this.cryptoService.randomNumber(0, listLength); -+ if (o.capitalize) { -+ wordList[i] = this.capitalize(EEFLongWordList[wordIndex]); -+ } else { -+ wordList[i] = EEFLongWordList[wordIndex]; -+ } -+ } - -- if (options.length < enforcedPolicyOptions.minLength) { -- options.length = enforcedPolicyOptions.minLength; -- } -+ if (o.includeNumber) { -+ await this.appendRandomNumberToRandomWord(wordList); -+ } -+ return wordList.join(o.wordSeparator); -+ } -+ -+ async getOptions(): Promise<[any, PasswordGeneratorPolicyOptions]> { -+ let options = await this.stateService.getPasswordGenerationOptions(); -+ if (options == null) { -+ options = DefaultOptions; -+ } else { -+ options = Object.assign({}, DefaultOptions, options); -+ } -+ await this.stateService.setPasswordGenerationOptions(options); -+ const enforcedOptions = await this.enforcePasswordGeneratorPoliciesOnOptions(options); -+ options = enforcedOptions[0]; -+ return [options, enforcedOptions[1]]; -+ } -+ -+ async enforcePasswordGeneratorPoliciesOnOptions( -+ options: any -+ ): Promise<[any, PasswordGeneratorPolicyOptions]> { -+ let enforcedPolicyOptions = await this.getPasswordGeneratorPolicyOptions(); -+ if (enforcedPolicyOptions != null) { -+ if (options.length < enforcedPolicyOptions.minLength) { -+ options.length = enforcedPolicyOptions.minLength; -+ } -+ -+ if (enforcedPolicyOptions.useUppercase) { -+ options.uppercase = true; -+ } -+ -+ if (enforcedPolicyOptions.useLowercase) { -+ options.lowercase = true; -+ } -+ -+ if (enforcedPolicyOptions.useNumbers) { -+ options.number = true; -+ } -+ -+ if (options.minNumber < enforcedPolicyOptions.numberCount) { -+ options.minNumber = enforcedPolicyOptions.numberCount; -+ } -+ -+ if (enforcedPolicyOptions.useSpecial) { -+ options.special = true; -+ } -+ -+ if (options.minSpecial < enforcedPolicyOptions.specialCount) { -+ options.minSpecial = enforcedPolicyOptions.specialCount; -+ } -+ -+ // Must normalize these fields because the receiving call expects all options to pass the current rules -+ if (options.minSpecial + options.minNumber > options.length) { -+ options.minSpecial = options.length - options.minNumber; -+ } -+ -+ if (options.numWords < enforcedPolicyOptions.minNumberWords) { -+ options.numWords = enforcedPolicyOptions.minNumberWords; -+ } -+ -+ if (enforcedPolicyOptions.capitalize) { -+ options.capitalize = true; -+ } -+ -+ if (enforcedPolicyOptions.includeNumber) { -+ options.includeNumber = true; -+ } -+ -+ // Force default type if password/passphrase selected via policy -+ if ( -+ enforcedPolicyOptions.defaultType === "password" || -+ enforcedPolicyOptions.defaultType === "passphrase" -+ ) { -+ options.type = enforcedPolicyOptions.defaultType; -+ } -+ } else { -+ // UI layer expects an instantiated object to prevent more explicit null checks -+ enforcedPolicyOptions = new PasswordGeneratorPolicyOptions(); -+ } -+ return [options, enforcedPolicyOptions]; -+ } -+ -+ async getPasswordGeneratorPolicyOptions(): Promise { -+ const policies: Policy[] = -+ this.policyService == null -+ ? null -+ : await this.policyService.getAll(PolicyType.PasswordGenerator); -+ let enforcedOptions: PasswordGeneratorPolicyOptions = null; -+ -+ if (policies == null || policies.length === 0) { -+ return enforcedOptions; -+ } - -- if (!options.minNumber) { -- options.minNumber = 0; -- } else if (options.minNumber > options.length) { -- options.minNumber = options.length; -- } else if (options.minNumber > 9) { -- options.minNumber = 9; -- } -+ policies.forEach((currentPolicy) => { -+ if (!currentPolicy.enabled || currentPolicy.data == null) { -+ return; -+ } -+ -+ if (enforcedOptions == null) { -+ enforcedOptions = new PasswordGeneratorPolicyOptions(); -+ } -+ -+ // Password wins in multi-org collisions -+ if (currentPolicy.data.defaultType != null && enforcedOptions.defaultType !== "password") { -+ enforcedOptions.defaultType = currentPolicy.data.defaultType; -+ } -+ -+ if ( -+ currentPolicy.data.minLength != null && -+ currentPolicy.data.minLength > enforcedOptions.minLength -+ ) { -+ enforcedOptions.minLength = currentPolicy.data.minLength; -+ } -+ -+ if (currentPolicy.data.useUpper) { -+ enforcedOptions.useUppercase = true; -+ } -+ -+ if (currentPolicy.data.useLower) { -+ enforcedOptions.useLowercase = true; -+ } -+ -+ if (currentPolicy.data.useNumbers) { -+ enforcedOptions.useNumbers = true; -+ } -+ -+ if ( -+ currentPolicy.data.minNumbers != null && -+ currentPolicy.data.minNumbers > enforcedOptions.numberCount -+ ) { -+ enforcedOptions.numberCount = currentPolicy.data.minNumbers; -+ } -+ -+ if (currentPolicy.data.useSpecial) { -+ enforcedOptions.useSpecial = true; -+ } -+ -+ if ( -+ currentPolicy.data.minSpecial != null && -+ currentPolicy.data.minSpecial > enforcedOptions.specialCount -+ ) { -+ enforcedOptions.specialCount = currentPolicy.data.minSpecial; -+ } -+ -+ if ( -+ currentPolicy.data.minNumberWords != null && -+ currentPolicy.data.minNumberWords > enforcedOptions.minNumberWords -+ ) { -+ enforcedOptions.minNumberWords = currentPolicy.data.minNumberWords; -+ } -+ -+ if (currentPolicy.data.capitalize) { -+ enforcedOptions.capitalize = true; -+ } -+ -+ if (currentPolicy.data.includeNumber) { -+ enforcedOptions.includeNumber = true; -+ } -+ }); -+ -+ return enforcedOptions; -+ } -+ -+ async saveOptions(options: any) { -+ await this.stateService.setPasswordGenerationOptions(options); -+ } -+ -+ async getHistory(): Promise { -+ const hasKey = await this.cryptoService.hasKey(); -+ if (!hasKey) { -+ return new Array(); -+ } - -- if (options.minNumber < enforcedPolicyOptions.numberCount) { -- options.minNumber = enforcedPolicyOptions.numberCount; -- } -+ if ((await this.stateService.getDecryptedPasswordGenerationHistory()) != null) { -+ const encrypted = await this.stateService.getEncryptedPasswordGenerationHistory(); -+ const decrypted = await this.decryptHistory(encrypted); -+ await this.stateService.setDecryptedPasswordGenerationHistory(decrypted); -+ } - -- if (!options.minSpecial) { -- options.minSpecial = 0; -- } else if (options.minSpecial > options.length) { -- options.minSpecial = options.length; -- } else if (options.minSpecial > 9) { -- options.minSpecial = 9; -- } -+ const passwordGenerationHistory = -+ await this.stateService.getDecryptedPasswordGenerationHistory(); -+ return passwordGenerationHistory != null -+ ? passwordGenerationHistory -+ : new Array(); -+ } -+ -+ async addHistory(password: string): Promise { -+ // Cannot add new history if no key is available -+ const hasKey = await this.cryptoService.hasKey(); -+ if (!hasKey) { -+ return; -+ } - -- if (options.minSpecial < enforcedPolicyOptions.specialCount) { -- options.minSpecial = enforcedPolicyOptions.specialCount; -- } -+ const currentHistory = await this.getHistory(); - -- if (options.minSpecial + options.minNumber > options.length) { -- options.minSpecial = options.length - options.minNumber; -- } -+ // Prevent duplicates -+ if (this.matchesPrevious(password, currentHistory)) { -+ return; -+ } - -- if (options.numWords == null || options.length < 3) { -- options.numWords = 3; -- } else if (options.numWords > 20) { -- options.numWords = 20; -- } -+ currentHistory.unshift(new GeneratedPasswordHistory(password, Date.now())); - -- if (options.numWords < enforcedPolicyOptions.minNumberWords) { -- options.numWords = enforcedPolicyOptions.minNumberWords; -- } -+ // Remove old items. -+ if (currentHistory.length > MaxPasswordsInHistory) { -+ currentHistory.pop(); -+ } - -- if (options.wordSeparator != null && options.wordSeparator.length > 1) { -- options.wordSeparator = options.wordSeparator[0]; -- } -- -- this.sanitizePasswordLength(options, false); -- } -- -- private capitalize(str: string) { -- return str.charAt(0).toUpperCase() + str.slice(1); -- } -- -- private async appendRandomNumberToRandomWord(wordList: string[]) { -- if (wordList == null || wordList.length <= 0) { -- return; -- } -- const index = await this.cryptoService.randomNumber(0, wordList.length - 1); -- const num = await this.cryptoService.randomNumber(0, 9); -- wordList[index] = wordList[index] + num; -- } -- -- private async encryptHistory(history: GeneratedPasswordHistory[]): Promise { -- if (history == null || history.length === 0) { -- return Promise.resolve([]); -- } -- -- const promises = history.map(async item => { -- const encrypted = await this.cryptoService.encrypt(item.password); -- return new GeneratedPasswordHistory(encrypted.encryptedString, item.date); -- }); -- -- return await Promise.all(promises); -- } -- -- private async decryptHistory(history: GeneratedPasswordHistory[]): Promise { -- if (history == null || history.length === 0) { -- return Promise.resolve([]); -- } -- -- const promises = history.map(async item => { -- const decrypted = await this.cryptoService.decryptToUtf8(new EncString(item.password)); -- return new GeneratedPasswordHistory(decrypted, item.date); -- }); -- -- return await Promise.all(promises); -- } -- -- private matchesPrevious(password: string, history: GeneratedPasswordHistory[]): boolean { -- if (history == null || history.length === 0) { -- return false; -- } -- -- return history[history.length - 1].password === password; -- } -- -- // ref: https://stackoverflow.com/a/12646864/1090359 -- private async shuffleArray(array: string[]) { -- for (let i = array.length - 1; i > 0; i--) { -- const j = await this.cryptoService.randomNumber(0, i); -- [array[i], array[j]] = [array[j], array[i]]; -- } -- } -- -- private sanitizePasswordLength(options: any, forGeneration: boolean) { -- let minUppercaseCalc = 0; -- let minLowercaseCalc = 0; -- let minNumberCalc: number = options.minNumber; -- let minSpecialCalc: number = options.minSpecial; -- -- if (options.uppercase && options.minUppercase <= 0) { -- minUppercaseCalc = 1; -- } else if (!options.uppercase) { -- minUppercaseCalc = 0; -- } -+ const newHistory = await this.encryptHistory(currentHistory); -+ return await this.stateService.setEncryptedPasswordGenerationHistory(newHistory); -+ } - -- if (options.lowercase && options.minLowercase <= 0) { -- minLowercaseCalc = 1; -- } else if (!options.lowercase) { -- minLowercaseCalc = 0; -- } -+ async clear(userId?: string): Promise { -+ await this.stateService.setEncryptedPasswordGenerationHistory(null, { userId: userId }); -+ await this.stateService.setDecryptedPasswordGenerationHistory(null, { userId: userId }); -+ } - -- if (options.number && options.minNumber <= 0) { -- minNumberCalc = 1; -- } else if (!options.number) { -- minNumberCalc = 0; -- } -+ passwordStrength(password: string, userInputs: string[] = null): zxcvbn.ZXCVBNResult { -+ if (password == null || password.length === 0) { -+ return null; -+ } -+ let globalUserInputs = ["bitwarden", "bit", "warden"]; -+ if (userInputs != null && userInputs.length > 0) { -+ globalUserInputs = globalUserInputs.concat(userInputs); -+ } -+ // Use a hash set to get rid of any duplicate user inputs -+ const finalUserInputs = Array.from(new Set(globalUserInputs)); -+ const result = zxcvbn(password, finalUserInputs); -+ return result; -+ } -+ -+ normalizeOptions(options: any, enforcedPolicyOptions: PasswordGeneratorPolicyOptions) { -+ options.minLowercase = 0; -+ options.minUppercase = 0; -+ -+ if (!options.length || options.length < 5) { -+ options.length = 5; -+ } else if (options.length > 128) { -+ options.length = 128; -+ } - -- if (options.special && options.minSpecial <= 0) { -- minSpecialCalc = 1; -- } else if (!options.special) { -- minSpecialCalc = 0; -- } -+ if (options.length < enforcedPolicyOptions.minLength) { -+ options.length = enforcedPolicyOptions.minLength; -+ } - -- // This should never happen but is a final safety net -- if (!options.length || options.length < 1) { -- options.length = 10; -- } -+ if (!options.minNumber) { -+ options.minNumber = 0; -+ } else if (options.minNumber > options.length) { -+ options.minNumber = options.length; -+ } else if (options.minNumber > 9) { -+ options.minNumber = 9; -+ } - -- const minLength: number = minUppercaseCalc + minLowercaseCalc + minNumberCalc + minSpecialCalc; -- // Normalize and Generation both require this modification -- if (options.length < minLength) { -- options.length = minLength; -- } -+ if (options.minNumber < enforcedPolicyOptions.numberCount) { -+ options.minNumber = enforcedPolicyOptions.numberCount; -+ } -+ -+ if (!options.minSpecial) { -+ options.minSpecial = 0; -+ } else if (options.minSpecial > options.length) { -+ options.minSpecial = options.length; -+ } else if (options.minSpecial > 9) { -+ options.minSpecial = 9; -+ } -+ -+ if (options.minSpecial < enforcedPolicyOptions.specialCount) { -+ options.minSpecial = enforcedPolicyOptions.specialCount; -+ } -+ -+ if (options.minSpecial + options.minNumber > options.length) { -+ options.minSpecial = options.length - options.minNumber; -+ } -+ -+ if (options.numWords == null || options.length < 3) { -+ options.numWords = 3; -+ } else if (options.numWords > 20) { -+ options.numWords = 20; -+ } -+ -+ if (options.numWords < enforcedPolicyOptions.minNumberWords) { -+ options.numWords = enforcedPolicyOptions.minNumberWords; -+ } -+ -+ if (options.wordSeparator != null && options.wordSeparator.length > 1) { -+ options.wordSeparator = options.wordSeparator[0]; -+ } -+ -+ this.sanitizePasswordLength(options, false); -+ } -+ -+ private capitalize(str: string) { -+ return str.charAt(0).toUpperCase() + str.slice(1); -+ } -+ -+ private async appendRandomNumberToRandomWord(wordList: string[]) { -+ if (wordList == null || wordList.length <= 0) { -+ return; -+ } -+ const index = await this.cryptoService.randomNumber(0, wordList.length - 1); -+ const num = await this.cryptoService.randomNumber(0, 9); -+ wordList[index] = wordList[index] + num; -+ } -+ -+ private async encryptHistory( -+ history: GeneratedPasswordHistory[] -+ ): Promise { -+ if (history == null || history.length === 0) { -+ return Promise.resolve([]); -+ } -+ -+ const promises = history.map(async (item) => { -+ const encrypted = await this.cryptoService.encrypt(item.password); -+ return new GeneratedPasswordHistory(encrypted.encryptedString, item.date); -+ }); -+ -+ return await Promise.all(promises); -+ } -+ -+ private async decryptHistory( -+ history: GeneratedPasswordHistory[] -+ ): Promise { -+ if (history == null || history.length === 0) { -+ return Promise.resolve([]); -+ } -+ -+ const promises = history.map(async (item) => { -+ const decrypted = await this.cryptoService.decryptToUtf8(new EncString(item.password)); -+ return new GeneratedPasswordHistory(decrypted, item.date); -+ }); -+ -+ return await Promise.all(promises); -+ } -+ -+ private matchesPrevious(password: string, history: GeneratedPasswordHistory[]): boolean { -+ if (history == null || history.length === 0) { -+ return false; -+ } -+ -+ return history[history.length - 1].password === password; -+ } -+ -+ // ref: https://stackoverflow.com/a/12646864/1090359 -+ private async shuffleArray(array: string[]) { -+ for (let i = array.length - 1; i > 0; i--) { -+ const j = await this.cryptoService.randomNumber(0, i); -+ [array[i], array[j]] = [array[j], array[i]]; -+ } -+ } -+ -+ private sanitizePasswordLength(options: any, forGeneration: boolean) { -+ let minUppercaseCalc = 0; -+ let minLowercaseCalc = 0; -+ let minNumberCalc: number = options.minNumber; -+ let minSpecialCalc: number = options.minSpecial; -+ -+ if (options.uppercase && options.minUppercase <= 0) { -+ minUppercaseCalc = 1; -+ } else if (!options.uppercase) { -+ minUppercaseCalc = 0; -+ } -+ -+ if (options.lowercase && options.minLowercase <= 0) { -+ minLowercaseCalc = 1; -+ } else if (!options.lowercase) { -+ minLowercaseCalc = 0; -+ } -+ -+ if (options.number && options.minNumber <= 0) { -+ minNumberCalc = 1; -+ } else if (!options.number) { -+ minNumberCalc = 0; -+ } -+ -+ if (options.special && options.minSpecial <= 0) { -+ minSpecialCalc = 1; -+ } else if (!options.special) { -+ minSpecialCalc = 0; -+ } -+ -+ // This should never happen but is a final safety net -+ if (!options.length || options.length < 1) { -+ options.length = 10; -+ } -+ -+ const minLength: number = minUppercaseCalc + minLowercaseCalc + minNumberCalc + minSpecialCalc; -+ // Normalize and Generation both require this modification -+ if (options.length < minLength) { -+ options.length = minLength; -+ } - -- // Apply other changes if the options object passed in is for generation -- if (forGeneration) { -- options.minUppercase = minUppercaseCalc; -- options.minLowercase = minLowercaseCalc; -- options.minNumber = minNumberCalc; -- options.minSpecial = minSpecialCalc; -- } -+ // Apply other changes if the options object passed in is for generation -+ if (forGeneration) { -+ options.minUppercase = minUppercaseCalc; -+ options.minLowercase = minLowercaseCalc; -+ options.minNumber = minNumberCalc; -+ options.minSpecial = minSpecialCalc; - } -+ } - } -diff --git a/jslib/common/src/services/policy.service.ts b/jslib/common/src/services/policy.service.ts -index 4d23448a..dce9af6f 100644 ---- a/jslib/common/src/services/policy.service.ts -+++ b/jslib/common/src/services/policy.service.ts -@@ -1,219 +1,249 @@ --import { PolicyService as PolicyServiceAbstraction } from '../abstractions/policy.service'; --import { StorageService } from '../abstractions/storage.service'; --import { UserService } from '../abstractions/user.service'; -+import { OrganizationService } from "../abstractions/organization.service"; -+import { PolicyService as PolicyServiceAbstraction } from "../abstractions/policy.service"; -+import { StateService } from "../abstractions/state.service"; - --import { PolicyData } from '../models/data/policyData'; -+import { PolicyData } from "../models/data/policyData"; - --import { MasterPasswordPolicyOptions } from '../models/domain/masterPasswordPolicyOptions'; --import { Organization } from '../models/domain/organization'; --import { Policy } from '../models/domain/policy'; --import { ResetPasswordPolicyOptions } from '../models/domain/resetPasswordPolicyOptions'; -+import { MasterPasswordPolicyOptions } from "../models/domain/masterPasswordPolicyOptions"; -+import { Organization } from "../models/domain/organization"; -+import { Policy } from "../models/domain/policy"; -+import { ResetPasswordPolicyOptions } from "../models/domain/resetPasswordPolicyOptions"; - --import { OrganizationUserStatusType } from '../enums/organizationUserStatusType'; --import { OrganizationUserType } from '../enums/organizationUserType'; --import { PolicyType } from '../enums/policyType'; -+import { OrganizationUserStatusType } from "../enums/organizationUserStatusType"; -+import { OrganizationUserType } from "../enums/organizationUserType"; -+import { PolicyType } from "../enums/policyType"; - --import { ApiService } from '../abstractions/api.service'; --import { ListResponse } from '../models/response/listResponse'; --import { PolicyResponse } from '../models/response/policyResponse'; -- --const Keys = { -- policiesPrefix: 'policies_', --}; -+import { ApiService } from "../abstractions/api.service"; -+import { ListResponse } from "../models/response/listResponse"; -+import { PolicyResponse } from "../models/response/policyResponse"; - - export class PolicyService implements PolicyServiceAbstraction { -- policyCache: Policy[]; -- -- constructor(private userService: UserService, private storageService: StorageService, -- private apiService: ApiService) { -- } -- -- clearCache(): void { -- this.policyCache = null; -+ policyCache: Policy[]; -+ -+ constructor( -+ private stateService: StateService, -+ private organizationService: OrganizationService, -+ private apiService: ApiService -+ ) {} -+ -+ async clearCache(): Promise { -+ await this.stateService.setDecryptedPolicies(null); -+ } -+ -+ async getAll(type?: PolicyType, userId?: string): Promise { -+ let response: Policy[] = []; -+ const decryptedPolicies = await this.stateService.getDecryptedPolicies({ userId: userId }); -+ if (decryptedPolicies != null) { -+ response = decryptedPolicies; -+ } else { -+ const diskPolicies = await this.stateService.getEncryptedPolicies({ userId: userId }); -+ for (const id in diskPolicies) { -+ if (diskPolicies.hasOwnProperty(id)) { -+ response.push(new Policy(diskPolicies[id])); -+ } -+ } -+ await this.stateService.setDecryptedPolicies(response, { userId: userId }); - } -- -- async getAll(type?: PolicyType): Promise { -- if (this.policyCache == null) { -- const userId = await this.userService.getUserId(); -- const policies = await this.storageService.get<{ [id: string]: PolicyData; }>( -- Keys.policiesPrefix + userId); -- const response: Policy[] = []; -- for (const id in policies) { -- if (policies.hasOwnProperty(id)) { -- response.push(new Policy(policies[id])); -- } -- } -- this.policyCache = response; -- } -- if (type != null) { -- return this.policyCache.filter(p => p.type === type); -- } else { -- return this.policyCache; -- } -+ if (type != null) { -+ return response.filter((policy) => policy.type === type); -+ } else { -+ return response; - } -+ } - -- async getPolicyForOrganization(policyType: PolicyType, organizationId: string): Promise { -- const org = await this.userService.getOrganization(organizationId); -- if (org?.isProviderUser) { -- const orgPolicies = await this.apiService.getPolicies(organizationId); -- const policy = orgPolicies.data.find(p => p.organizationId === organizationId); -- -- if (policy == null) { -- return null; -- } -+ async getPolicyForOrganization(policyType: PolicyType, organizationId: string): Promise { -+ const org = await this.organizationService.get(organizationId); -+ if (org?.isProviderUser) { -+ const orgPolicies = await this.apiService.getPolicies(organizationId); -+ const policy = orgPolicies.data.find((p) => p.organizationId === organizationId); - -- return new Policy(new PolicyData(policy)); -- } -+ if (policy == null) { -+ return null; -+ } - -- const policies = await this.getAll(policyType); -- return policies.find(p => p.organizationId === organizationId); -+ return new Policy(new PolicyData(policy)); - } - -- async replace(policies: { [id: string]: PolicyData; }): Promise { -- const userId = await this.userService.getUserId(); -- await this.storageService.save(Keys.policiesPrefix + userId, policies); -- this.policyCache = null; -+ const policies = await this.getAll(policyType); -+ return policies.find((p) => p.organizationId === organizationId); -+ } -+ -+ async replace(policies: { [id: string]: PolicyData }): Promise { -+ await this.stateService.setDecryptedPolicies(null); -+ await this.stateService.setEncryptedPolicies(policies); -+ } -+ -+ async clear(userId?: string): Promise { -+ await this.stateService.setDecryptedPolicies(null, { userId: userId }); -+ await this.stateService.setEncryptedPolicies(null, { userId: userId }); -+ } -+ -+ async getMasterPasswordPoliciesForInvitedUsers( -+ orgId: string -+ ): Promise { -+ const userId = await this.stateService.getUserId(); -+ const response = await this.apiService.getPoliciesByInvitedUser(orgId, userId); -+ const policies = await this.mapPoliciesFromToken(response); -+ return this.getMasterPasswordPolicyOptions(policies); -+ } -+ -+ async getMasterPasswordPolicyOptions(policies?: Policy[]): Promise { -+ let enforcedOptions: MasterPasswordPolicyOptions = null; -+ -+ if (policies == null) { -+ policies = await this.getAll(PolicyType.MasterPassword); -+ } else { -+ policies = policies.filter((p) => p.type === PolicyType.MasterPassword); - } - -- async clear(userId: string): Promise { -- await this.storageService.remove(Keys.policiesPrefix + userId); -- this.policyCache = null; -+ if (policies == null || policies.length === 0) { -+ return enforcedOptions; - } - -- async getMasterPasswordPolicyOptions(policies?: Policy[]): Promise { -- let enforcedOptions: MasterPasswordPolicyOptions = null; -- -- if (policies == null) { -- policies = await this.getAll(PolicyType.MasterPassword); -- } else { -- policies = policies.filter(p => p.type === PolicyType.MasterPassword); -- } -- -- if (policies == null || policies.length === 0) { -- return enforcedOptions; -- } -- -- policies.forEach(currentPolicy => { -- if (!currentPolicy.enabled || currentPolicy.data == null) { -- return; -- } -- -- if (enforcedOptions == null) { -- enforcedOptions = new MasterPasswordPolicyOptions(); -- } -- -- if (currentPolicy.data.minComplexity != null -- && currentPolicy.data.minComplexity > enforcedOptions.minComplexity) { -- enforcedOptions.minComplexity = currentPolicy.data.minComplexity; -- } -- -- if (currentPolicy.data.minLength != null -- && currentPolicy.data.minLength > enforcedOptions.minLength) { -- enforcedOptions.minLength = currentPolicy.data.minLength; -- } -- -- if (currentPolicy.data.requireUpper) { -- enforcedOptions.requireUpper = true; -- } -- -- if (currentPolicy.data.requireLower) { -- enforcedOptions.requireLower = true; -- } -- -- if (currentPolicy.data.requireNumbers) { -- enforcedOptions.requireNumbers = true; -- } -- -- if (currentPolicy.data.requireSpecial) { -- enforcedOptions.requireSpecial = true; -- } -- }); -+ policies.forEach((currentPolicy) => { -+ if (!currentPolicy.enabled || currentPolicy.data == null) { -+ return; -+ } -+ -+ if (enforcedOptions == null) { -+ enforcedOptions = new MasterPasswordPolicyOptions(); -+ } -+ -+ if ( -+ currentPolicy.data.minComplexity != null && -+ currentPolicy.data.minComplexity > enforcedOptions.minComplexity -+ ) { -+ enforcedOptions.minComplexity = currentPolicy.data.minComplexity; -+ } -+ -+ if ( -+ currentPolicy.data.minLength != null && -+ currentPolicy.data.minLength > enforcedOptions.minLength -+ ) { -+ enforcedOptions.minLength = currentPolicy.data.minLength; -+ } -+ -+ if (currentPolicy.data.requireUpper) { -+ enforcedOptions.requireUpper = true; -+ } -+ -+ if (currentPolicy.data.requireLower) { -+ enforcedOptions.requireLower = true; -+ } -+ -+ if (currentPolicy.data.requireNumbers) { -+ enforcedOptions.requireNumbers = true; -+ } -+ -+ if (currentPolicy.data.requireSpecial) { -+ enforcedOptions.requireSpecial = true; -+ } -+ }); -+ -+ return enforcedOptions; -+ } -+ -+ evaluateMasterPassword( -+ passwordStrength: number, -+ newPassword: string, -+ enforcedPolicyOptions: MasterPasswordPolicyOptions -+ ): boolean { -+ if (enforcedPolicyOptions == null) { -+ return true; -+ } - -- return enforcedOptions; -+ if ( -+ enforcedPolicyOptions.minComplexity > 0 && -+ enforcedPolicyOptions.minComplexity > passwordStrength -+ ) { -+ return false; - } - -- evaluateMasterPassword(passwordStrength: number, newPassword: string, -- enforcedPolicyOptions: MasterPasswordPolicyOptions): boolean { -- if (enforcedPolicyOptions == null) { -- return true; -- } -+ if ( -+ enforcedPolicyOptions.minLength > 0 && -+ enforcedPolicyOptions.minLength > newPassword.length -+ ) { -+ return false; -+ } - -- if (enforcedPolicyOptions.minComplexity > 0 && enforcedPolicyOptions.minComplexity > passwordStrength) { -- return false; -- } -+ if (enforcedPolicyOptions.requireUpper && newPassword.toLocaleLowerCase() === newPassword) { -+ return false; -+ } - -- if (enforcedPolicyOptions.minLength > 0 && enforcedPolicyOptions.minLength > newPassword.length) { -- return false; -- } -+ if (enforcedPolicyOptions.requireLower && newPassword.toLocaleUpperCase() === newPassword) { -+ return false; -+ } - -- if (enforcedPolicyOptions.requireUpper && newPassword.toLocaleLowerCase() === newPassword) { -- return false; -- } -+ if (enforcedPolicyOptions.requireNumbers && !/[0-9]/.test(newPassword)) { -+ return false; -+ } - -- if (enforcedPolicyOptions.requireLower && newPassword.toLocaleUpperCase() === newPassword) { -- return false; -- } -+ if (enforcedPolicyOptions.requireSpecial && !/[!@#$%\^&*]/g.test(newPassword)) { -+ return false; -+ } - -- if (enforcedPolicyOptions.requireNumbers && !(/[0-9]/.test(newPassword))) { -- return false; -- } -+ return true; -+ } - -- if (enforcedPolicyOptions.requireSpecial && !(/[!@#$%\^&*]/g.test(newPassword))) { -- return false; -- } -+ getResetPasswordPolicyOptions( -+ policies: Policy[], -+ orgId: string -+ ): [ResetPasswordPolicyOptions, boolean] { -+ const resetPasswordPolicyOptions = new ResetPasswordPolicyOptions(); - -- return true; -+ if (policies == null || orgId == null) { -+ return [resetPasswordPolicyOptions, false]; - } - -- getResetPasswordPolicyOptions(policies: Policy[], orgId: string): [ResetPasswordPolicyOptions, boolean] { -- const resetPasswordPolicyOptions = new ResetPasswordPolicyOptions(); -+ const policy = policies.find( -+ (p) => p.organizationId === orgId && p.type === PolicyType.ResetPassword && p.enabled -+ ); -+ resetPasswordPolicyOptions.autoEnrollEnabled = policy?.data?.autoEnrollEnabled ?? false; - -- if (policies == null || orgId == null) { -- return [resetPasswordPolicyOptions, false]; -- } -+ return [resetPasswordPolicyOptions, policy?.enabled ?? false]; -+ } - -- const policy = policies.find(p => p.organizationId === orgId && p.type === PolicyType.ResetPassword && p.enabled); -- resetPasswordPolicyOptions.autoEnrollEnabled = policy?.data?.autoEnrollEnabled ?? false; -- -- return [resetPasswordPolicyOptions, policy?.enabled ?? false]; -+ mapPoliciesFromToken(policiesResponse: ListResponse): Policy[] { -+ if (policiesResponse == null || policiesResponse.data == null) { -+ return null; - } - -- mapPoliciesFromToken(policiesResponse: ListResponse): Policy[] { -- if (policiesResponse == null || policiesResponse.data == null) { -- return null; -- } -- -- const policiesData = policiesResponse.data.map(p => new PolicyData(p)); -- return policiesData.map(p => new Policy(p)); -+ const policiesData = policiesResponse.data.map((p) => new PolicyData(p)); -+ return policiesData.map((p) => new Policy(p)); -+ } -+ -+ async policyAppliesToUser( -+ policyType: PolicyType, -+ policyFilter?: (policy: Policy) => boolean, -+ userId?: string -+ ) { -+ const policies = await this.getAll(policyType, userId); -+ const organizations = await this.organizationService.getAll(userId); -+ let filteredPolicies; -+ -+ if (policyFilter != null) { -+ filteredPolicies = policies.filter((p) => p.enabled && policyFilter(p)); -+ } else { -+ filteredPolicies = policies.filter((p) => p.enabled); - } - -- async policyAppliesToUser(policyType: PolicyType, policyFilter?: (policy: Policy) => boolean) { -- const policies = await this.getAll(policyType); -- const organizations = await this.userService.getAllOrganizations(); -- let filteredPolicies; -- -- if (policyFilter != null) { -- filteredPolicies = policies.filter(p => p.enabled && policyFilter(p)); -- } -- else { -- filteredPolicies = policies.filter(p => p.enabled); -- } -- -- const policySet = new Set(filteredPolicies.map(p => p.organizationId)); -- -- return organizations.some(o => -- o.enabled && -- o.status >= OrganizationUserStatusType.Accepted && -- o.usePolicies && -- !this.isExcemptFromPolicies(o, policyType) && -- policySet.has(o.id)); -+ const policySet = new Set(filteredPolicies.map((p) => p.organizationId)); -+ -+ return organizations.some( -+ (o) => -+ o.enabled && -+ o.status >= OrganizationUserStatusType.Accepted && -+ o.usePolicies && -+ !this.isExcemptFromPolicies(o, policyType) && -+ policySet.has(o.id) -+ ); -+ } -+ -+ private isExcemptFromPolicies(organization: Organization, policyType: PolicyType) { -+ if (policyType === PolicyType.MaximumVaultTimeout) { -+ return organization.type === OrganizationUserType.Owner; - } - -- private isExcemptFromPolicies(organization: Organization, policyType: PolicyType) { -- if (policyType === PolicyType.MaximumVaultTimeout) { -- return organization.type === OrganizationUserType.Owner; -- } -- -- return organization.isExemptFromPolicies; -- } -+ return organization.isExemptFromPolicies; -+ } - } -diff --git a/jslib/common/src/services/provider.service.ts b/jslib/common/src/services/provider.service.ts -new file mode 100644 -index 00000000..349e2082 ---- /dev/null -+++ b/jslib/common/src/services/provider.service.ts -@@ -0,0 +1,34 @@ -+import { ProviderService as ProviderServiceAbstraction } from "../abstractions/provider.service"; -+import { StateService } from "../abstractions/state.service"; -+ -+import { ProviderData } from "../models/data/providerData"; -+ -+import { Provider } from "../models/domain/provider"; -+ -+export class ProviderService implements ProviderServiceAbstraction { -+ constructor(private stateService: StateService) {} -+ -+ async get(id: string): Promise { -+ const providers = await this.stateService.getProviders(); -+ if (providers == null || !providers.hasOwnProperty(id)) { -+ return null; -+ } -+ -+ return new Provider(providers[id]); -+ } -+ -+ async getAll(): Promise { -+ const providers = await this.stateService.getProviders(); -+ const response: Provider[] = []; -+ for (const id in providers) { -+ if (providers.hasOwnProperty(id)) { -+ response.push(new Provider(providers[id])); -+ } -+ } -+ return response; -+ } -+ -+ async save(providers: { [id: string]: ProviderData }) { -+ await this.stateService.setProviders(providers); -+ } -+} -diff --git a/jslib/common/src/services/search.service.ts b/jslib/common/src/services/search.service.ts -index 91eb2b6b..97b0d247 100644 ---- a/jslib/common/src/services/search.service.ts -+++ b/jslib/common/src/services/search.service.ts -@@ -1,272 +1,287 @@ --import * as lunr from 'lunr'; -+import * as lunr from "lunr"; - --import { CipherView } from '../models/view/cipherView'; -+import { CipherView } from "../models/view/cipherView"; - --import { CipherService } from '../abstractions/cipher.service'; --import { I18nService } from '../abstractions/i18n.service'; --import { LogService } from '../abstractions/log.service'; --import { SearchService as SearchServiceAbstraction } from '../abstractions/search.service'; -+import { CipherService } from "../abstractions/cipher.service"; -+import { I18nService } from "../abstractions/i18n.service"; -+import { LogService } from "../abstractions/log.service"; -+import { SearchService as SearchServiceAbstraction } from "../abstractions/search.service"; - --import { CipherType } from '../enums/cipherType'; --import { FieldType } from '../enums/fieldType'; --import { UriMatchType } from '../enums/uriMatchType'; --import { SendView } from '../models/view/sendView'; -+import { CipherType } from "../enums/cipherType"; -+import { FieldType } from "../enums/fieldType"; -+import { UriMatchType } from "../enums/uriMatchType"; -+import { SendView } from "../models/view/sendView"; - - export class SearchService implements SearchServiceAbstraction { -- indexedEntityId?: string = null; -- private indexing = false; -- private index: lunr.Index = null; -- private searchableMinLength = 2; -+ indexedEntityId?: string = null; -+ private indexing = false; -+ private index: lunr.Index = null; -+ private searchableMinLength = 2; - -- constructor(private cipherService: CipherService, private logService: LogService, -- private i18nService: I18nService) { -- if (['zh-CN', 'zh-TW'].indexOf(i18nService.locale) !== -1) { -- this.searchableMinLength = 1; -- } -+ constructor( -+ private cipherService: CipherService, -+ private logService: LogService, -+ private i18nService: I18nService -+ ) { -+ if (["zh-CN", "zh-TW"].indexOf(i18nService.locale) !== -1) { -+ this.searchableMinLength = 1; - } -+ } - -- clearIndex(): void { -- this.indexedEntityId = null; -- this.index = null; -- } -+ clearIndex(): void { -+ this.indexedEntityId = null; -+ this.index = null; -+ } -+ -+ isSearchable(query: string): boolean { -+ const notSearchable = -+ query == null || -+ (this.index == null && query.length < this.searchableMinLength) || -+ (this.index != null && query.length < this.searchableMinLength && query.indexOf(">") !== 0); -+ return !notSearchable; -+ } - -- isSearchable(query: string): boolean { -- const notSearchable = query == null || (this.index == null && query.length < this.searchableMinLength) || -- (this.index != null && query.length < this.searchableMinLength && query.indexOf('>') !== 0); -- return !notSearchable; -+ async indexCiphers(indexedEntityId?: string, ciphers?: CipherView[]): Promise { -+ if (this.indexing) { -+ return; - } - -- async indexCiphers(indexedEntityId?: string, ciphers?: CipherView[]): Promise { -- if (this.indexing) { -- return; -+ this.logService.time("search indexing"); -+ this.indexing = true; -+ this.indexedEntityId = indexedEntityId; -+ this.index = null; -+ const builder = new lunr.Builder(); -+ builder.ref("id"); -+ builder.field("shortid", { boost: 100, extractor: (c: CipherView) => c.id.substr(0, 8) }); -+ builder.field("name", { boost: 10 }); -+ builder.field("subtitle", { -+ boost: 5, -+ extractor: (c: CipherView) => { -+ if (c.subTitle != null && c.type === CipherType.Card) { -+ return c.subTitle.replace(/\*/g, ""); - } -+ return c.subTitle; -+ }, -+ }); -+ builder.field("notes"); -+ builder.field("login.username", { -+ extractor: (c: CipherView) => -+ c.type === CipherType.Login && c.login != null ? c.login.username : null, -+ }); -+ builder.field("login.uris", { boost: 2, extractor: (c: CipherView) => this.uriExtractor(c) }); -+ builder.field("fields", { extractor: (c: CipherView) => this.fieldExtractor(c, false) }); -+ builder.field("fields_joined", { extractor: (c: CipherView) => this.fieldExtractor(c, true) }); -+ builder.field("attachments", { -+ extractor: (c: CipherView) => this.attachmentExtractor(c, false), -+ }); -+ builder.field("attachments_joined", { -+ extractor: (c: CipherView) => this.attachmentExtractor(c, true), -+ }); -+ builder.field("organizationid", { extractor: (c: CipherView) => c.organizationId }); -+ ciphers = ciphers || (await this.cipherService.getAllDecrypted()); -+ ciphers.forEach((c) => builder.add(c)); -+ this.index = builder.build(); - -- this.logService.time('search indexing'); -- this.indexing = true; -- this.indexedEntityId = indexedEntityId; -- this.index = null; -- const builder = new lunr.Builder(); -- builder.ref('id'); -- builder.field('shortid', { boost: 100, extractor: (c: CipherView) => c.id.substr(0, 8) }); -- builder.field('name', { boost: 10 }); -- builder.field('subtitle', { -- boost: 5, -- extractor: (c: CipherView) => { -- if (c.subTitle != null && c.type === CipherType.Card) { -- return c.subTitle.replace(/\*/g, ''); -- } -- return c.subTitle; -- }, -- }); -- builder.field('notes'); -- builder.field('login.username', { -- extractor: (c: CipherView) => c.type === CipherType.Login && c.login != null ? c.login.username : null, -- }); -- builder.field('login.uris', { boost: 2, extractor: (c: CipherView) => this.uriExtractor(c) }); -- builder.field('fields', { extractor: (c: CipherView) => this.fieldExtractor(c, false) }); -- builder.field('fields_joined', { extractor: (c: CipherView) => this.fieldExtractor(c, true) }); -- builder.field('attachments', { extractor: (c: CipherView) => this.attachmentExtractor(c, false) }); -- builder.field('attachments_joined', -- { extractor: (c: CipherView) => this.attachmentExtractor(c, true) }); -- builder.field('organizationid', { extractor: (c: CipherView) => c.organizationId }); -- ciphers = ciphers || await this.cipherService.getAllDecrypted(); -- ciphers.forEach(c => builder.add(c)); -- this.index = builder.build(); -+ this.indexing = false; - -- this.indexing = false; -+ this.logService.timeEnd("search indexing"); -+ } - -- this.logService.timeEnd('search indexing'); -+ async searchCiphers( -+ query: string, -+ filter: ((cipher: CipherView) => boolean) | ((cipher: CipherView) => boolean)[] = null, -+ ciphers: CipherView[] = null -+ ): Promise { -+ const results: CipherView[] = []; -+ if (query != null) { -+ query = query.trim().toLowerCase(); -+ } -+ if (query === "") { -+ query = null; - } - -- async searchCiphers(query: string, -- filter: (((cipher: CipherView) => boolean) | (((cipher: CipherView) => boolean)[])) = null, -- ciphers: CipherView[] = null): -- Promise { -- const results: CipherView[] = []; -- if (query != null) { -- query = query.trim().toLowerCase(); -- } -- if (query === '') { -- query = null; -- } -- -- if (ciphers == null) { -- ciphers = await this.cipherService.getAllDecrypted(); -- } -+ if (ciphers == null) { -+ ciphers = await this.cipherService.getAllDecrypted(); -+ } - -- if (filter != null && Array.isArray(filter) && filter.length > 0) { -- ciphers = ciphers.filter(c => filter.every(f => f == null || f(c))); -- } else if (filter != null) { -- ciphers = ciphers.filter(filter as (cipher: CipherView) => boolean); -- } -+ if (filter != null && Array.isArray(filter) && filter.length > 0) { -+ ciphers = ciphers.filter((c) => filter.every((f) => f == null || f(c))); -+ } else if (filter != null) { -+ ciphers = ciphers.filter(filter as (cipher: CipherView) => boolean); -+ } - -- if (!this.isSearchable(query)) { -- return ciphers; -- } -+ if (!this.isSearchable(query)) { -+ return ciphers; -+ } - -- if (this.indexing) { -- await new Promise(r => setTimeout(r, 250)); -- if (this.indexing) { -- await new Promise(r => setTimeout(r, 500)); -- } -- } -+ if (this.indexing) { -+ await new Promise((r) => setTimeout(r, 250)); -+ if (this.indexing) { -+ await new Promise((r) => setTimeout(r, 500)); -+ } -+ } - -- const index = this.getIndexForSearch(); -- if (index == null) { -- // Fall back to basic search if index is not available -- return this.searchCiphersBasic(ciphers, query); -- } -+ const index = this.getIndexForSearch(); -+ if (index == null) { -+ // Fall back to basic search if index is not available -+ return this.searchCiphersBasic(ciphers, query); -+ } - -- const ciphersMap = new Map(); -- ciphers.forEach(c => ciphersMap.set(c.id, c)); -+ const ciphersMap = new Map(); -+ ciphers.forEach((c) => ciphersMap.set(c.id, c)); - -- let searchResults: lunr.Index.Result[] = null; -- const isQueryString = query != null && query.length > 1 && query.indexOf('>') === 0; -- if (isQueryString) { -- try { -- searchResults = index.search(query.substr(1).trim()); -- } catch (e) { -- this.logService.error(e); -- } -- } else { -- // tslint:disable-next-line -- const soWild = lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING; -- searchResults = index.query(q => { -- lunr.tokenizer(query).forEach(token => { -- const t = token.toString(); -- q.term(t, { fields: ['name'], wildcard: soWild }); -- q.term(t, { fields: ['subtitle'], wildcard: soWild }); -- q.term(t, { fields: ['login.uris'], wildcard: soWild }); -- q.term(t, {}); -- }); -- }); -- } -+ let searchResults: lunr.Index.Result[] = null; -+ const isQueryString = query != null && query.length > 1 && query.indexOf(">") === 0; -+ if (isQueryString) { -+ try { -+ searchResults = index.search(query.substr(1).trim()); -+ } catch (e) { -+ this.logService.error(e); -+ } -+ } else { -+ // tslint:disable-next-line -+ const soWild = lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING; -+ searchResults = index.query((q) => { -+ lunr.tokenizer(query).forEach((token) => { -+ const t = token.toString(); -+ q.term(t, { fields: ["name"], wildcard: soWild }); -+ q.term(t, { fields: ["subtitle"], wildcard: soWild }); -+ q.term(t, { fields: ["login.uris"], wildcard: soWild }); -+ q.term(t, {}); -+ }); -+ }); -+ } - -- if (searchResults != null) { -- searchResults.forEach(r => { -- if (ciphersMap.has(r.ref)) { -- results.push(ciphersMap.get(r.ref)); -- } -- }); -+ if (searchResults != null) { -+ searchResults.forEach((r) => { -+ if (ciphersMap.has(r.ref)) { -+ results.push(ciphersMap.get(r.ref)); - } -- return results; -+ }); - } -+ return results; -+ } - -- searchCiphersBasic(ciphers: CipherView[], query: string, deleted: boolean = false) { -- query = query.trim().toLowerCase(); -- return ciphers.filter(c => { -- if (deleted !== c.isDeleted) { -- return false; -- } -- if (c.name != null && c.name.toLowerCase().indexOf(query) > -1) { -- return true; -- } -- if (query.length >= 8 && c.id.startsWith(query)) { -- return true; -- } -- if (c.subTitle != null && c.subTitle.toLowerCase().indexOf(query) > -1) { -- return true; -- } -- if (c.login && c.login.uri != null && c.login.uri.toLowerCase().indexOf(query) > -1) { -- return true; -- } -- return false; -- }); -- } -+ searchCiphersBasic(ciphers: CipherView[], query: string, deleted: boolean = false) { -+ query = query.trim().toLowerCase(); -+ return ciphers.filter((c) => { -+ if (deleted !== c.isDeleted) { -+ return false; -+ } -+ if (c.name != null && c.name.toLowerCase().indexOf(query) > -1) { -+ return true; -+ } -+ if (query.length >= 8 && c.id.startsWith(query)) { -+ return true; -+ } -+ if (c.subTitle != null && c.subTitle.toLowerCase().indexOf(query) > -1) { -+ return true; -+ } -+ if (c.login && c.login.uri != null && c.login.uri.toLowerCase().indexOf(query) > -1) { -+ return true; -+ } -+ return false; -+ }); -+ } - -- searchSends(sends: SendView[], query: string) { -- query = query.trim().toLocaleLowerCase(); -+ searchSends(sends: SendView[], query: string) { -+ query = query.trim().toLocaleLowerCase(); - -- return sends.filter(s => { -- if (s.name != null && s.name.toLowerCase().indexOf(query) > -1) { -- return true; -- } -- if (query.length >= 8 && (s.id.startsWith(query) || s.accessId.toLocaleLowerCase().startsWith(query) || (s.file?.id != null && s.file.id.startsWith(query)))) { -- return true; -- } -- if (s.notes != null && s.notes.toLowerCase().indexOf(query) > -1) { -- return true; -- } -- if (s.text?.text != null && s.text.text.toLowerCase().indexOf(query) > -1) { -- return true; -- } -- if (s.file?.fileName != null && s.file.fileName.toLowerCase().indexOf(query) > -1) { -- return true; -- } -- }); -- } -+ return sends.filter((s) => { -+ if (s.name != null && s.name.toLowerCase().indexOf(query) > -1) { -+ return true; -+ } -+ if ( -+ query.length >= 8 && -+ (s.id.startsWith(query) || -+ s.accessId.toLocaleLowerCase().startsWith(query) || -+ (s.file?.id != null && s.file.id.startsWith(query))) -+ ) { -+ return true; -+ } -+ if (s.notes != null && s.notes.toLowerCase().indexOf(query) > -1) { -+ return true; -+ } -+ if (s.text?.text != null && s.text.text.toLowerCase().indexOf(query) > -1) { -+ return true; -+ } -+ if (s.file?.fileName != null && s.file.fileName.toLowerCase().indexOf(query) > -1) { -+ return true; -+ } -+ }); -+ } - -- getIndexForSearch(): lunr.Index { -- return this.index; -- } -+ getIndexForSearch(): lunr.Index { -+ return this.index; -+ } - -- private fieldExtractor(c: CipherView, joined: boolean) { -- if (!c.hasFields) { -- return null; -- } -- let fields: string[] = []; -- c.fields.forEach(f => { -- if (f.name != null) { -- fields.push(f.name); -- } -- if (f.type === FieldType.Text && f.value != null) { -- fields.push(f.value); -- } -- }); -- fields = fields.filter(f => f.trim() !== ''); -- if (fields.length === 0) { -- return null; -- } -- return joined ? fields.join(' ') : fields; -+ private fieldExtractor(c: CipherView, joined: boolean) { -+ if (!c.hasFields) { -+ return null; -+ } -+ let fields: string[] = []; -+ c.fields.forEach((f) => { -+ if (f.name != null) { -+ fields.push(f.name); -+ } -+ if (f.type === FieldType.Text && f.value != null) { -+ fields.push(f.value); -+ } -+ }); -+ fields = fields.filter((f) => f.trim() !== ""); -+ if (fields.length === 0) { -+ return null; - } -+ return joined ? fields.join(" ") : fields; -+ } - -- private attachmentExtractor(c: CipherView, joined: boolean) { -- if (!c.hasAttachments) { -- return null; -- } -- let attachments: string[] = []; -- c.attachments.forEach(a => { -- if (a != null && a.fileName != null) { -- if (joined && a.fileName.indexOf('.') > -1) { -- attachments.push(a.fileName.substr(0, a.fileName.lastIndexOf('.'))); -- } else { -- attachments.push(a.fileName); -- } -- } -- }); -- attachments = attachments.filter(f => f.trim() !== ''); -- if (attachments.length === 0) { -- return null; -+ private attachmentExtractor(c: CipherView, joined: boolean) { -+ if (!c.hasAttachments) { -+ return null; -+ } -+ let attachments: string[] = []; -+ c.attachments.forEach((a) => { -+ if (a != null && a.fileName != null) { -+ if (joined && a.fileName.indexOf(".") > -1) { -+ attachments.push(a.fileName.substr(0, a.fileName.lastIndexOf("."))); -+ } else { -+ attachments.push(a.fileName); - } -- return joined ? attachments.join(' ') : attachments; -+ } -+ }); -+ attachments = attachments.filter((f) => f.trim() !== ""); -+ if (attachments.length === 0) { -+ return null; - } -+ return joined ? attachments.join(" ") : attachments; -+ } - -- private uriExtractor(c: CipherView) { -- if (c.type !== CipherType.Login || c.login == null || !c.login.hasUris) { -- return null; -- } -- const uris: string[] = []; -- c.login.uris.forEach(u => { -- if (u.uri == null || u.uri === '') { -- return; -- } -- if (u.hostname != null) { -- uris.push(u.hostname); -- return; -- } -- let uri = u.uri; -- if (u.match !== UriMatchType.RegularExpression) { -- const protocolIndex = uri.indexOf('://'); -- if (protocolIndex > -1) { -- uri = uri.substr(protocolIndex + 3); -- } -- const queryIndex = uri.search(/\?|&|#/); -- if (queryIndex > -1) { -- uri = uri.substring(0, queryIndex); -- } -- } -- uris.push(uri); -- }); -- return uris.length > 0 ? uris : null; -+ private uriExtractor(c: CipherView) { -+ if (c.type !== CipherType.Login || c.login == null || !c.login.hasUris) { -+ return null; - } -+ const uris: string[] = []; -+ c.login.uris.forEach((u) => { -+ if (u.uri == null || u.uri === "") { -+ return; -+ } -+ if (u.hostname != null) { -+ uris.push(u.hostname); -+ return; -+ } -+ let uri = u.uri; -+ if (u.match !== UriMatchType.RegularExpression) { -+ const protocolIndex = uri.indexOf("://"); -+ if (protocolIndex > -1) { -+ uri = uri.substr(protocolIndex + 3); -+ } -+ const queryIndex = uri.search(/\?|&|#/); -+ if (queryIndex > -1) { -+ uri = uri.substring(0, queryIndex); -+ } -+ } -+ uris.push(uri); -+ }); -+ return uris.length > 0 ? uris : null; -+ } - } -diff --git a/jslib/common/src/services/send.service.ts b/jslib/common/src/services/send.service.ts -index a5a9a699..630511d4 100644 ---- a/jslib/common/src/services/send.service.ts -+++ b/jslib/common/src/services/send.service.ts -@@ -1,285 +1,301 @@ --import { SendData } from '../models/data/sendData'; -+import { SendData } from "../models/data/sendData"; - --import { SendRequest } from '../models/request/sendRequest'; -+import { SendRequest } from "../models/request/sendRequest"; - --import { ErrorResponse } from '../models/response/errorResponse'; --import { SendResponse } from '../models/response/sendResponse'; -+import { ErrorResponse } from "../models/response/errorResponse"; -+import { SendResponse } from "../models/response/sendResponse"; - --import { EncArrayBuffer } from '../models/domain/encArrayBuffer'; --import { EncString } from '../models/domain/encString'; --import { Send } from '../models/domain/send'; --import { SendFile } from '../models/domain/sendFile'; --import { SendText } from '../models/domain/sendText'; --import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; -+import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; -+import { EncString } from "../models/domain/encString"; -+import { Send } from "../models/domain/send"; -+import { SendFile } from "../models/domain/sendFile"; -+import { SendText } from "../models/domain/sendText"; -+import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; - --import { FileUploadType } from '../enums/fileUploadType'; --import { SendType } from '../enums/sendType'; -+import { SendType } from "../enums/sendType"; - --import { SendView } from '../models/view/sendView'; -+import { SendView } from "../models/view/sendView"; - --import { ApiService } from '../abstractions/api.service'; --import { CryptoService } from '../abstractions/crypto.service'; --import { CryptoFunctionService } from '../abstractions/cryptoFunction.service'; --import { FileUploadService } from '../abstractions/fileUpload.service'; --import { I18nService } from '../abstractions/i18n.service'; --import { SendService as SendServiceAbstraction } from '../abstractions/send.service'; --import { StorageService } from '../abstractions/storage.service'; --import { UserService } from '../abstractions/user.service'; -+import { ApiService } from "../abstractions/api.service"; -+import { CryptoService } from "../abstractions/crypto.service"; -+import { CryptoFunctionService } from "../abstractions/cryptoFunction.service"; -+import { FileUploadService } from "../abstractions/fileUpload.service"; -+import { I18nService } from "../abstractions/i18n.service"; -+import { SendService as SendServiceAbstraction } from "../abstractions/send.service"; -+import { StateService } from "../abstractions/state.service"; - --import { Utils } from '../misc/utils'; -- --const Keys = { -- sendsPrefix: 'sends_', --}; -+import { Utils } from "../misc/utils"; - - export class SendService implements SendServiceAbstraction { -- decryptedSendCache: SendView[]; -- -- constructor(private cryptoService: CryptoService, private userService: UserService, -- private apiService: ApiService, private fileUploadService: FileUploadService, -- private storageService: StorageService, private i18nService: I18nService, -- private cryptoFunctionService: CryptoFunctionService) { } -- -- clearCache(): void { -- this.decryptedSendCache = null; -- } -- -- async encrypt(model: SendView, file: File | ArrayBuffer, password: string, -- key?: SymmetricCryptoKey): Promise<[Send, EncArrayBuffer]> { -- let fileData: EncArrayBuffer = null; -- const send = new Send(); -- send.id = model.id; -- send.type = model.type; -- send.disabled = model.disabled; -- send.hideEmail = model.hideEmail; -- send.maxAccessCount = model.maxAccessCount; -- if (model.key == null) { -- model.key = await this.cryptoFunctionService.randomBytes(16); -- model.cryptoKey = await this.cryptoService.makeSendKey(model.key); -- } -- if (password != null) { -- const passwordHash = await this.cryptoFunctionService.pbkdf2(password, model.key, 'sha256', 100000); -- send.password = Utils.fromBufferToB64(passwordHash); -- } -- send.key = await this.cryptoService.encrypt(model.key, key); -- send.name = await this.cryptoService.encrypt(model.name, model.cryptoKey); -- send.notes = await this.cryptoService.encrypt(model.notes, model.cryptoKey); -- if (send.type === SendType.Text) { -- send.text = new SendText(); -- send.text.text = await this.cryptoService.encrypt(model.text.text, model.cryptoKey); -- send.text.hidden = model.text.hidden; -- } else if (send.type === SendType.File) { -- send.file = new SendFile(); -- if (file != null) { -- if (file instanceof ArrayBuffer) { -- const [name, data] = await this.encryptFileData(model.file.fileName, file, model.cryptoKey); -- send.file.fileName = name; -- fileData = data; -- } else { -- fileData = await this.parseFile(send, file, model.cryptoKey); -- } -- } -- } -- -- return [send, fileData]; -+ constructor( -+ private cryptoService: CryptoService, -+ private apiService: ApiService, -+ private fileUploadService: FileUploadService, -+ private i18nService: I18nService, -+ private cryptoFunctionService: CryptoFunctionService, -+ private stateService: StateService -+ ) {} -+ -+ async clearCache(): Promise { -+ await this.stateService.setDecryptedSends(null); -+ } -+ -+ async encrypt( -+ model: SendView, -+ file: File | ArrayBuffer, -+ password: string, -+ key?: SymmetricCryptoKey -+ ): Promise<[Send, EncArrayBuffer]> { -+ let fileData: EncArrayBuffer = null; -+ const send = new Send(); -+ send.id = model.id; -+ send.type = model.type; -+ send.disabled = model.disabled; -+ send.hideEmail = model.hideEmail; -+ send.maxAccessCount = model.maxAccessCount; -+ if (model.key == null) { -+ model.key = await this.cryptoFunctionService.randomBytes(16); -+ model.cryptoKey = await this.cryptoService.makeSendKey(model.key); - } -- -- async get(id: string): Promise { -- const userId = await this.userService.getUserId(); -- const sends = await this.storageService.get<{ [id: string]: SendData; }>( -- Keys.sendsPrefix + userId); -- if (sends == null || !sends.hasOwnProperty(id)) { -- return null; -- } -- -- return new Send(sends[id]); -+ if (password != null) { -+ const passwordHash = await this.cryptoFunctionService.pbkdf2( -+ password, -+ model.key, -+ "sha256", -+ 100000 -+ ); -+ send.password = Utils.fromBufferToB64(passwordHash); - } -- -- async getAll(): Promise { -- const userId = await this.userService.getUserId(); -- const sends = await this.storageService.get<{ [id: string]: SendData; }>( -- Keys.sendsPrefix + userId); -- const response: Send[] = []; -- for (const id in sends) { -- if (sends.hasOwnProperty(id)) { -- response.push(new Send(sends[id])); -- } -+ send.key = await this.cryptoService.encrypt(model.key, key); -+ send.name = await this.cryptoService.encrypt(model.name, model.cryptoKey); -+ send.notes = await this.cryptoService.encrypt(model.notes, model.cryptoKey); -+ if (send.type === SendType.Text) { -+ send.text = new SendText(); -+ send.text.text = await this.cryptoService.encrypt(model.text.text, model.cryptoKey); -+ send.text.hidden = model.text.hidden; -+ } else if (send.type === SendType.File) { -+ send.file = new SendFile(); -+ if (file != null) { -+ if (file instanceof ArrayBuffer) { -+ const [name, data] = await this.encryptFileData( -+ model.file.fileName, -+ file, -+ model.cryptoKey -+ ); -+ send.file.fileName = name; -+ fileData = data; -+ } else { -+ fileData = await this.parseFile(send, file, model.cryptoKey); - } -- return response; -+ } - } - -- async getAllDecrypted(): Promise { -- if (this.decryptedSendCache != null) { -- return this.decryptedSendCache; -- } -- -- const hasKey = await this.cryptoService.hasKey(); -- if (!hasKey) { -- throw new Error('No key.'); -- } -+ return [send, fileData]; -+ } - -- const decSends: SendView[] = []; -- const promises: Promise[] = []; -- const sends = await this.getAll(); -- sends.forEach(send => { -- promises.push(send.decrypt().then(f => decSends.push(f))); -- }); -+ async get(id: string): Promise { -+ const sends = await this.stateService.getEncryptedSends(); -+ if (sends == null || !sends.hasOwnProperty(id)) { -+ return null; -+ } - -- await Promise.all(promises); -- decSends.sort(Utils.getSortFunction(this.i18nService, 'name')); -+ return new Send(sends[id]); -+ } - -- this.decryptedSendCache = decSends; -- return this.decryptedSendCache; -+ async getAll(): Promise { -+ const sends = await this.stateService.getEncryptedSends(); -+ const response: Send[] = []; -+ for (const id in sends) { -+ if (sends.hasOwnProperty(id)) { -+ response.push(new Send(sends[id])); -+ } - } -+ return response; -+ } - -- async saveWithServer(sendData: [Send, EncArrayBuffer]): Promise { -- const request = new SendRequest(sendData[0], sendData[1]?.buffer.byteLength); -- let response: SendResponse; -- if (sendData[0].id == null) { -- if (sendData[0].type === SendType.Text) { -- response = await this.apiService.postSend(request); -- } else { -- try { -- const uploadDataResponse = await this.apiService.postFileTypeSend(request); -- response = uploadDataResponse.sendResponse; -- -- await this.fileUploadService.uploadSendFile(uploadDataResponse, sendData[0].file.fileName, sendData[1]); -- } catch (e) { -- if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) { -- response = await this.legacyServerSendFileUpload(sendData, request); -- } else if (e instanceof ErrorResponse) { -- throw new Error((e as ErrorResponse).getSingleMessage()); -- } else { -- throw e; -- } -- } -- } -- sendData[0].id = response.id; -- sendData[0].accessId = response.accessId; -- } else { -- response = await this.apiService.putSend(sendData[0].id, request); -- } -+ async getAllDecrypted(): Promise { -+ let decSends = await this.stateService.getDecryptedSends(); -+ if (decSends != null) { -+ return decSends; -+ } - -- const userId = await this.userService.getUserId(); -- const data = new SendData(response, userId); -- await this.upsert(data); -+ decSends = []; -+ const hasKey = await this.cryptoService.hasKey(); -+ if (!hasKey) { -+ throw new Error("No key."); - } - -- /** -- * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. -- * This method still exists for backward compatibility with old server versions. -- */ -- async legacyServerSendFileUpload(sendData: [Send, EncArrayBuffer], request: SendRequest): Promise -- { -- const fd = new FormData(); -+ const promises: Promise[] = []; -+ const sends = await this.getAll(); -+ sends.forEach((send) => { -+ promises.push(send.decrypt().then((f) => decSends.push(f))); -+ }); -+ -+ await Promise.all(promises); -+ decSends.sort(Utils.getSortFunction(this.i18nService, "name")); -+ -+ await this.stateService.setDecryptedSends(decSends); -+ return decSends; -+ } -+ -+ async saveWithServer(sendData: [Send, EncArrayBuffer]): Promise { -+ const request = new SendRequest(sendData[0], sendData[1]?.buffer.byteLength); -+ let response: SendResponse; -+ if (sendData[0].id == null) { -+ if (sendData[0].type === SendType.Text) { -+ response = await this.apiService.postSend(request); -+ } else { - try { -- const blob = new Blob([sendData[1].buffer], { type: 'application/octet-stream' }); -- fd.append('model', JSON.stringify(request)); -- fd.append('data', blob, sendData[0].file.fileName.encryptedString); -+ const uploadDataResponse = await this.apiService.postFileTypeSend(request); -+ response = uploadDataResponse.sendResponse; -+ -+ await this.fileUploadService.uploadSendFile( -+ uploadDataResponse, -+ sendData[0].file.fileName, -+ sendData[1] -+ ); - } catch (e) { -- if (Utils.isNode && !Utils.isBrowser) { -- fd.append('model', JSON.stringify(request)); -- fd.append('data', Buffer.from(sendData[1].buffer) as any, { -- filepath: sendData[0].file.fileName.encryptedString, -- contentType: 'application/octet-stream', -- } as any); -- } else { -- throw e; -- } -+ if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) { -+ response = await this.legacyServerSendFileUpload(sendData, request); -+ } else if (e instanceof ErrorResponse) { -+ throw new Error((e as ErrorResponse).getSingleMessage()); -+ } else { -+ throw e; -+ } - } -- return await this.apiService.postSendFileLegacy(fd); -+ } -+ sendData[0].id = response.id; -+ sendData[0].accessId = response.accessId; -+ } else { -+ response = await this.apiService.putSend(sendData[0].id, request); - } - -- async upsert(send: SendData | SendData[]): Promise { -- const userId = await this.userService.getUserId(); -- let sends = await this.storageService.get<{ [id: string]: SendData; }>( -- Keys.sendsPrefix + userId); -- if (sends == null) { -- sends = {}; -- } -- -- if (send instanceof SendData) { -- const s = send as SendData; -- sends[s.id] = s; -- } else { -- (send as SendData[]).forEach(s => { -- sends[s.id] = s; -- }); -- } -- -- await this.storageService.save(Keys.sendsPrefix + userId, sends); -- this.decryptedSendCache = null; -+ const userId = await this.stateService.getUserId(); -+ const data = new SendData(response, userId); -+ await this.upsert(data); -+ } -+ -+ /** -+ * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. -+ * This method still exists for backward compatibility with old server versions. -+ */ -+ async legacyServerSendFileUpload( -+ sendData: [Send, EncArrayBuffer], -+ request: SendRequest -+ ): Promise { -+ const fd = new FormData(); -+ try { -+ const blob = new Blob([sendData[1].buffer], { type: "application/octet-stream" }); -+ fd.append("model", JSON.stringify(request)); -+ fd.append("data", blob, sendData[0].file.fileName.encryptedString); -+ } catch (e) { -+ if (Utils.isNode && !Utils.isBrowser) { -+ fd.append("model", JSON.stringify(request)); -+ fd.append( -+ "data", -+ Buffer.from(sendData[1].buffer) as any, -+ { -+ filepath: sendData[0].file.fileName.encryptedString, -+ contentType: "application/octet-stream", -+ } as any -+ ); -+ } else { -+ throw e; -+ } - } -+ return await this.apiService.postSendFileLegacy(fd); -+ } - -- async replace(sends: { [id: string]: SendData; }): Promise { -- const userId = await this.userService.getUserId(); -- await this.storageService.save(Keys.sendsPrefix + userId, sends); -- this.decryptedSendCache = null; -+ async upsert(send: SendData | SendData[]): Promise { -+ let sends = await this.stateService.getEncryptedSends(); -+ if (sends == null) { -+ sends = {}; - } - -- async clear(userId: string): Promise { -- await this.storageService.remove(Keys.sendsPrefix + userId); -- this.decryptedSendCache = null; -+ if (send instanceof SendData) { -+ const s = send as SendData; -+ sends[s.id] = s; -+ } else { -+ (send as SendData[]).forEach((s) => { -+ sends[s.id] = s; -+ }); - } - -- async delete(id: string | string[]): Promise { -- const userId = await this.userService.getUserId(); -- const sends = await this.storageService.get<{ [id: string]: SendData; }>( -- Keys.sendsPrefix + userId); -- if (sends == null) { -- return; -- } -- -- if (typeof id === 'string') { -- if (sends[id] == null) { -- return; -- } -- delete sends[id]; -- } else { -- (id as string[]).forEach(i => { -- delete sends[i]; -- }); -- } -+ await this.replace(sends); -+ } - -- await this.storageService.save(Keys.sendsPrefix + userId, sends); -- this.decryptedSendCache = null; -- } -+ async replace(sends: { [id: string]: SendData }): Promise { -+ await this.stateService.setDecryptedSends(null); -+ await this.stateService.setEncryptedSends(sends); -+ } - -- async deleteWithServer(id: string): Promise { -- await this.apiService.deleteSend(id); -- await this.delete(id); -- } -+ async clear(): Promise { -+ await this.stateService.setDecryptedSends(null); -+ await this.stateService.setEncryptedSends(null); -+ } - -- async removePasswordWithServer(id: string): Promise { -- const response = await this.apiService.putSendRemovePassword(id); -- const userId = await this.userService.getUserId(); -- const data = new SendData(response, userId); -- await this.upsert(data); -+ async delete(id: string | string[]): Promise { -+ const sends = await this.stateService.getEncryptedSends(); -+ if (sends == null) { -+ return; - } - -- private parseFile(send: Send, file: File, key: SymmetricCryptoKey): Promise { -- return new Promise((resolve, reject) => { -- const reader = new FileReader(); -- reader.readAsArrayBuffer(file); -- reader.onload = async evt => { -- try { -- const [name, data] = await this.encryptFileData(file.name, evt.target.result as ArrayBuffer, key); -- send.file.fileName = name; -- resolve(data); -- } catch (e) { -- reject(e); -- } -- }; -- reader.onerror = evt => { -- reject('Error reading file.'); -- }; -- }); -+ if (typeof id === "string") { -+ if (sends[id] == null) { -+ return; -+ } -+ delete sends[id]; -+ } else { -+ (id as string[]).forEach((i) => { -+ delete sends[i]; -+ }); - } - -- private async encryptFileData(fileName: string, data: ArrayBuffer, -- key: SymmetricCryptoKey): Promise<[EncString, EncArrayBuffer]> { -- const encFileName = await this.cryptoService.encrypt(fileName, key); -- const encFileData = await this.cryptoService.encryptToBytes(data, key); -- return [encFileName, encFileData]; -- } -+ await this.replace(sends); -+ } -+ -+ async deleteWithServer(id: string): Promise { -+ await this.apiService.deleteSend(id); -+ await this.delete(id); -+ } -+ -+ async removePasswordWithServer(id: string): Promise { -+ const response = await this.apiService.putSendRemovePassword(id); -+ const userId = await this.stateService.getUserId(); -+ const data = new SendData(response, userId); -+ await this.upsert(data); -+ } -+ -+ private parseFile(send: Send, file: File, key: SymmetricCryptoKey): Promise { -+ return new Promise((resolve, reject) => { -+ const reader = new FileReader(); -+ reader.readAsArrayBuffer(file); -+ reader.onload = async (evt) => { -+ try { -+ const [name, data] = await this.encryptFileData( -+ file.name, -+ evt.target.result as ArrayBuffer, -+ key -+ ); -+ send.file.fileName = name; -+ resolve(data); -+ } catch (e) { -+ reject(e); -+ } -+ }; -+ reader.onerror = () => { -+ reject("Error reading file."); -+ }; -+ }); -+ } -+ -+ private async encryptFileData( -+ fileName: string, -+ data: ArrayBuffer, -+ key: SymmetricCryptoKey -+ ): Promise<[EncString, EncArrayBuffer]> { -+ const encFileName = await this.cryptoService.encrypt(fileName, key); -+ const encFileData = await this.cryptoService.encryptToBytes(data, key); -+ return [encFileName, encFileData]; -+ } - } -diff --git a/jslib/common/src/services/settings.service.ts b/jslib/common/src/services/settings.service.ts -index 51d970f3..a8fdf333 100644 ---- a/jslib/common/src/services/settings.service.ts -+++ b/jslib/common/src/services/settings.service.ts -@@ -1,62 +1,55 @@ --import { SettingsService as SettingsServiceAbstraction } from '../abstractions/settings.service'; --import { StorageService } from '../abstractions/storage.service'; --import { UserService } from '../abstractions/user.service'; -+import { SettingsService as SettingsServiceAbstraction } from "../abstractions/settings.service"; -+import { StateService } from "../abstractions/state.service"; - - const Keys = { -- settingsPrefix: 'settings_', -- equivalentDomains: 'equivalentDomains', -+ settingsPrefix: "settings_", -+ equivalentDomains: "equivalentDomains", - }; - - export class SettingsService implements SettingsServiceAbstraction { -- private settingsCache: any; -+ constructor(private stateService: StateService) {} - -- constructor(private userService: UserService, private storageService: StorageService) { -- } -- -- clearCache(): void { -- this.settingsCache = null; -- } -+ async clearCache(): Promise { -+ await this.stateService.setSettings(null); -+ } - -- getEquivalentDomains(): Promise { -- return this.getSettingsKey(Keys.equivalentDomains); -- } -+ getEquivalentDomains(): Promise { -+ return this.getSettingsKey(Keys.equivalentDomains); -+ } - -- async setEquivalentDomains(equivalentDomains: string[][]): Promise { -- await this.setSettingsKey(Keys.equivalentDomains, equivalentDomains); -- } -+ async setEquivalentDomains(equivalentDomains: string[][]): Promise { -+ await this.setSettingsKey(Keys.equivalentDomains, equivalentDomains); -+ } - -- async clear(userId: string): Promise { -- await this.storageService.remove(Keys.settingsPrefix + userId); -- this.clearCache(); -- } -+ async clear(userId?: string): Promise { -+ await this.stateService.setSettings(null, { userId: userId }); -+ } - -- // Helpers -+ // Helpers - -- private async getSettings(): Promise { -- if (this.settingsCache == null) { -- const userId = await this.userService.getUserId(); -- this.settingsCache = this.storageService.get(Keys.settingsPrefix + userId); -- } -- return this.settingsCache; -+ private async getSettings(): Promise { -+ const settings = await this.stateService.getSettings(); -+ if (settings == null) { -+ const userId = await this.stateService.getUserId(); - } -+ return settings; -+ } - -- private async getSettingsKey(key: string): Promise { -- const settings = await this.getSettings(); -- if (settings != null && settings[key]) { -- return settings[key]; -- } -- return null; -+ private async getSettingsKey(key: string): Promise { -+ const settings = await this.getSettings(); -+ if (settings != null && settings[key]) { -+ return settings[key]; - } -+ return null; -+ } - -- private async setSettingsKey(key: string, value: any): Promise { -- const userId = await this.userService.getUserId(); -- let settings = await this.getSettings(); -- if (!settings) { -- settings = {}; -- } -- -- settings[key] = value; -- await this.storageService.save(Keys.settingsPrefix + userId, settings); -- this.settingsCache = settings; -+ private async setSettingsKey(key: string, value: any): Promise { -+ let settings = await this.getSettings(); -+ if (!settings) { -+ settings = {}; - } -+ -+ settings[key] = value; -+ await this.stateService.setSettings(settings); -+ } - } -diff --git a/jslib/common/src/services/state.service.ts b/jslib/common/src/services/state.service.ts -index 03a09e30..8ce399e9 100644 ---- a/jslib/common/src/services/state.service.ts -+++ b/jslib/common/src/services/state.service.ts -@@ -1,27 +1,2455 @@ --import { StateService as StateServiceAbstraction } from '../abstractions/state.service'; -+import { StateService as StateServiceAbstraction } from "../abstractions/state.service"; - --export class StateService implements StateServiceAbstraction { -- private state: any = {}; -+import { Account, AccountData } from "../models/domain/account"; - -- get(key: string): Promise { -- if (this.state.hasOwnProperty(key)) { -- return Promise.resolve(this.state[key]); -- } -- return Promise.resolve(null); -+import { LogService } from "../abstractions/log.service"; -+import { StorageService } from "../abstractions/storage.service"; -+ -+import { HtmlStorageLocation } from "../enums/htmlStorageLocation"; -+import { KdfType } from "../enums/kdfType"; -+import { StorageLocation } from "../enums/storageLocation"; -+import { ThemeType } from "../enums/themeType"; -+import { UriMatchType } from "../enums/uriMatchType"; -+ -+import { CipherView } from "../models/view/cipherView"; -+import { CollectionView } from "../models/view/collectionView"; -+import { FolderView } from "../models/view/folderView"; -+import { SendView } from "../models/view/sendView"; -+ -+import { EncString } from "../models/domain/encString"; -+import { GeneratedPasswordHistory } from "../models/domain/generatedPasswordHistory"; -+import { GlobalState } from "../models/domain/globalState"; -+import { Policy } from "../models/domain/policy"; -+import { State } from "../models/domain/state"; -+import { StorageOptions } from "../models/domain/storageOptions"; -+import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; -+ -+import { CipherData } from "../models/data/cipherData"; -+import { CollectionData } from "../models/data/collectionData"; -+import { EventData } from "../models/data/eventData"; -+import { FolderData } from "../models/data/folderData"; -+import { OrganizationData } from "../models/data/organizationData"; -+import { PolicyData } from "../models/data/policyData"; -+import { ProviderData } from "../models/data/providerData"; -+import { SendData } from "../models/data/sendData"; -+ -+import { BehaviorSubject } from "rxjs"; -+ -+import { StateMigrationService } from "../abstractions/stateMigration.service"; -+import { EnvironmentUrls } from "../models/domain/environmentUrls"; -+import { WindowState } from "../models/domain/windowState"; -+ -+import { StateFactory } from "../factories/stateFactory"; -+ -+const keys = { -+ global: "global", -+ authenticatedAccounts: "authenticatedAccounts", -+ activeUserId: "activeUserId", -+ tempAccountSettings: "tempAccountSettings", // used to hold account specific settings (i.e clear clipboard) between initial migration and first account authentication -+}; -+ -+const partialKeys = { -+ autoKey: "_masterkey_auto", -+ biometricKey: "_masterkey_biometric", -+ masterKey: "_masterkey", -+}; -+ -+export class StateService< -+ TAccount extends Account = Account, -+ TGlobalState extends GlobalState = GlobalState -+> implements StateServiceAbstraction -+{ -+ accounts = new BehaviorSubject<{ [userId: string]: TAccount }>({}); -+ activeAccount = new BehaviorSubject(null); -+ -+ protected state: State = new State( -+ this.createGlobals() -+ ); -+ -+ private hasBeenInited: boolean = false; -+ -+ constructor( -+ protected storageService: StorageService, -+ protected secureStorageService: StorageService, -+ protected logService: LogService, -+ protected stateMigrationService: StateMigrationService, -+ protected stateFactory: StateFactory -+ ) {} -+ -+ async init(): Promise { -+ if (this.hasBeenInited) { -+ return; -+ } -+ -+ if (await this.stateMigrationService.needsMigration()) { -+ await this.stateMigrationService.migrate(); -+ } -+ -+ await this.initAccountState(); -+ this.hasBeenInited = true; -+ } -+ -+ async initAccountState() { -+ this.state.authenticatedAccounts = -+ (await this.storageService.get(keys.authenticatedAccounts)) ?? []; -+ for (const i in this.state.authenticatedAccounts) { -+ if (i != null) { -+ await this.syncAccountFromDisk(this.state.authenticatedAccounts[i]); -+ } -+ } -+ const storedActiveUser = await this.storageService.get(keys.activeUserId); -+ if (storedActiveUser != null) { -+ this.state.activeUserId = storedActiveUser; -+ } -+ await this.pushAccounts(); -+ this.activeAccount.next(this.state.activeUserId); -+ } -+ -+ async syncAccountFromDisk(userId: string) { -+ if (userId == null) { -+ return; -+ } -+ this.state.accounts[userId] = this.createAccount(); -+ const diskAccount = await this.getAccountFromDisk({ userId: userId }); -+ this.state.accounts[userId].profile = diskAccount.profile; -+ } -+ -+ async addAccount(account: TAccount) { -+ account = await this.setAccountEnvironmentUrls(account); -+ this.state.authenticatedAccounts.push(account.profile.userId); -+ this.storageService.save(keys.authenticatedAccounts, this.state.authenticatedAccounts); -+ this.state.accounts[account.profile.userId] = account; -+ await this.scaffoldNewAccountStorage(account); -+ await this.setActiveUser(account.profile.userId); -+ this.activeAccount.next(account.profile.userId); -+ } -+ -+ async setActiveUser(userId: string): Promise { -+ this.clearDecryptedDataForActiveUser(); -+ this.state.activeUserId = userId; -+ await this.storageService.save(keys.activeUserId, userId); -+ this.activeAccount.next(this.state.activeUserId); -+ await this.pushAccounts(); -+ } -+ -+ async clean(options?: StorageOptions): Promise { -+ options = this.reconcileOptions(options, this.defaultInMemoryOptions); -+ await this.deAuthenticateAccount(options.userId); -+ if (options.userId === this.state.activeUserId) { -+ await this.dynamicallySetActiveUser(); -+ } -+ -+ await this.removeAccountFromDisk(options?.userId); -+ this.removeAccountFromMemory(options?.userId); -+ await this.pushAccounts(); -+ } -+ -+ async getAccessToken(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.tokens?.accessToken; -+ } -+ -+ async setAccessToken(value: string, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.tokens.accessToken = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getAddEditCipherInfo(options?: StorageOptions): Promise { -+ return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.data?.addEditCipherInfo; -+ } -+ -+ async setAddEditCipherInfo(value: any, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.data.addEditCipherInfo = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getAlwaysShowDock(options?: StorageOptions): Promise { -+ return ( -+ (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.alwaysShowDock ?? false -+ ); -+ } -+ -+ async setAlwaysShowDock(value: boolean, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ globals.alwaysShowDock = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getApiKeyClientId(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.profile?.apiKeyClientId; -+ } -+ -+ async setApiKeyClientId(value: string, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.profile.apiKeyClientId = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getApiKeyClientSecret(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.keys?.apiKeyClientSecret; -+ } -+ -+ async setApiKeyClientSecret(value: string, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.keys.apiKeyClientSecret = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getAutoConfirmFingerPrints(options?: StorageOptions): Promise { -+ return ( -+ (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.settings?.autoConfirmFingerPrints ?? true -+ ); -+ } -+ -+ async setAutoConfirmFingerprints(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.autoConfirmFingerPrints = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getAutoFillOnPageLoadDefault(options?: StorageOptions): Promise { -+ return ( -+ (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.settings?.autoFillOnPageLoadDefault ?? false -+ ); -+ } -+ -+ async setAutoFillOnPageLoadDefault(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.autoFillOnPageLoadDefault = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getBiometricAwaitingAcceptance(options?: StorageOptions): Promise { -+ return ( -+ (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.biometricAwaitingAcceptance ?? false -+ ); -+ } -+ -+ async setBiometricAwaitingAcceptance(value: boolean, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ globals.biometricAwaitingAcceptance = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getBiometricFingerprintValidated(options?: StorageOptions): Promise { -+ return ( -+ (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.biometricFingerprintValidated ?? false -+ ); -+ } -+ -+ async setBiometricFingerprintValidated(value: boolean, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ globals.biometricFingerprintValidated = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getBiometricLocked(options?: StorageOptions): Promise { -+ return ( -+ (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.settings -+ ?.biometricLocked ?? false -+ ); -+ } -+ -+ async setBiometricLocked(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.settings.biometricLocked = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getBiometricText(options?: StorageOptions): Promise { -+ return ( -+ await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.biometricText; -+ } -+ -+ async setBiometricText(value: string, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ globals.biometricText = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getBiometricUnlock(options?: StorageOptions): Promise { -+ return ( -+ (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.settings?.biometricUnlock ?? false -+ ); -+ } -+ -+ async setBiometricUnlock(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.biometricUnlock = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getCanAccessPremium(options?: StorageOptions): Promise { -+ if (!(await this.getIsAuthenticated(options))) { -+ return false; -+ } -+ -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ if (account.profile.hasPremiumPersonally) { -+ return true; - } - -- save(key: string, obj: any): Promise { -- this.state[key] = obj; -- return Promise.resolve(); -+ const organizations = await this.getOrganizations(options); -+ if (organizations == null) { -+ return false; - } - -- remove(key: string): Promise { -- delete this.state[key]; -- return Promise.resolve(); -+ for (const id of Object.keys(organizations)) { -+ const o = organizations[id]; -+ if (o.enabled && o.usersGetPremium && !o.isProviderUser) { -+ return true; -+ } - } - -- purge(): Promise { -- this.state = {}; -- return Promise.resolve(); -+ return false; -+ } -+ -+ async getClearClipboard(options?: StorageOptions): Promise { -+ return ( -+ ( -+ await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ) -+ )?.settings?.clearClipboard ?? null -+ ); -+ } -+ -+ async setClearClipboard(value: number, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ account.settings.clearClipboard = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ } -+ -+ async getCollapsedGroupings(options?: StorageOptions): Promise> { -+ return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.data?.collapsedGroupings; -+ } -+ -+ async setCollapsedGroupings(value: Set, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.data.collapsedGroupings = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getConvertAccountToKeyConnector(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.profile?.convertAccountToKeyConnector; -+ } -+ -+ async setConvertAccountToKeyConnector(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.profile.convertAccountToKeyConnector = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getCryptoMasterKey(options?: StorageOptions): Promise { -+ return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.keys?.cryptoMasterKey; -+ } -+ -+ async setCryptoMasterKey(value: SymmetricCryptoKey, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.keys.cryptoMasterKey = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getCryptoMasterKeyAuto(options?: StorageOptions): Promise { -+ options = this.reconcileOptions( -+ this.reconcileOptions(options, { keySuffix: "auto" }), -+ await this.defaultSecureStorageOptions() -+ ); -+ if (options?.userId == null) { -+ return null; -+ } -+ return await this.secureStorageService.get(`${options.userId}${partialKeys.autoKey}`, options); -+ } -+ -+ async setCryptoMasterKeyAuto(value: string, options?: StorageOptions): Promise { -+ options = this.reconcileOptions( -+ this.reconcileOptions(options, { keySuffix: "auto" }), -+ await this.defaultSecureStorageOptions() -+ ); -+ if (options?.userId == null) { -+ return; -+ } -+ await this.secureStorageService.save(`${options.userId}${partialKeys.autoKey}`, value, options); -+ } -+ -+ async getCryptoMasterKeyB64(options?: StorageOptions): Promise { -+ options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); -+ if (options?.userId == null) { -+ return null; -+ } -+ return await this.secureStorageService.get( -+ `${options?.userId}${partialKeys.masterKey}`, -+ options -+ ); -+ } -+ -+ async setCryptoMasterKeyB64(value: string, options?: StorageOptions): Promise { -+ options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); -+ if (options?.userId == null) { -+ return; -+ } -+ await this.secureStorageService.save( -+ `${options.userId}${partialKeys.masterKey}`, -+ value, -+ options -+ ); -+ } -+ -+ async getCryptoMasterKeyBiometric(options?: StorageOptions): Promise { -+ options = this.reconcileOptions( -+ this.reconcileOptions(options, { keySuffix: "biometric" }), -+ await this.defaultSecureStorageOptions() -+ ); -+ if (options?.userId == null) { -+ return null; -+ } -+ return await this.secureStorageService.get( -+ `${options.userId}${partialKeys.biometricKey}`, -+ options -+ ); -+ } -+ -+ async hasCryptoMasterKeyBiometric(options?: StorageOptions): Promise { -+ options = this.reconcileOptions( -+ this.reconcileOptions(options, { keySuffix: "biometric" }), -+ await this.defaultSecureStorageOptions() -+ ); -+ if (options?.userId == null) { -+ return false; -+ } -+ return await this.secureStorageService.has( -+ `${options.userId}${partialKeys.biometricKey}`, -+ options -+ ); -+ } -+ -+ async setCryptoMasterKeyBiometric(value: string, options?: StorageOptions): Promise { -+ options = this.reconcileOptions( -+ this.reconcileOptions(options, { keySuffix: "biometric" }), -+ await this.defaultSecureStorageOptions() -+ ); -+ if (options?.userId == null) { -+ return; -+ } -+ await this.secureStorageService.save( -+ `${options.userId}${partialKeys.biometricKey}`, -+ value, -+ options -+ ); -+ } -+ -+ async getDecodedToken(options?: StorageOptions): Promise { -+ return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.tokens?.decodedToken; -+ } -+ -+ async setDecodedToken(value: any, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.tokens.decodedToken = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getDecryptedCiphers(options?: StorageOptions): Promise { -+ return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.data?.ciphers?.decrypted; -+ } -+ -+ async setDecryptedCiphers(value: CipherView[], options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.data.ciphers.decrypted = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getDecryptedCollections(options?: StorageOptions): Promise { -+ return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.data?.collections?.decrypted; -+ } -+ -+ async setDecryptedCollections(value: CollectionView[], options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.data.collections.decrypted = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getDecryptedCryptoSymmetricKey(options?: StorageOptions): Promise { -+ return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.keys?.cryptoSymmetricKey?.decrypted; -+ } -+ -+ async setDecryptedCryptoSymmetricKey( -+ value: SymmetricCryptoKey, -+ options?: StorageOptions -+ ): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.keys.cryptoSymmetricKey.decrypted = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getDecryptedFolders(options?: StorageOptions): Promise { -+ return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.data?.folders?.decrypted; -+ } -+ -+ async setDecryptedFolders(value: FolderView[], options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.data.folders.decrypted = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getDecryptedOrganizationKeys( -+ options?: StorageOptions -+ ): Promise> { -+ return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.keys?.organizationKeys?.decrypted; -+ } -+ -+ async setDecryptedOrganizationKeys( -+ value: Map, -+ options?: StorageOptions -+ ): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.keys.organizationKeys.decrypted = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getDecryptedPasswordGenerationHistory( -+ options?: StorageOptions -+ ): Promise { -+ return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.data?.passwordGenerationHistory?.decrypted; -+ } -+ -+ async setDecryptedPasswordGenerationHistory( -+ value: GeneratedPasswordHistory[], -+ options?: StorageOptions -+ ): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.data.passwordGenerationHistory.decrypted = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getDecryptedPinProtected(options?: StorageOptions): Promise { -+ return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.settings?.pinProtected?.decrypted; -+ } -+ -+ async setDecryptedPinProtected(value: EncString, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.settings.pinProtected.decrypted = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getDecryptedPolicies(options?: StorageOptions): Promise { -+ return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.data?.policies?.decrypted; -+ } -+ -+ async setDecryptedPolicies(value: Policy[], options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.data.policies.decrypted = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getDecryptedPrivateKey(options?: StorageOptions): Promise { -+ return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.keys?.privateKey?.decrypted; -+ } -+ -+ async setDecryptedPrivateKey(value: ArrayBuffer, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.keys.privateKey.decrypted = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getDecryptedProviderKeys( -+ options?: StorageOptions -+ ): Promise> { -+ return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.keys?.providerKeys?.decrypted; -+ } -+ -+ async setDecryptedProviderKeys( -+ value: Map, -+ options?: StorageOptions -+ ): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.keys.providerKeys.decrypted = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getDecryptedSends(options?: StorageOptions): Promise { -+ return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.data?.sends?.decrypted; -+ } -+ -+ async setDecryptedSends(value: SendView[], options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.data.sends.decrypted = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getDefaultUriMatch(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.settings?.defaultUriMatch; -+ } -+ -+ async setDefaultUriMatch(value: UriMatchType, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.defaultUriMatch = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getDisableAddLoginNotification(options?: StorageOptions): Promise { -+ return ( -+ (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.settings?.disableAddLoginNotification ?? false -+ ); -+ } -+ -+ async setDisableAddLoginNotification(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.disableAddLoginNotification = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getDisableAutoBiometricsPrompt(options?: StorageOptions): Promise { -+ return ( -+ (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.settings?.disableAutoBiometricsPrompt ?? false -+ ); -+ } -+ -+ async setDisableAutoBiometricsPrompt(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.disableAutoBiometricsPrompt = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getDisableAutoTotpCopy(options?: StorageOptions): Promise { -+ return ( -+ (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.settings?.disableAutoTotpCopy ?? false -+ ); -+ } -+ -+ async setDisableAutoTotpCopy(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.disableAutoTotpCopy = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getDisableBadgeCounter(options?: StorageOptions): Promise { -+ return ( -+ (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.settings?.disableBadgeCounter ?? false -+ ); -+ } -+ -+ async setDisableBadgeCounter(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.disableBadgeCounter = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getDisableChangedPasswordNotification(options?: StorageOptions): Promise { -+ return ( -+ (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.settings?.disableChangedPasswordNotification ?? false -+ ); -+ } -+ -+ async setDisableChangedPasswordNotification( -+ value: boolean, -+ options?: StorageOptions -+ ): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.disableChangedPasswordNotification = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getDisableContextMenuItem(options?: StorageOptions): Promise { -+ return ( -+ (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.settings?.disableContextMenuItem ?? false -+ ); -+ } -+ -+ async setDisableContextMenuItem(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.disableContextMenuItem = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getDisableFavicon(options?: StorageOptions): Promise { -+ return ( -+ ( -+ await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ) -+ )?.disableFavicon ?? false -+ ); -+ } -+ -+ async setDisableFavicon(value: boolean, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ globals.disableFavicon = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ } -+ -+ async getDisableGa(options?: StorageOptions): Promise { -+ return ( -+ (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.settings?.disableGa ?? false -+ ); -+ } -+ -+ async setDisableGa(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.disableGa = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getDontShowCardsCurrentTab(options?: StorageOptions): Promise { -+ return ( -+ (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.settings?.dontShowCardsCurrentTab ?? false -+ ); -+ } -+ -+ async setDontShowCardsCurrentTab(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.dontShowCardsCurrentTab = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getDontShowIdentitiesCurrentTab(options?: StorageOptions): Promise { -+ return ( -+ (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.settings?.dontShowIdentitiesCurrentTab ?? false -+ ); -+ } -+ -+ async setDontShowIdentitiesCurrentTab(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.dontShowIdentitiesCurrentTab = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEmail(options?: StorageOptions): Promise { -+ return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.profile?.email; -+ } -+ -+ async setEmail(value: string, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.profile.email = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getEmailVerified(options?: StorageOptions): Promise { -+ return ( -+ (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.profile.emailVerified ?? false -+ ); -+ } -+ -+ async setEmailVerified(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.profile.emailVerified = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEnableAlwaysOnTop(options?: StorageOptions): Promise { -+ const accountPreference = ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.settings?.enableAlwaysOnTop; -+ const globalPreference = ( -+ await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.enableAlwaysOnTop; -+ return accountPreference ?? globalPreference ?? false; -+ } -+ -+ async setEnableAlwaysOnTop(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.enableAlwaysOnTop = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ globals.enableAlwaysOnTop = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEnableAutoFillOnPageLoad(options?: StorageOptions): Promise { -+ return ( -+ (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.settings?.enableAutoFillOnPageLoad ?? false -+ ); -+ } -+ -+ async setEnableAutoFillOnPageLoad(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.enableAutoFillOnPageLoad = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEnableBiometric(options?: StorageOptions): Promise { -+ return ( -+ (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.enableBiometrics ?? false -+ ); -+ } -+ -+ async setEnableBiometric(value: boolean, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ globals.enableBiometrics = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEnableBrowserIntegration(options?: StorageOptions): Promise { -+ return ( -+ (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.enableBrowserIntegration ?? false -+ ); -+ } -+ -+ async setEnableBrowserIntegration(value: boolean, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ globals.enableBrowserIntegration = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEnableBrowserIntegrationFingerprint(options?: StorageOptions): Promise { -+ return ( -+ (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.enableBrowserIntegrationFingerprint ?? false -+ ); -+ } -+ -+ async setEnableBrowserIntegrationFingerprint( -+ value: boolean, -+ options?: StorageOptions -+ ): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ globals.enableBrowserIntegrationFingerprint = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEnableCloseToTray(options?: StorageOptions): Promise { -+ return ( -+ (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.enableCloseToTray ?? false -+ ); -+ } -+ -+ async setEnableCloseToTray(value: boolean, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ globals.enableCloseToTray = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEnableFullWidth(options?: StorageOptions): Promise { -+ return ( -+ ( -+ await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ) -+ )?.settings?.enableFullWidth ?? false -+ ); -+ } -+ -+ async setEnableFullWidth(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ account.settings.enableFullWidth = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ } -+ -+ async getEnableGravitars(options?: StorageOptions): Promise { -+ return ( -+ ( -+ await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ) -+ )?.settings?.enableGravitars ?? false -+ ); -+ } -+ -+ async setEnableGravitars(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ account.settings.enableGravitars = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ } -+ -+ async getEnableMinimizeToTray(options?: StorageOptions): Promise { -+ return ( -+ (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.enableMinimizeToTray ?? false -+ ); -+ } -+ -+ async setEnableMinimizeToTray(value: boolean, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ globals.enableMinimizeToTray = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEnableStartToTray(options?: StorageOptions): Promise { -+ return ( -+ (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.enableStartToTray ?? false -+ ); -+ } -+ -+ async setEnableStartToTray(value: boolean, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ globals.enableStartToTray = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEnableTray(options?: StorageOptions): Promise { -+ return ( -+ (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.enableTray ?? false -+ ); -+ } -+ -+ async setEnableTray(value: boolean, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ globals.enableTray = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEncryptedCiphers(options?: StorageOptions): Promise<{ [id: string]: CipherData }> { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())) -+ )?.data?.ciphers?.encrypted; -+ } -+ -+ async setEncryptedCiphers( -+ value: { [id: string]: CipherData }, -+ options?: StorageOptions -+ ): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) -+ ); -+ account.data.ciphers.encrypted = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) -+ ); -+ } -+ -+ async getEncryptedCollections( -+ options?: StorageOptions -+ ): Promise<{ [id: string]: CollectionData }> { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())) -+ )?.data?.collections?.encrypted; -+ } -+ -+ async setEncryptedCollections( -+ value: { [id: string]: CollectionData }, -+ options?: StorageOptions -+ ): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) -+ ); -+ account.data.collections.encrypted = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) -+ ); -+ } -+ -+ async getEncryptedCryptoSymmetricKey(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.keys.cryptoSymmetricKey.encrypted; -+ } -+ -+ async setEncryptedCryptoSymmetricKey(value: string, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.keys.cryptoSymmetricKey.encrypted = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEncryptedFolders(options?: StorageOptions): Promise<{ [id: string]: FolderData }> { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())) -+ )?.data?.folders?.encrypted; -+ } -+ -+ async setEncryptedFolders( -+ value: { [id: string]: FolderData }, -+ options?: StorageOptions -+ ): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) -+ ); -+ account.data.folders.encrypted = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) -+ ); -+ } -+ -+ async getEncryptedOrganizationKeys(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.keys?.organizationKeys.encrypted; -+ } -+ -+ async setEncryptedOrganizationKeys( -+ value: Map, -+ options?: StorageOptions -+ ): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.keys.organizationKeys.encrypted = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEncryptedPasswordGenerationHistory( -+ options?: StorageOptions -+ ): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.data?.passwordGenerationHistory?.encrypted; -+ } -+ -+ async setEncryptedPasswordGenerationHistory( -+ value: GeneratedPasswordHistory[], -+ options?: StorageOptions -+ ): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.data.passwordGenerationHistory.encrypted = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEncryptedPinProtected(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.settings?.pinProtected?.encrypted; -+ } -+ -+ async setEncryptedPinProtected(value: string, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.pinProtected.encrypted = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEncryptedPolicies(options?: StorageOptions): Promise<{ [id: string]: PolicyData }> { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.data?.policies?.encrypted; -+ } -+ -+ async setEncryptedPolicies( -+ value: { [id: string]: PolicyData }, -+ options?: StorageOptions -+ ): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.data.policies.encrypted = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEncryptedPrivateKey(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.keys?.privateKey?.encrypted; -+ } -+ -+ async setEncryptedPrivateKey(value: string, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.keys.privateKey.encrypted = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEncryptedProviderKeys(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.keys?.providerKeys?.encrypted; -+ } -+ -+ async setEncryptedProviderKeys(value: any, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.keys.providerKeys.encrypted = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEncryptedSends(options?: StorageOptions): Promise<{ [id: string]: SendData }> { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())) -+ )?.data?.sends.encrypted; -+ } -+ -+ async setEncryptedSends( -+ value: { [id: string]: SendData }, -+ options?: StorageOptions -+ ): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) -+ ); -+ account.data.sends.encrypted = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) -+ ); -+ } -+ -+ async getEntityId(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) -+ )?.profile?.entityId; -+ } -+ -+ async setEntityId(value: string, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ account.profile.entityId = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ } -+ -+ async getEntityType(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) -+ )?.profile?.entityType; -+ } -+ -+ async setEntityType(value: string, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ account.profile.entityType = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ } -+ -+ async getEnvironmentUrls(options?: StorageOptions): Promise { -+ if (this.state.activeUserId == null) { -+ return await this.getGlobalEnvironmentUrls(options); -+ } -+ options = this.reconcileOptions(options, await this.defaultOnDiskOptions()); -+ return (await this.getAccount(options))?.settings?.environmentUrls ?? new EnvironmentUrls(); -+ } -+ -+ async setEnvironmentUrls(value: EnvironmentUrls, options?: StorageOptions): Promise { -+ // Global values are set on each change and the current global settings are passed to any newly authed accounts. -+ // This is to allow setting environement values before an account is active, while still allowing individual accounts to have their own environments. -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ globals.environmentUrls = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEquivalentDomains(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.settings?.equivalentDomains; -+ } -+ -+ async setEquivalentDomains(value: string, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.equivalentDomains = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEventCollection(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.data?.eventCollection; -+ } -+ -+ async setEventCollection(value: EventData[], options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.data.eventCollection = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getEverBeenUnlocked(options?: StorageOptions): Promise { -+ return ( -+ (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.profile?.everBeenUnlocked ?? false -+ ); -+ } -+ -+ async setEverBeenUnlocked(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.profile.everBeenUnlocked = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getForcePasswordReset(options?: StorageOptions): Promise { -+ return ( -+ (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions)))?.profile -+ ?.forcePasswordReset ?? false -+ ); -+ } -+ -+ async setForcePasswordReset(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.profile.forcePasswordReset = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getInstalledVersion(options?: StorageOptions): Promise { -+ return ( -+ await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.installedVersion; -+ } -+ -+ async setInstalledVersion(value: string, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ globals.installedVersion = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getIsAuthenticated(options?: StorageOptions): Promise { -+ return (await this.getAccessToken(options)) != null && (await this.getUserId(options)) != null; -+ } -+ -+ async getKdfIterations(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.profile?.kdfIterations; -+ } -+ -+ async setKdfIterations(value: number, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.profile.kdfIterations = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getKdfType(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.profile?.kdfType; -+ } -+ -+ async setKdfType(value: KdfType, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.profile.kdfType = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getKeyHash(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.profile?.keyHash; -+ } -+ -+ async setKeyHash(value: string, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.profile.keyHash = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getLastActive(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.profile?.lastActive; -+ } -+ -+ async setLastActive(value: number, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ if (account != null) { -+ account.profile.lastActive = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ } -+ -+ async getLastSync(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())) -+ )?.profile?.lastSync; -+ } -+ -+ async setLastSync(value: string, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) -+ ); -+ account.profile.lastSync = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) -+ ); -+ } -+ -+ async getLegacyEtmKey(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.keys?.legacyEtmKey; -+ } -+ -+ async setLegacyEtmKey(value: SymmetricCryptoKey, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.keys.legacyEtmKey = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getLocalData(options?: StorageOptions): Promise { -+ return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.data?.localData; -+ } -+ -+ async setLocalData(value: string, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.data.localData = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getLocale(options?: StorageOptions): Promise { -+ return ( -+ await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) -+ )?.locale; -+ } -+ -+ async setLocale(value: string, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ globals.locale = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ } -+ -+ async getLoginRedirect(options?: StorageOptions): Promise { -+ return (await this.getGlobals(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.loginRedirect; -+ } -+ -+ async setLoginRedirect(value: any, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ globals.loginRedirect = value; -+ await this.saveGlobals(globals, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getMainWindowSize(options?: StorageOptions): Promise { -+ return (await this.getGlobals(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.mainWindowSize; -+ } -+ -+ async setMainWindowSize(value: number, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ globals.mainWindowSize = value; -+ await this.saveGlobals(globals, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getMinimizeOnCopyToClipboard(options?: StorageOptions): Promise { -+ return ( -+ (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.settings?.minimizeOnCopyToClipboard ?? false -+ ); -+ } -+ -+ async setMinimizeOnCopyToClipboard(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.minimizeOnCopyToClipboard = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getNeverDomains(options?: StorageOptions): Promise<{ [id: string]: any }> { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.settings?.neverDomains; -+ } -+ -+ async setNeverDomains(value: { [id: string]: any }, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.neverDomains = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getNoAutoPromptBiometrics(options?: StorageOptions): Promise { -+ return ( -+ (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.noAutoPromptBiometrics ?? false -+ ); -+ } -+ -+ async setNoAutoPromptBiometrics(value: boolean, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ globals.noAutoPromptBiometrics = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getNoAutoPromptBiometricsText(options?: StorageOptions): Promise { -+ return ( -+ await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.noAutoPromptBiometricsText; -+ } -+ -+ async setNoAutoPromptBiometricsText(value: string, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ globals.noAutoPromptBiometricsText = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getOpenAtLogin(options?: StorageOptions): Promise { -+ return ( -+ (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) -+ ?.openAtLogin ?? false -+ ); -+ } -+ -+ async setOpenAtLogin(value: boolean, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ globals.openAtLogin = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getOrganizationInvitation(options?: StorageOptions): Promise { -+ return (await this.getGlobals(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.organizationInvitation; -+ } -+ -+ async setOrganizationInvitation(value: any, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ globals.organizationInvitation = value; -+ await this.saveGlobals(globals, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getOrganizations(options?: StorageOptions): Promise<{ [id: string]: OrganizationData }> { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.data?.organizations; -+ } -+ -+ async setOrganizations( -+ value: { [id: string]: OrganizationData }, -+ options?: StorageOptions -+ ): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.data.organizations = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getPasswordGenerationOptions(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.settings?.passwordGenerationOptions; -+ } -+ -+ async setPasswordGenerationOptions(value: any, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.passwordGenerationOptions = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getProtectedPin(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.settings?.protectedPin; -+ } -+ -+ async setProtectedPin(value: string, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.settings.protectedPin = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getProviders(options?: StorageOptions): Promise<{ [id: string]: ProviderData }> { -+ return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.data?.providers; -+ } -+ -+ async setProviders( -+ value: { [id: string]: ProviderData }, -+ options?: StorageOptions -+ ): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.data.providers = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getPublicKey(options?: StorageOptions): Promise { -+ return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.keys?.publicKey; -+ } -+ -+ async setPublicKey(value: ArrayBuffer, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.keys.publicKey = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getRefreshToken(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.tokens?.refreshToken; -+ } -+ -+ async setRefreshToken(value: string, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.tokens.refreshToken = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getRememberedEmail(options?: StorageOptions): Promise { -+ return ( -+ await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) -+ )?.rememberedEmail; -+ } -+ -+ async setRememberedEmail(value: string, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ globals.rememberedEmail = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ } -+ -+ async getSecurityStamp(options?: StorageOptions): Promise { -+ return (await this.getAccount(this.reconcileOptions(options, this.defaultInMemoryOptions))) -+ ?.tokens?.securityStamp; -+ } -+ -+ async setSecurityStamp(value: string, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, this.defaultInMemoryOptions) -+ ); -+ account.tokens.securityStamp = value; -+ await this.saveAccount(account, this.reconcileOptions(options, this.defaultInMemoryOptions)); -+ } -+ -+ async getSettings(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())) -+ )?.settings?.settings; -+ } -+ -+ async setSettings(value: string, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) -+ ); -+ account.settings.settings = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) -+ ); -+ } -+ -+ async getSsoCodeVerifier(options?: StorageOptions): Promise { -+ return ( -+ await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.ssoCodeVerifier; -+ } -+ -+ async setSsoCodeVerifier(value: string, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ globals.ssoCodeVerifier = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getSsoOrgIdentifier(options?: StorageOptions): Promise { -+ return ( -+ await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) -+ )?.ssoOrganizationIdentifier; -+ } -+ -+ async setSsoOrganizationIdentifier(value: string, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ globals.ssoOrganizationIdentifier = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ } -+ -+ async getSsoState(options?: StorageOptions): Promise { -+ return ( -+ await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.ssoState; -+ } -+ -+ async setSsoState(value: string, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ globals.ssoState = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getTheme(options?: StorageOptions): Promise { -+ return ( -+ await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) -+ )?.theme; -+ } -+ -+ async setTheme(value: ThemeType, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ globals.theme = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ } -+ -+ async getTwoFactorToken(options?: StorageOptions): Promise { -+ return ( -+ await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) -+ )?.twoFactorToken; -+ } -+ -+ async setTwoFactorToken(value: string, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ globals.twoFactorToken = value; -+ await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ } -+ -+ async getUserId(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.profile?.userId; -+ } -+ -+ async getUsesKeyConnector(options?: StorageOptions): Promise { -+ return ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) -+ )?.profile?.usesKeyConnector; -+ } -+ -+ async setUsesKeyConnector(value: boolean, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ account.profile.usesKeyConnector = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ async getVaultTimeout(options?: StorageOptions): Promise { -+ const accountVaultTimeout = ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) -+ )?.settings?.vaultTimeout; -+ return accountVaultTimeout; -+ } -+ -+ async setVaultTimeout(value: number, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ account.settings.vaultTimeout = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ } -+ -+ async getVaultTimeoutAction(options?: StorageOptions): Promise { -+ const accountVaultTimeoutAction = ( -+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) -+ )?.settings?.vaultTimeoutAction; -+ const globalVaultTimeoutAction = ( -+ await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) -+ )?.vaultTimeoutAction; -+ return accountVaultTimeoutAction ?? globalVaultTimeoutAction; -+ } -+ -+ async setVaultTimeoutAction(value: string, options?: StorageOptions): Promise { -+ const account = await this.getAccount( -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ account.settings.vaultTimeoutAction = value; -+ await this.saveAccount( -+ account, -+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) -+ ); -+ } -+ -+ async getStateVersion(): Promise { -+ return (await this.getGlobals(await this.defaultOnDiskLocalOptions())).stateVersion ?? 1; -+ } -+ -+ async setStateVersion(value: number): Promise { -+ const globals = await this.getGlobals(await this.defaultOnDiskOptions()); -+ globals.stateVersion = value; -+ await this.saveGlobals(globals, await this.defaultOnDiskOptions()); -+ } -+ -+ async getWindow(): Promise { -+ const globals = await this.getGlobals(await this.defaultOnDiskOptions()); -+ return globals?.window != null && Object.keys(globals.window).length > 0 -+ ? globals.window -+ : new WindowState(); -+ } -+ -+ async setWindow(value: WindowState, options?: StorageOptions): Promise { -+ const globals = await this.getGlobals( -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ globals.window = value; -+ return await this.saveGlobals( -+ globals, -+ this.reconcileOptions(options, await this.defaultOnDiskOptions()) -+ ); -+ } -+ -+ protected async getGlobals(options: StorageOptions): Promise { -+ let globals: TGlobalState; -+ if (this.useMemory(options.storageLocation)) { -+ globals = this.getGlobalsFromMemory(); -+ } -+ -+ if (this.useDisk && globals == null) { -+ globals = await this.getGlobalsFromDisk(options); -+ } -+ -+ return globals ?? this.createGlobals(); -+ } -+ -+ protected async saveGlobals(globals: TGlobalState, options: StorageOptions) { -+ return this.useMemory(options.storageLocation) -+ ? this.saveGlobalsToMemory(globals) -+ : await this.saveGlobalsToDisk(globals, options); -+ } -+ -+ protected getGlobalsFromMemory(): TGlobalState { -+ return this.state.globals; -+ } -+ -+ protected async getGlobalsFromDisk(options: StorageOptions): Promise { -+ return await this.storageService.get(keys.global, options); -+ } -+ -+ protected saveGlobalsToMemory(globals: TGlobalState): void { -+ this.state.globals = globals; -+ } -+ -+ protected async saveGlobalsToDisk(globals: TGlobalState, options: StorageOptions): Promise { -+ if (options.useSecureStorage) { -+ await this.secureStorageService.save(keys.global, globals, options); -+ } else { -+ await this.storageService.save(keys.global, globals, options); -+ } -+ } -+ -+ protected async getAccount(options: StorageOptions): Promise { -+ try { -+ let account: TAccount; -+ if (this.useMemory(options.storageLocation)) { -+ account = this.getAccountFromMemory(options); -+ } -+ -+ if (this.useDisk(options.storageLocation) && account == null) { -+ account = await this.getAccountFromDisk(options); -+ } -+ -+ return account; -+ } catch (e) { -+ this.logService.error(e); -+ } -+ } -+ -+ protected getAccountFromMemory(options: StorageOptions): TAccount { -+ if (this.state.accounts == null) { -+ return null; -+ } -+ return this.state.accounts[this.getUserIdFromMemory(options)]; -+ } -+ -+ protected getUserIdFromMemory(options: StorageOptions): string { -+ return options?.userId != null -+ ? this.state.accounts[options.userId]?.profile?.userId -+ : this.state.activeUserId; -+ } -+ -+ protected async getAccountFromDisk(options: StorageOptions): Promise { -+ if (options?.userId == null && this.state.activeUserId == null) { -+ return null; -+ } -+ -+ const account = options?.useSecureStorage -+ ? (await this.secureStorageService.get(options.userId, options)) ?? -+ (await this.storageService.get( -+ options.userId, -+ this.reconcileOptions(options, { htmlStorageLocation: HtmlStorageLocation.Local }) -+ )) -+ : await this.storageService.get(options.userId, options); -+ -+ return account; -+ } -+ -+ protected useMemory(storageLocation: StorageLocation) { -+ return storageLocation === StorageLocation.Memory || storageLocation === StorageLocation.Both; -+ } -+ -+ protected useDisk(storageLocation: StorageLocation) { -+ return storageLocation === StorageLocation.Disk || storageLocation === StorageLocation.Both; -+ } -+ -+ protected async saveAccount( -+ account: TAccount, -+ options: StorageOptions = { -+ storageLocation: StorageLocation.Both, -+ useSecureStorage: false, -+ } -+ ) { -+ return this.useMemory(options.storageLocation) -+ ? await this.saveAccountToMemory(account) -+ : await this.saveAccountToDisk(account, options); -+ } -+ -+ protected async saveAccountToDisk(account: TAccount, options: StorageOptions): Promise { -+ const storageLocation = options.useSecureStorage -+ ? this.secureStorageService -+ : this.storageService; -+ -+ await storageLocation.save(`${options.userId}`, account, options); -+ } -+ -+ protected async saveAccountToMemory(account: TAccount): Promise { -+ if (this.getAccountFromMemory({ userId: account.profile.userId }) !== null) { -+ this.state.accounts[account.profile.userId] = account; -+ } -+ await this.pushAccounts(); -+ } -+ -+ protected async scaffoldNewAccountStorage(account: TAccount): Promise { -+ await this.scaffoldNewAccountLocalStorage(account); -+ await this.scaffoldNewAccountSessionStorage(account); -+ await this.scaffoldNewAccountMemoryStorage(account); -+ } -+ -+ // TODO: There is a tech debt item for splitting up these methods - only Web uses multiple storage locations in its storageService. -+ // For now these methods exist with some redundancy to facilitate this special web requirement. -+ protected async scaffoldNewAccountLocalStorage(account: TAccount): Promise { -+ const storedAccount = await this.storageService.get( -+ account.profile.userId, -+ await this.defaultOnDiskLocalOptions() -+ ); -+ // EnvironmentUrls are set before authenticating and should override whatever is stored from any previous session -+ const environmentUrls = account.settings.environmentUrls; -+ if (storedAccount?.settings != null) { -+ account.settings = storedAccount.settings; -+ } else if (await this.storageService.has(keys.tempAccountSettings)) { -+ account.settings = await this.storageService.get(keys.tempAccountSettings); -+ await this.storageService.remove(keys.tempAccountSettings); -+ } -+ Object.assign(account.settings, this.createAccount().settings); -+ account.settings.environmentUrls = environmentUrls; -+ await this.storageService.save( -+ account.profile.userId, -+ account, -+ await this.defaultOnDiskLocalOptions() -+ ); -+ } -+ -+ protected async scaffoldNewAccountMemoryStorage(account: TAccount): Promise { -+ const storedAccount = await this.storageService.get( -+ account.profile.userId, -+ await this.defaultOnDiskMemoryOptions() -+ ); -+ if (storedAccount?.settings != null) { -+ storedAccount.settings.environmentUrls = account.settings.environmentUrls; -+ account.settings = storedAccount.settings; -+ } -+ await this.storageService.save( -+ account.profile.userId, -+ account, -+ await this.defaultOnDiskMemoryOptions() -+ ); -+ } -+ -+ protected async scaffoldNewAccountSessionStorage(account: TAccount): Promise { -+ const storedAccount = await this.storageService.get( -+ account.profile.userId, -+ await this.defaultOnDiskOptions() -+ ); -+ if (storedAccount?.settings != null) { -+ storedAccount.settings.environmentUrls = account.settings.environmentUrls; -+ account.settings = storedAccount.settings; -+ } -+ await this.storageService.save( -+ account.profile.userId, -+ account, -+ await this.defaultOnDiskOptions() -+ ); -+ } -+ // -+ -+ protected async pushAccounts(): Promise { -+ await this.pruneInMemoryAccounts(); -+ if (this.state?.accounts == null || Object.keys(this.state.accounts).length < 1) { -+ this.accounts.next(null); -+ return; -+ } -+ -+ this.accounts.next(this.state.accounts); -+ } -+ -+ protected reconcileOptions( -+ requestedOptions: StorageOptions, -+ defaultOptions: StorageOptions -+ ): StorageOptions { -+ if (requestedOptions == null) { -+ return defaultOptions; -+ } -+ requestedOptions.userId = requestedOptions?.userId ?? defaultOptions.userId; -+ requestedOptions.storageLocation = -+ requestedOptions?.storageLocation ?? defaultOptions.storageLocation; -+ requestedOptions.useSecureStorage = -+ requestedOptions?.useSecureStorage ?? defaultOptions.useSecureStorage; -+ requestedOptions.htmlStorageLocation = -+ requestedOptions?.htmlStorageLocation ?? defaultOptions.htmlStorageLocation; -+ requestedOptions.keySuffix = requestedOptions?.keySuffix ?? defaultOptions.keySuffix; -+ return requestedOptions; -+ } -+ -+ protected get defaultInMemoryOptions(): StorageOptions { -+ return { storageLocation: StorageLocation.Memory, userId: this.state.activeUserId }; -+ } -+ -+ protected async defaultOnDiskOptions(): Promise { -+ return { -+ storageLocation: StorageLocation.Disk, -+ htmlStorageLocation: HtmlStorageLocation.Session, -+ userId: this.state.activeUserId ?? (await this.getActiveUserIdFromStorage()), -+ useSecureStorage: false, -+ }; -+ } -+ -+ protected async defaultOnDiskLocalOptions(): Promise { -+ return { -+ storageLocation: StorageLocation.Disk, -+ htmlStorageLocation: HtmlStorageLocation.Local, -+ userId: this.state.activeUserId ?? (await this.getActiveUserIdFromStorage()), -+ useSecureStorage: false, -+ }; -+ } -+ -+ protected async defaultOnDiskMemoryOptions(): Promise { -+ return { -+ storageLocation: StorageLocation.Disk, -+ htmlStorageLocation: HtmlStorageLocation.Memory, -+ userId: this.state.activeUserId ?? (await this.getUserId()), -+ useSecureStorage: false, -+ }; -+ } -+ -+ protected async defaultSecureStorageOptions(): Promise { -+ return { -+ storageLocation: StorageLocation.Disk, -+ useSecureStorage: true, -+ userId: this.state.activeUserId ?? (await this.getActiveUserIdFromStorage()), -+ }; -+ } -+ -+ protected async getActiveUserIdFromStorage(): Promise { -+ return await this.storageService.get(keys.activeUserId); -+ } -+ -+ protected async removeAccountFromLocalStorage( -+ userId: string = this.state.activeUserId -+ ): Promise { -+ const storedAccount = await this.storageService.get(userId, { -+ htmlStorageLocation: HtmlStorageLocation.Local, -+ }); -+ await this.storageService.save( -+ userId, -+ this.resetAccount(storedAccount), -+ await this.defaultOnDiskLocalOptions() -+ ); -+ } -+ -+ protected async removeAccountFromSessionStorage( -+ userId: string = this.state.activeUserId -+ ): Promise { -+ const storedAccount = await this.storageService.get(userId, { -+ htmlStorageLocation: HtmlStorageLocation.Session, -+ }); -+ await this.storageService.save( -+ userId, -+ this.resetAccount(storedAccount), -+ await this.defaultOnDiskOptions() -+ ); -+ } -+ -+ protected async removeAccountFromSecureStorage( -+ userId: string = this.state.activeUserId -+ ): Promise { -+ await this.setCryptoMasterKeyAuto(null, { userId: userId }); -+ await this.setCryptoMasterKeyBiometric(null, { userId: userId }); -+ await this.setCryptoMasterKeyB64(null, { userId: userId }); -+ } -+ -+ protected removeAccountFromMemory(userId: string = this.state.activeUserId): void { -+ delete this.state.accounts[userId]; -+ } -+ -+ protected async pruneInMemoryAccounts() { -+ // We preserve settings for logged out accounts, but we don't want to consider them when thinking about active account state -+ for (const userId in this.state.accounts) { -+ if (!(await this.getIsAuthenticated({ userId: userId }))) { -+ delete this.state.accounts[userId]; -+ } -+ } -+ } -+ -+ // settings persist even on reset, and are not effected by this method -+ protected resetAccount(account: TAccount) { -+ const persistentAccountInformation = { settings: account.settings }; -+ return Object.assign(this.createAccount(), persistentAccountInformation); -+ } -+ -+ protected async setAccountEnvironmentUrls(account: TAccount): Promise { -+ account.settings.environmentUrls = await this.getGlobalEnvironmentUrls(); -+ return account; -+ } -+ -+ protected async getGlobalEnvironmentUrls(options?: StorageOptions): Promise { -+ options = this.reconcileOptions(options, await this.defaultOnDiskOptions()); -+ return (await this.getGlobals(options)).environmentUrls ?? new EnvironmentUrls(); -+ } -+ -+ protected clearDecryptedDataForActiveUser() { -+ const userId = this.state.activeUserId; -+ if (userId == null) { -+ return; -+ } -+ this.state.accounts[userId].data = new AccountData(); -+ } -+ -+ protected createAccount(init: Partial = null): TAccount { -+ return this.stateFactory.createAccount(init); -+ } -+ -+ protected createGlobals(init: Partial = null): TGlobalState { -+ return this.stateFactory.createGlobal(init); -+ } -+ -+ protected async deAuthenticateAccount(userId: string) { -+ await this.setAccessToken(null, { userId: userId }); -+ const index = this.state.authenticatedAccounts.indexOf(userId); -+ if (index > -1) { -+ this.state.authenticatedAccounts.splice(index, 1); -+ await this.storageService.save(keys.authenticatedAccounts, this.state.authenticatedAccounts); -+ } -+ } -+ -+ protected async removeAccountFromDisk(userId: string) { -+ await this.removeAccountFromSessionStorage(userId); -+ await this.removeAccountFromLocalStorage(userId); -+ await this.removeAccountFromSecureStorage(userId); -+ } -+ -+ protected async dynamicallySetActiveUser() { -+ for (const userId in this.state.accounts) { -+ if (userId == null) { -+ continue; -+ } -+ if (await this.getIsAuthenticated({ userId: userId })) { -+ await this.setActiveUser(userId); -+ break; -+ } -+ await this.setActiveUser(null); - } -+ } - } -diff --git a/jslib/common/src/services/stateMigration.service.ts b/jslib/common/src/services/stateMigration.service.ts -new file mode 100644 -index 00000000..c1bf03a7 ---- /dev/null -+++ b/jslib/common/src/services/stateMigration.service.ts -@@ -0,0 +1,451 @@ -+import { StorageService } from "../abstractions/storage.service"; -+ -+import { GeneratedPasswordHistory } from "../models/domain/generatedPasswordHistory"; -+import { GlobalState } from "../models/domain/globalState"; -+import { StorageOptions } from "../models/domain/storageOptions"; -+ -+import { CipherData } from "../models/data/cipherData"; -+import { CollectionData } from "../models/data/collectionData"; -+import { EventData } from "../models/data/eventData"; -+import { FolderData } from "../models/data/folderData"; -+import { OrganizationData } from "../models/data/organizationData"; -+import { PolicyData } from "../models/data/policyData"; -+import { ProviderData } from "../models/data/providerData"; -+import { SendData } from "../models/data/sendData"; -+ -+import { HtmlStorageLocation } from "../enums/htmlStorageLocation"; -+import { KdfType } from "../enums/kdfType"; -+import { StateVersion } from "../enums/stateVersion"; -+import { ThemeType } from "../enums/themeType"; -+ -+import { EnvironmentUrls } from "../models/domain/environmentUrls"; -+ -+import { GlobalStateFactory } from "../factories/globalStateFactory"; -+ -+// Originally (before January 2022) storage was handled as a flat key/value pair store. -+// With the move to a typed object for state storage these keys should no longer be in use anywhere outside of this migration. -+const v1Keys: { [key: string]: string } = { -+ accessToken: "accessToken", -+ alwaysShowDock: "alwaysShowDock", -+ autoConfirmFingerprints: "autoConfirmFingerprints", -+ autoFillOnPageLoadDefault: "autoFillOnPageLoadDefault", -+ biometricAwaitingAcceptance: "biometricAwaitingAcceptance", -+ biometricFingerprintValidated: "biometricFingerprintValidated", -+ biometricText: "biometricText", -+ biometricUnlock: "biometric", -+ clearClipboard: "clearClipboardKey", -+ clientId: "apikey_clientId", -+ clientSecret: "apikey_clientSecret", -+ collapsedGroupings: "collapsedGroupings", -+ convertAccountToKeyConnector: "convertAccountToKeyConnector", -+ defaultUriMatch: "defaultUriMatch", -+ disableAddLoginNotification: "disableAddLoginNotification", -+ disableAutoBiometricsPrompt: "noAutoPromptBiometrics", -+ disableAutoTotpCopy: "disableAutoTotpCopy", -+ disableBadgeCounter: "disableBadgeCounter", -+ disableChangedPasswordNotification: "disableChangedPasswordNotification", -+ disableContextMenuItem: "disableContextMenuItem", -+ disableFavicon: "disableFavicon", -+ disableGa: "disableGa", -+ dontShowCardsCurrentTab: "dontShowCardsCurrentTab", -+ dontShowIdentitiesCurrentTab: "dontShowIdentitiesCurrentTab", -+ emailVerified: "emailVerified", -+ enableAlwaysOnTop: "enableAlwaysOnTopKey", -+ enableAutoFillOnPageLoad: "enableAutoFillOnPageLoad", -+ enableBiometric: "enabledBiometric", -+ enableBrowserIntegration: "enableBrowserIntegration", -+ enableBrowserIntegrationFingerprint: "enableBrowserIntegrationFingerprint", -+ enableCloseToTray: "enableCloseToTray", -+ enableFullWidth: "enableFullWidth", -+ enableGravatars: "enableGravatars", -+ enableMinimizeToTray: "enableMinimizeToTray", -+ enableStartToTray: "enableStartToTrayKey", -+ enableTray: "enableTray", -+ encKey: "encKey", // Generated Symmetric Key -+ encOrgKeys: "encOrgKeys", -+ encPrivate: "encPrivateKey", -+ encProviderKeys: "encProviderKeys", -+ entityId: "entityId", -+ entityType: "entityType", -+ environmentUrls: "environmentUrls", -+ equivalentDomains: "equivalentDomains", -+ eventCollection: "eventCollection", -+ forcePasswordReset: "forcePasswordReset", -+ history: "generatedPasswordHistory", -+ installedVersion: "installedVersion", -+ kdf: "kdf", -+ kdfIterations: "kdfIterations", -+ key: "key", // Master Key -+ keyHash: "keyHash", -+ lastActive: "lastActive", -+ localData: "sitesLocalData", -+ locale: "locale", -+ mainWindowSize: "mainWindowSize", -+ minimizeOnCopyToClipboard: "minimizeOnCopyToClipboardKey", -+ neverDomains: "neverDomains", -+ noAutoPromptBiometricsText: "noAutoPromptBiometricsText", -+ openAtLogin: "openAtLogin", -+ passwordGenerationOptions: "passwordGenerationOptions", -+ pinProtected: "pinProtectedKey", -+ protectedPin: "protectedPin", -+ refreshToken: "refreshToken", -+ ssoCodeVerifier: "ssoCodeVerifier", -+ ssoIdentifier: "ssoOrgIdentifier", -+ ssoState: "ssoState", -+ stamp: "securityStamp", -+ theme: "theme", -+ userEmail: "userEmail", -+ userId: "userId", -+ usesConnector: "usesKeyConnector", -+ vaultTimeoutAction: "vaultTimeoutAction", -+ vaultTimeout: "lockOption", -+ rememberedEmail: "rememberedEmail", -+}; -+ -+const v1KeyPrefixes: { [key: string]: string } = { -+ ciphers: "ciphers_", -+ collections: "collections_", -+ folders: "folders_", -+ lastSync: "lastSync_", -+ policies: "policies_", -+ twoFactorToken: "twoFactorToken_", -+ organizations: "organizations_", -+ providers: "providers_", -+ sends: "sends_", -+ settings: "settings_", -+}; -+ -+const keys = { -+ global: "global", -+ authenticatedAccounts: "authenticatedAccounts", -+ activeUserId: "activeUserId", -+ tempAccountSettings: "tempAccountSettings", // used to hold account specific settings (i.e clear clipboard) between initial migration and first account authentication -+}; -+ -+const partialKeys = { -+ autoKey: "_masterkey_auto", -+ biometricKey: "_masterkey_biometric", -+ masterKey: "_masterkey", -+}; -+ -+export class StateMigrationService { -+ constructor( -+ protected storageService: StorageService, -+ protected secureStorageService: StorageService, -+ protected globalStateFactory: GlobalStateFactory -+ ) {} -+ -+ async needsMigration(): Promise { -+ const currentStateVersion = await this.getCurrentStateVersion(); -+ return currentStateVersion == null || currentStateVersion < StateVersion.Latest; -+ } -+ -+ async migrate(): Promise { -+ let currentStateVersion = await this.getCurrentStateVersion(); -+ while (currentStateVersion < StateVersion.Latest) { -+ switch (currentStateVersion) { -+ case StateVersion.One: -+ await this.migrateStateFrom1To2(); -+ break; -+ } -+ -+ currentStateVersion += 1; -+ } -+ } -+ -+ protected async migrateStateFrom1To2(): Promise { -+ const clearV1Keys = async (clearingUserId?: string) => { -+ for (const key in v1Keys) { -+ if (key == null) { -+ continue; -+ } -+ await this.set(v1Keys[key], null); -+ } -+ if (clearingUserId != null) { -+ for (const keyPrefix in v1KeyPrefixes) { -+ if (keyPrefix == null) { -+ continue; -+ } -+ await this.set(v1KeyPrefixes[keyPrefix] + userId, null); -+ } -+ } -+ }; -+ -+ // Some processes, like biometrics, may have already defined a value before migrations are run. -+ // We don't want to null out those values if they don't exist in the old storage scheme (like for new installs) -+ // So, the OOO for migration is that we: -+ // 1. Check for an existing storage value from the old storage structure OR -+ // 2. Check for a value already set by processes that run before migration OR -+ // 3. Assign the default value -+ const globals = (await this.get(keys.global)) ?? this.globalStateFactory.create(); -+ globals.stateVersion = StateVersion.Two; -+ globals.environmentUrls = -+ (await this.get(v1Keys.environmentUrls)) ?? globals.environmentUrls; -+ globals.locale = (await this.get(v1Keys.locale)) ?? globals.locale; -+ globals.noAutoPromptBiometrics = -+ (await this.get(v1Keys.disableAutoBiometricsPrompt)) ?? -+ globals.noAutoPromptBiometrics; -+ globals.noAutoPromptBiometricsText = -+ (await this.get(v1Keys.noAutoPromptBiometricsText)) ?? -+ globals.noAutoPromptBiometricsText; -+ globals.ssoCodeVerifier = -+ (await this.get(v1Keys.ssoCodeVerifier)) ?? globals.ssoCodeVerifier; -+ globals.ssoOrganizationIdentifier = -+ (await this.get(v1Keys.ssoIdentifier)) ?? globals.ssoOrganizationIdentifier; -+ globals.ssoState = (await this.get(v1Keys.ssoState)) ?? globals.ssoState; -+ globals.rememberedEmail = -+ (await this.get(v1Keys.rememberedEmail)) ?? globals.rememberedEmail; -+ globals.theme = (await this.get(v1Keys.theme)) ?? globals.theme; -+ globals.vaultTimeout = (await this.get(v1Keys.vaultTimeout)) ?? globals.vaultTimeout; -+ globals.vaultTimeoutAction = -+ (await this.get(v1Keys.vaultTimeoutAction)) ?? globals.vaultTimeoutAction; -+ globals.window = (await this.get(v1Keys.mainWindowSize)) ?? globals.window; -+ globals.enableTray = (await this.get(v1Keys.enableTray)) ?? globals.enableTray; -+ globals.enableMinimizeToTray = -+ (await this.get(v1Keys.enableMinimizeToTray)) ?? globals.enableMinimizeToTray; -+ globals.enableCloseToTray = -+ (await this.get(v1Keys.enableCloseToTray)) ?? globals.enableCloseToTray; -+ globals.enableStartToTray = -+ (await this.get(v1Keys.enableStartToTray)) ?? globals.enableStartToTray; -+ globals.openAtLogin = (await this.get(v1Keys.openAtLogin)) ?? globals.openAtLogin; -+ globals.alwaysShowDock = -+ (await this.get(v1Keys.alwaysShowDock)) ?? globals.alwaysShowDock; -+ globals.enableBrowserIntegration = -+ (await this.get(v1Keys.enableBrowserIntegration)) ?? -+ globals.enableBrowserIntegration; -+ globals.enableBrowserIntegrationFingerprint = -+ (await this.get(v1Keys.enableBrowserIntegrationFingerprint)) ?? -+ globals.enableBrowserIntegrationFingerprint; -+ -+ const userId = -+ (await this.get(v1Keys.userId)) ?? (await this.get(v1Keys.entityId)); -+ -+ // (userId == null) = no logged in user (so no known userId) and we need to temporarily store account specific settings in state to migrate on first auth -+ // (userId != null) = we have a currently authed user (so known userId) with encrypted data and other key settings we can move, no need to temporarily store account settings -+ if (userId == null) { -+ await this.set(keys.tempAccountSettings, { -+ autoConfirmFingerPrints: await this.get(v1Keys.autoConfirmFingerprints), -+ autoFillOnPageLoadDefault: await this.get(v1Keys.autoFillOnPageLoadDefault), -+ biometricLocked: null, -+ biometricUnlock: await this.get(v1Keys.biometricUnlock), -+ clearClipboard: await this.get(v1Keys.clearClipboard), -+ defaultUriMatch: await this.get(v1Keys.defaultUriMatch), -+ disableAddLoginNotification: await this.get(v1Keys.disableAddLoginNotification), -+ disableAutoBiometricsPrompt: await this.get(v1Keys.disableAutoBiometricsPrompt), -+ disableAutoTotpCopy: await this.get(v1Keys.disableAutoTotpCopy), -+ disableBadgeCounter: await this.get(v1Keys.disableBadgeCounter), -+ disableChangedPasswordNotification: await this.get( -+ v1Keys.disableChangedPasswordNotification -+ ), -+ disableContextMenuItem: await this.get(v1Keys.disableContextMenuItem), -+ disableGa: await this.get(v1Keys.disableGa), -+ dontShowCardsCurrentTab: await this.get(v1Keys.dontShowCardsCurrentTab), -+ dontShowIdentitiesCurrentTab: await this.get(v1Keys.dontShowIdentitiesCurrentTab), -+ enableAlwaysOnTop: await this.get(v1Keys.enableAlwaysOnTop), -+ enableAutoFillOnPageLoad: await this.get(v1Keys.enableAutoFillOnPageLoad), -+ enableBiometric: await this.get(v1Keys.enableBiometric), -+ enableFullWidth: await this.get(v1Keys.enableFullWidth), -+ enableGravitars: await this.get(v1Keys.enableGravatars), -+ environmentUrls: globals.environmentUrls, -+ equivalentDomains: await this.get(v1Keys.equivalentDomains), -+ minimizeOnCopyToClipboard: await this.get(v1Keys.minimizeOnCopyToClipboard), -+ neverDomains: await this.get(v1Keys.neverDomains), -+ passwordGenerationOptions: await this.get(v1Keys.passwordGenerationOptions), -+ pinProtected: { -+ decrypted: null, -+ encrypted: await this.get(v1Keys.pinProtected), -+ }, -+ protectedPin: await this.get(v1Keys.protectedPin), -+ settings: null, -+ vaultTimeout: await this.get(v1Keys.vaultTimeout), -+ vaultTimeoutAction: await this.get(v1Keys.vaultTimeoutAction), -+ }); -+ await this.set(keys.global, globals); -+ await this.set(keys.authenticatedAccounts, []); -+ await this.set(keys.activeUserId, null); -+ await clearV1Keys(); -+ return; -+ } -+ -+ globals.twoFactorToken = await this.get(v1KeyPrefixes.twoFactorToken + userId); -+ await this.set(keys.global, globals); -+ await this.set(userId, { -+ data: { -+ addEditCipherInfo: null, -+ ciphers: { -+ decrypted: null, -+ encrypted: await this.get<{ [id: string]: CipherData }>(v1KeyPrefixes.ciphers + userId), -+ }, -+ collapsedGroupings: null, -+ collections: { -+ decrypted: null, -+ encrypted: await this.get<{ [id: string]: CollectionData }>( -+ v1KeyPrefixes.collections + userId -+ ), -+ }, -+ eventCollection: await this.get(v1Keys.eventCollection), -+ folders: { -+ decrypted: null, -+ encrypted: await this.get<{ [id: string]: FolderData }>(v1KeyPrefixes.folders + userId), -+ }, -+ localData: null, -+ organizations: await this.get<{ [id: string]: OrganizationData }>( -+ v1KeyPrefixes.organizations + userId -+ ), -+ passwordGenerationHistory: { -+ decrypted: null, -+ encrypted: await this.get(v1Keys.history), -+ }, -+ policies: { -+ decrypted: null, -+ encrypted: await this.get<{ [id: string]: PolicyData }>(v1KeyPrefixes.policies + userId), -+ }, -+ providers: await this.get<{ [id: string]: ProviderData }>(v1KeyPrefixes.providers + userId), -+ sends: { -+ decrypted: null, -+ encrypted: await this.get<{ [id: string]: SendData }>(v1KeyPrefixes.sends + userId), -+ }, -+ }, -+ keys: { -+ apiKeyClientSecret: await this.get(v1Keys.clientSecret), -+ cryptoMasterKey: null, -+ cryptoMasterKeyAuto: null, -+ cryptoMasterKeyB64: null, -+ cryptoMasterKeyBiometric: null, -+ cryptoSymmetricKey: { -+ encrypted: await this.get(v1Keys.encKey), -+ decrypted: null, -+ }, -+ legacyEtmKey: null, -+ organizationKeys: { -+ decrypted: null, -+ encrypted: await this.get(v1Keys.encOrgKeys + userId), -+ }, -+ privateKey: { -+ decrypted: null, -+ encrypted: await this.get(v1Keys.encPrivate), -+ }, -+ providerKeys: { -+ decrypted: null, -+ encrypted: await this.get(v1Keys.encProviderKeys + userId), -+ }, -+ publicKey: null, -+ }, -+ profile: { -+ apiKeyClientId: await this.get(v1Keys.clientId), -+ authenticationStatus: null, -+ convertAccountToKeyConnector: await this.get(v1Keys.convertAccountToKeyConnector), -+ email: await this.get(v1Keys.userEmail), -+ emailVerified: await this.get(v1Keys.emailVerified), -+ entityId: null, -+ entityType: null, -+ everBeenUnlocked: null, -+ forcePasswordReset: null, -+ hasPremiumPersonally: null, -+ kdfIterations: await this.get(v1Keys.kdfIterations), -+ kdfType: await this.get(v1Keys.kdf), -+ keyHash: await this.get(v1Keys.keyHash), -+ lastActive: await this.get(v1Keys.lastActive), -+ lastSync: null, -+ userId: userId, -+ usesKeyConnector: null, -+ }, -+ settings: { -+ autoConfirmFingerPrints: await this.get(v1Keys.autoConfirmFingerprints), -+ autoFillOnPageLoadDefault: await this.get(v1Keys.autoFillOnPageLoadDefault), -+ biometricLocked: null, -+ biometricUnlock: await this.get(v1Keys.biometricUnlock), -+ clearClipboard: await this.get(v1Keys.clearClipboard), -+ defaultUriMatch: await this.get(v1Keys.defaultUriMatch), -+ disableAddLoginNotification: await this.get(v1Keys.disableAddLoginNotification), -+ disableAutoBiometricsPrompt: await this.get(v1Keys.disableAutoBiometricsPrompt), -+ disableAutoTotpCopy: await this.get(v1Keys.disableAutoTotpCopy), -+ disableBadgeCounter: await this.get(v1Keys.disableBadgeCounter), -+ disableChangedPasswordNotification: await this.get( -+ v1Keys.disableChangedPasswordNotification -+ ), -+ disableContextMenuItem: await this.get(v1Keys.disableContextMenuItem), -+ disableGa: await this.get(v1Keys.disableGa), -+ dontShowCardsCurrentTab: await this.get(v1Keys.dontShowCardsCurrentTab), -+ dontShowIdentitiesCurrentTab: await this.get(v1Keys.dontShowIdentitiesCurrentTab), -+ enableAlwaysOnTop: await this.get(v1Keys.enableAlwaysOnTop), -+ enableAutoFillOnPageLoad: await this.get(v1Keys.enableAutoFillOnPageLoad), -+ enableBiometric: await this.get(v1Keys.enableBiometric), -+ enableFullWidth: await this.get(v1Keys.enableFullWidth), -+ enableGravitars: await this.get(v1Keys.enableGravatars), -+ environmentUrls: globals.environmentUrls, -+ equivalentDomains: await this.get(v1Keys.equivalentDomains), -+ minimizeOnCopyToClipboard: await this.get(v1Keys.minimizeOnCopyToClipboard), -+ neverDomains: await this.get(v1Keys.neverDomains), -+ passwordGenerationOptions: await this.get(v1Keys.passwordGenerationOptions), -+ pinProtected: { -+ decrypted: null, -+ encrypted: await this.get(v1Keys.pinProtected), -+ }, -+ protectedPin: await this.get(v1Keys.protectedPin), -+ settings: await this.get(v1KeyPrefixes.settings + userId), -+ vaultTimeout: await this.get(v1Keys.vaultTimeout), -+ vaultTimeoutAction: await this.get(v1Keys.vaultTimeoutAction), -+ }, -+ tokens: { -+ accessToken: await this.get(v1Keys.accessToken), -+ decodedToken: null, -+ refreshToken: await this.get(v1Keys.refreshToken), -+ securityStamp: null, -+ }, -+ }); -+ -+ await this.set(keys.authenticatedAccounts, [userId]); -+ await this.set(keys.activeUserId, userId); -+ await clearV1Keys(userId); -+ -+ if (await this.secureStorageService.has(v1Keys.key, { keySuffix: "biometric" })) { -+ await this.secureStorageService.save( -+ `${userId}${partialKeys.biometricKey}`, -+ await this.secureStorageService.get(v1Keys.key, { keySuffix: "biometric" }), -+ { keySuffix: "biometric" } -+ ); -+ await this.secureStorageService.remove(v1Keys.key, { keySuffix: "biometric" }); -+ } -+ -+ if (await this.secureStorageService.has(v1Keys.key, { keySuffix: "auto" })) { -+ await this.secureStorageService.save( -+ `${userId}${partialKeys.autoKey}`, -+ await this.secureStorageService.get(v1Keys.key, { keySuffix: "auto" }), -+ { keySuffix: "auto" } -+ ); -+ await this.secureStorageService.remove(v1Keys.key, { keySuffix: "auto" }); -+ } -+ -+ if (await this.secureStorageService.has(v1Keys.key)) { -+ await this.secureStorageService.save( -+ `${userId}${partialKeys.masterKey}`, -+ await this.secureStorageService.get(v1Keys.key) -+ ); -+ await this.secureStorageService.remove(v1Keys.key); -+ } -+ } -+ -+ protected get options(): StorageOptions { -+ return { htmlStorageLocation: HtmlStorageLocation.Local }; -+ } -+ -+ protected get(key: string): Promise { -+ return this.storageService.get(key, this.options); -+ } -+ -+ protected set(key: string, value: any): Promise { -+ if (value == null) { -+ return this.storageService.remove(key, this.options); -+ } -+ return this.storageService.save(key, value, this.options); -+ } -+ -+ protected async getGlobals(): Promise { -+ return await this.get(keys.global); -+ } -+ -+ protected async getCurrentStateVersion(): Promise { -+ return (await this.getGlobals())?.stateVersion; -+ } -+} -diff --git a/jslib/common/src/services/sync.service.ts b/jslib/common/src/services/sync.service.ts -index 8395ddcc..d32db18e 100644 ---- a/jslib/common/src/services/sync.service.ts -+++ b/jslib/common/src/services/sync.service.ts -@@ -1,394 +1,402 @@ --import { ApiService } from '../abstractions/api.service'; --import { CipherService } from '../abstractions/cipher.service'; --import { CollectionService } from '../abstractions/collection.service'; --import { CryptoService } from '../abstractions/crypto.service'; --import { FolderService } from '../abstractions/folder.service'; --import { KeyConnectorService } from '../abstractions/keyConnector.service'; --import { LogService } from '../abstractions/log.service'; --import { MessagingService } from '../abstractions/messaging.service'; --import { PolicyService } from '../abstractions/policy.service'; --import { SendService } from '../abstractions/send.service'; --import { SettingsService } from '../abstractions/settings.service'; --import { StorageService } from '../abstractions/storage.service'; --import { SyncService as SyncServiceAbstraction } from '../abstractions/sync.service'; --import { TokenService } from '../abstractions/token.service'; --import { UserService } from '../abstractions/user.service'; -- --import { CipherData } from '../models/data/cipherData'; --import { CollectionData } from '../models/data/collectionData'; --import { FolderData } from '../models/data/folderData'; --import { OrganizationData } from '../models/data/organizationData'; --import { PolicyData } from '../models/data/policyData'; --import { ProviderData } from '../models/data/providerData'; --import { SendData } from '../models/data/sendData'; -- --import { CipherResponse } from '../models/response/cipherResponse'; --import { CollectionDetailsResponse } from '../models/response/collectionResponse'; --import { DomainsResponse } from '../models/response/domainsResponse'; --import { FolderResponse } from '../models/response/folderResponse'; -+import { ApiService } from "../abstractions/api.service"; -+import { CipherService } from "../abstractions/cipher.service"; -+import { CollectionService } from "../abstractions/collection.service"; -+import { CryptoService } from "../abstractions/crypto.service"; -+import { FolderService } from "../abstractions/folder.service"; -+import { KeyConnectorService } from "../abstractions/keyConnector.service"; -+import { LogService } from "../abstractions/log.service"; -+import { MessagingService } from "../abstractions/messaging.service"; -+import { OrganizationService } from "../abstractions/organization.service"; -+import { PolicyService } from "../abstractions/policy.service"; -+import { ProviderService } from "../abstractions/provider.service"; -+import { SendService } from "../abstractions/send.service"; -+import { SettingsService } from "../abstractions/settings.service"; -+import { StateService } from "../abstractions/state.service"; -+import { SyncService as SyncServiceAbstraction } from "../abstractions/sync.service"; -+ -+import { CipherData } from "../models/data/cipherData"; -+import { CollectionData } from "../models/data/collectionData"; -+import { FolderData } from "../models/data/folderData"; -+import { OrganizationData } from "../models/data/organizationData"; -+import { PolicyData } from "../models/data/policyData"; -+import { ProviderData } from "../models/data/providerData"; -+import { SendData } from "../models/data/sendData"; -+ -+import { CipherResponse } from "../models/response/cipherResponse"; -+import { CollectionDetailsResponse } from "../models/response/collectionResponse"; -+import { DomainsResponse } from "../models/response/domainsResponse"; -+import { FolderResponse } from "../models/response/folderResponse"; - import { -- SyncCipherNotification, -- SyncFolderNotification, -- SyncSendNotification, --} from '../models/response/notificationResponse'; --import { PolicyResponse } from '../models/response/policyResponse'; --import { ProfileResponse } from '../models/response/profileResponse'; --import { SendResponse } from '../models/response/sendResponse'; -- --const Keys = { -- lastSyncPrefix: 'lastSync_', --}; -+ SyncCipherNotification, -+ SyncFolderNotification, -+ SyncSendNotification, -+} from "../models/response/notificationResponse"; -+import { PolicyResponse } from "../models/response/policyResponse"; -+import { ProfileResponse } from "../models/response/profileResponse"; -+import { SendResponse } from "../models/response/sendResponse"; - - export class SyncService implements SyncServiceAbstraction { -- syncInProgress: boolean = false; -- -- constructor(private userService: UserService, private apiService: ApiService, -- private settingsService: SettingsService, private folderService: FolderService, -- private cipherService: CipherService, private cryptoService: CryptoService, -- private collectionService: CollectionService, private storageService: StorageService, -- private messagingService: MessagingService, private policyService: PolicyService, -- private sendService: SendService, private logService: LogService, -- private tokenService: TokenService, private keyConnectorService: KeyConnectorService, -- private logoutCallback: (expired: boolean) => Promise) { -+ syncInProgress: boolean = false; -+ -+ constructor( -+ private apiService: ApiService, -+ private settingsService: SettingsService, -+ private folderService: FolderService, -+ private cipherService: CipherService, -+ private cryptoService: CryptoService, -+ private collectionService: CollectionService, -+ private messagingService: MessagingService, -+ private policyService: PolicyService, -+ private sendService: SendService, -+ private logService: LogService, -+ private keyConnectorService: KeyConnectorService, -+ private stateService: StateService, -+ private organizationService: OrganizationService, -+ private providerService: ProviderService, -+ private logoutCallback: (expired: boolean) => Promise -+ ) {} -+ -+ async getLastSync(): Promise { -+ if ((await this.stateService.getUserId()) == null) { -+ return null; - } - -- async getLastSync(): Promise { -- const userId = await this.userService.getUserId(); -- if (userId == null) { -- return null; -- } -+ const lastSync = await this.stateService.getLastSync(); -+ if (lastSync) { -+ return new Date(lastSync); -+ } - -- const lastSync = await this.storageService.get(Keys.lastSyncPrefix + userId); -- if (lastSync) { -- return new Date(lastSync); -- } -+ return null; -+ } - -- return null; -- } -+ async setLastSync(date: Date, userId?: string): Promise { -+ await this.stateService.setLastSync(date.toJSON(), { userId: userId }); -+ } - -- async setLastSync(date: Date): Promise { -- const userId = await this.userService.getUserId(); -- if (userId == null) { -- return; -- } -+ async fullSync(forceSync: boolean, allowThrowOnError = false): Promise { -+ this.syncStarted(); -+ const isAuthenticated = await this.stateService.getIsAuthenticated(); -+ if (!isAuthenticated) { -+ return this.syncCompleted(false); -+ } - -- await this.storageService.save(Keys.lastSyncPrefix + userId, date.toJSON()); -+ const now = new Date(); -+ let needsSync = false; -+ try { -+ needsSync = await this.needsSyncing(forceSync); -+ } catch (e) { -+ if (allowThrowOnError) { -+ throw e; -+ } - } - -- async fullSync(forceSync: boolean, allowThrowOnError = false): Promise { -- this.syncStarted(); -- const isAuthenticated = await this.userService.isAuthenticated(); -- if (!isAuthenticated) { -- return this.syncCompleted(false); -- } -+ if (!needsSync) { -+ await this.setLastSync(now); -+ return this.syncCompleted(false); -+ } - -- const now = new Date(); -- let needsSync = false; -- try { -- needsSync = await this.needsSyncing(forceSync); -- } catch (e) { -- if (allowThrowOnError) { -- throw e; -- } -+ const userId = await this.stateService.getUserId(); -+ try { -+ await this.apiService.refreshIdentityToken(); -+ const response = await this.apiService.getSync(); -+ -+ await this.syncProfile(response.profile); -+ await this.syncFolders(userId, response.folders); -+ await this.syncCollections(response.collections); -+ await this.syncCiphers(userId, response.ciphers); -+ await this.syncSends(userId, response.sends); -+ await this.syncSettings(response.domains); -+ await this.syncPolicies(response.policies); -+ -+ await this.setLastSync(now); -+ return this.syncCompleted(true); -+ } catch (e) { -+ if (allowThrowOnError) { -+ throw e; -+ } else { -+ return this.syncCompleted(false); -+ } -+ } -+ } -+ -+ async syncUpsertFolder(notification: SyncFolderNotification, isEdit: boolean): Promise { -+ this.syncStarted(); -+ if (await this.stateService.getIsAuthenticated()) { -+ try { -+ const localFolder = await this.folderService.get(notification.id); -+ if ( -+ (!isEdit && localFolder == null) || -+ (isEdit && localFolder != null && localFolder.revisionDate < notification.revisionDate) -+ ) { -+ const remoteFolder = await this.apiService.getFolder(notification.id); -+ if (remoteFolder != null) { -+ const userId = await this.stateService.getUserId(); -+ await this.folderService.upsert(new FolderData(remoteFolder, userId)); -+ this.messagingService.send("syncedUpsertedFolder", { folderId: notification.id }); -+ return this.syncCompleted(true); -+ } - } -- -- if (!needsSync) { -- await this.setLastSync(now); -- return this.syncCompleted(false); -+ } catch (e) { -+ this.logService.error(e); -+ } -+ } -+ return this.syncCompleted(false); -+ } -+ -+ async syncDeleteFolder(notification: SyncFolderNotification): Promise { -+ this.syncStarted(); -+ if (await this.stateService.getIsAuthenticated()) { -+ await this.folderService.delete(notification.id); -+ this.messagingService.send("syncedDeletedFolder", { folderId: notification.id }); -+ this.syncCompleted(true); -+ return true; -+ } -+ return this.syncCompleted(false); -+ } -+ -+ async syncUpsertCipher(notification: SyncCipherNotification, isEdit: boolean): Promise { -+ this.syncStarted(); -+ if (await this.stateService.getIsAuthenticated()) { -+ try { -+ let shouldUpdate = true; -+ const localCipher = await this.cipherService.get(notification.id); -+ if (localCipher != null && localCipher.revisionDate >= notification.revisionDate) { -+ shouldUpdate = false; - } - -- const userId = await this.userService.getUserId(); -- try { -- await this.apiService.refreshIdentityToken(); -- const response = await this.apiService.getSync(); -- -- await this.syncProfile(response.profile); -- await this.syncFolders(userId, response.folders); -- await this.syncCollections(response.collections); -- await this.syncCiphers(userId, response.ciphers); -- await this.syncSends(userId, response.sends); -- await this.syncSettings(userId, response.domains); -- await this.syncPolicies(response.policies); -- -- await this.setLastSync(now); -- return this.syncCompleted(true); -- } catch (e) { -- if (allowThrowOnError) { -- throw e; -+ let checkCollections = false; -+ if (shouldUpdate) { -+ if (isEdit) { -+ shouldUpdate = localCipher != null; -+ checkCollections = true; -+ } else { -+ if (notification.collectionIds == null || notification.organizationId == null) { -+ shouldUpdate = localCipher == null; - } else { -- return this.syncCompleted(false); -+ shouldUpdate = false; -+ checkCollections = true; - } -+ } - } -- } - -- async syncUpsertFolder(notification: SyncFolderNotification, isEdit: boolean): Promise { -- this.syncStarted(); -- if (await this.userService.isAuthenticated()) { -- try { -- const localFolder = await this.folderService.get(notification.id); -- if ((!isEdit && localFolder == null) || -- (isEdit && localFolder != null && localFolder.revisionDate < notification.revisionDate)) { -- const remoteFolder = await this.apiService.getFolder(notification.id); -- if (remoteFolder != null) { -- const userId = await this.userService.getUserId(); -- await this.folderService.upsert(new FolderData(remoteFolder, userId)); -- this.messagingService.send('syncedUpsertedFolder', { folderId: notification.id }); -- return this.syncCompleted(true); -- } -- } -- } catch (e) { -- this.logService.error(e); -+ if ( -+ !shouldUpdate && -+ checkCollections && -+ notification.organizationId != null && -+ notification.collectionIds != null && -+ notification.collectionIds.length > 0 -+ ) { -+ const collections = await this.collectionService.getAll(); -+ if (collections != null) { -+ for (let i = 0; i < collections.length; i++) { -+ if (notification.collectionIds.indexOf(collections[i].id) > -1) { -+ shouldUpdate = true; -+ break; -+ } - } -+ } - } -- return this.syncCompleted(false); -- } - -- async syncDeleteFolder(notification: SyncFolderNotification): Promise { -- this.syncStarted(); -- if (await this.userService.isAuthenticated()) { -- await this.folderService.delete(notification.id); -- this.messagingService.send('syncedDeletedFolder', { folderId: notification.id }); -- this.syncCompleted(true); -- return true; -+ if (shouldUpdate) { -+ const remoteCipher = await this.apiService.getCipher(notification.id); -+ if (remoteCipher != null) { -+ const userId = await this.stateService.getUserId(); -+ await this.cipherService.upsert(new CipherData(remoteCipher, userId)); -+ this.messagingService.send("syncedUpsertedCipher", { cipherId: notification.id }); -+ return this.syncCompleted(true); -+ } - } -- return this.syncCompleted(false); -- } -- -- async syncUpsertCipher(notification: SyncCipherNotification, isEdit: boolean): Promise { -- this.syncStarted(); -- if (await this.userService.isAuthenticated()) { -- try { -- let shouldUpdate = true; -- const localCipher = await this.cipherService.get(notification.id); -- if (localCipher != null && localCipher.revisionDate >= notification.revisionDate) { -- shouldUpdate = false; -- } -- -- let checkCollections = false; -- if (shouldUpdate) { -- if (isEdit) { -- shouldUpdate = localCipher != null; -- checkCollections = true; -- } else { -- if (notification.collectionIds == null || notification.organizationId == null) { -- shouldUpdate = localCipher == null; -- } else { -- shouldUpdate = false; -- checkCollections = true; -- } -- } -- } -- -- if (!shouldUpdate && checkCollections && notification.organizationId != null && -- notification.collectionIds != null && notification.collectionIds.length > 0) { -- const collections = await this.collectionService.getAll(); -- if (collections != null) { -- for (let i = 0; i < collections.length; i++) { -- if (notification.collectionIds.indexOf(collections[i].id) > -1) { -- shouldUpdate = true; -- break; -- } -- } -- } -- } -- -- if (shouldUpdate) { -- const remoteCipher = await this.apiService.getCipher(notification.id); -- if (remoteCipher != null) { -- const userId = await this.userService.getUserId(); -- await this.cipherService.upsert(new CipherData(remoteCipher, userId)); -- this.messagingService.send('syncedUpsertedCipher', { cipherId: notification.id }); -- return this.syncCompleted(true); -- } -- } -- } catch (e) { -- if (e != null && e.statusCode === 404 && isEdit) { -- await this.cipherService.delete(notification.id); -- this.messagingService.send('syncedDeletedCipher', { cipherId: notification.id }); -- return this.syncCompleted(true); -- } -- } -+ } catch (e) { -+ if (e != null && e.statusCode === 404 && isEdit) { -+ await this.cipherService.delete(notification.id); -+ this.messagingService.send("syncedDeletedCipher", { cipherId: notification.id }); -+ return this.syncCompleted(true); - } -- return this.syncCompleted(false); -+ } - } -- -- async syncDeleteCipher(notification: SyncCipherNotification): Promise { -- this.syncStarted(); -- if (await this.userService.isAuthenticated()) { -- await this.cipherService.delete(notification.id); -- this.messagingService.send('syncedDeletedCipher', { cipherId: notification.id }); -+ return this.syncCompleted(false); -+ } -+ -+ async syncDeleteCipher(notification: SyncCipherNotification): Promise { -+ this.syncStarted(); -+ if (await this.stateService.getIsAuthenticated()) { -+ await this.cipherService.delete(notification.id); -+ this.messagingService.send("syncedDeletedCipher", { cipherId: notification.id }); -+ return this.syncCompleted(true); -+ } -+ return this.syncCompleted(false); -+ } -+ -+ async syncUpsertSend(notification: SyncSendNotification, isEdit: boolean): Promise { -+ this.syncStarted(); -+ if (await this.stateService.getIsAuthenticated()) { -+ try { -+ const localSend = await this.sendService.get(notification.id); -+ if ( -+ (!isEdit && localSend == null) || -+ (isEdit && localSend != null && localSend.revisionDate < notification.revisionDate) -+ ) { -+ const remoteSend = await this.apiService.getSend(notification.id); -+ if (remoteSend != null) { -+ const userId = await this.stateService.getUserId(); -+ await this.sendService.upsert(new SendData(remoteSend, userId)); -+ this.messagingService.send("syncedUpsertedSend", { sendId: notification.id }); - return this.syncCompleted(true); -+ } - } -- return this.syncCompleted(false); -+ } catch (e) { -+ this.logService.error(e); -+ } - } -- -- async syncUpsertSend(notification: SyncSendNotification, isEdit: boolean): Promise { -- this.syncStarted(); -- if (await this.userService.isAuthenticated()) { -- try { -- const localSend = await this.sendService.get(notification.id); -- if ((!isEdit && localSend == null) || -- (isEdit && localSend != null && localSend.revisionDate < notification.revisionDate)) { -- const remoteSend = await this.apiService.getSend(notification.id); -- if (remoteSend != null) { -- const userId = await this.userService.getUserId(); -- await this.sendService.upsert(new SendData(remoteSend, userId)); -- this.messagingService.send('syncedUpsertedSend', { sendId: notification.id }); -- return this.syncCompleted(true); -- } -- } -- } catch (e) { -- this.logService.error(e); -- } -- } -- return this.syncCompleted(false); -+ return this.syncCompleted(false); -+ } -+ -+ async syncDeleteSend(notification: SyncSendNotification): Promise { -+ this.syncStarted(); -+ if (await this.stateService.getIsAuthenticated()) { -+ await this.sendService.delete(notification.id); -+ this.messagingService.send("syncedDeletedSend", { sendId: notification.id }); -+ this.syncCompleted(true); -+ return true; - } -+ return this.syncCompleted(false); -+ } - -- async syncDeleteSend(notification: SyncSendNotification): Promise { -- this.syncStarted(); -- if (await this.userService.isAuthenticated()) { -- await this.sendService.delete(notification.id); -- this.messagingService.send('syncedDeletedSend', { sendId: notification.id }); -- this.syncCompleted(true); -- return true; -- } -- return this.syncCompleted(false); -- } -+ // Helpers - -- // Helpers -+ private syncStarted() { -+ this.syncInProgress = true; -+ this.messagingService.send("syncStarted"); -+ } - -- private syncStarted() { -- this.syncInProgress = true; -- this.messagingService.send('syncStarted'); -- } -+ private syncCompleted(successfully: boolean): boolean { -+ this.syncInProgress = false; -+ this.messagingService.send("syncCompleted", { successfully: successfully }); -+ return successfully; -+ } - -- private syncCompleted(successfully: boolean): boolean { -- this.syncInProgress = false; -- this.messagingService.send('syncCompleted', { successfully: successfully }); -- return successfully; -+ private async needsSyncing(forceSync: boolean) { -+ if (forceSync) { -+ return true; - } - -- private async needsSyncing(forceSync: boolean) { -- if (forceSync) { -- return true; -- } -- -- const lastSync = await this.getLastSync(); -- if (lastSync == null || lastSync.getTime() === 0) { -- return true; -- } -- -- const response = await this.apiService.getAccountRevisionDate(); -- if (new Date(response) <= lastSync) { -- return false; -- } -- return true; -+ const lastSync = await this.getLastSync(); -+ if (lastSync == null || lastSync.getTime() === 0) { -+ return true; - } - -- private async syncProfile(response: ProfileResponse) { -- const stamp = await this.userService.getSecurityStamp(); -- if (stamp != null && stamp !== response.securityStamp) { -- if (this.logoutCallback != null) { -- await this.logoutCallback(true); -- } -- -- throw new Error('Stamp has changed'); -- } -- -- await this.cryptoService.setEncKey(response.key); -- await this.cryptoService.setEncPrivateKey(response.privateKey); -- await this.cryptoService.setProviderKeys(response.providers); -- await this.cryptoService.setOrgKeys(response.organizations, response.providerOrganizations); -- await this.userService.setSecurityStamp(response.securityStamp); -- await this.userService.setEmailVerified(response.emailVerified); -- await this.userService.setForcePasswordReset(response.forcePasswordReset); -- await this.keyConnectorService.setUsesKeyConnector(response.usesKeyConnector); -- -- const organizations: { [id: string]: OrganizationData; } = {}; -- response.organizations.forEach(o => { -- organizations[o.id] = new OrganizationData(o); -- }); -- -- const providers: { [id: string]: ProviderData; } = {}; -- response.providers.forEach(p => { -- providers[p.id] = new ProviderData(p); -- }); -- -- response.providerOrganizations.forEach(o => { -- if (organizations[o.id] == null) { -- organizations[o.id] = new OrganizationData(o); -- organizations[o.id].isProviderUser = true; -- } -- }); -- -- await Promise.all([ -- this.userService.replaceOrganizations(organizations), -- this.userService.replaceProviders(providers), -- ]); -- -- if (await this.keyConnectorService.userNeedsMigration()) { -- this.messagingService.send('convertAccountToKeyConnector'); -- } else { -- this.keyConnectorService.removeConvertAccountRequired(); -- } -+ const response = await this.apiService.getAccountRevisionDate(); -+ if (new Date(response) <= lastSync) { -+ return false; - } -+ return true; -+ } - -- private async syncFolders(userId: string, response: FolderResponse[]) { -- const folders: { [id: string]: FolderData; } = {}; -- response.forEach(f => { -- folders[f.id] = new FolderData(f, userId); -- }); -- return await this.folderService.replace(folders); -- } -+ private async syncProfile(response: ProfileResponse) { -+ const stamp = await this.stateService.getSecurityStamp(); -+ if (stamp != null && stamp !== response.securityStamp) { -+ if (this.logoutCallback != null) { -+ await this.logoutCallback(true); -+ } - -- private async syncCollections(response: CollectionDetailsResponse[]) { -- const collections: { [id: string]: CollectionData; } = {}; -- response.forEach(c => { -- collections[c.id] = new CollectionData(c); -- }); -- return await this.collectionService.replace(collections); -+ throw new Error("Stamp has changed"); - } - -- private async syncCiphers(userId: string, response: CipherResponse[]) { -- const ciphers: { [id: string]: CipherData; } = {}; -- response.forEach(c => { -- ciphers[c.id] = new CipherData(c, userId); -- }); -- return await this.cipherService.replace(ciphers); -+ await this.cryptoService.setEncKey(response.key); -+ await this.cryptoService.setEncPrivateKey(response.privateKey); -+ await this.cryptoService.setProviderKeys(response.providers); -+ await this.cryptoService.setOrgKeys(response.organizations, response.providerOrganizations); -+ await this.stateService.setSecurityStamp(response.securityStamp); -+ await this.stateService.setEmailVerified(response.emailVerified); -+ await this.stateService.setForcePasswordReset(response.forcePasswordReset); -+ await this.keyConnectorService.setUsesKeyConnector(response.usesKeyConnector); -+ -+ const organizations: { [id: string]: OrganizationData } = {}; -+ response.organizations.forEach((o) => { -+ organizations[o.id] = new OrganizationData(o); -+ }); -+ -+ const providers: { [id: string]: ProviderData } = {}; -+ response.providers.forEach((p) => { -+ providers[p.id] = new ProviderData(p); -+ }); -+ -+ response.providerOrganizations.forEach((o) => { -+ if (organizations[o.id] == null) { -+ organizations[o.id] = new OrganizationData(o); -+ organizations[o.id].isProviderUser = true; -+ } -+ }); -+ -+ await Promise.all([ -+ this.organizationService.save(organizations), -+ this.providerService.save(providers), -+ ]); -+ -+ if (await this.keyConnectorService.userNeedsMigration()) { -+ await this.keyConnectorService.setConvertAccountRequired(true); -+ this.messagingService.send("convertAccountToKeyConnector"); -+ } else { -+ this.keyConnectorService.removeConvertAccountRequired(); - } -- -- private async syncSends(userId: string, response: SendResponse[]) { -- const sends: { [id: string]: SendData; } = {}; -- response.forEach(s => { -- sends[s.id] = new SendData(s, userId); -- }); -- return await this.sendService.replace(sends); -+ } -+ -+ private async syncFolders(userId: string, response: FolderResponse[]) { -+ const folders: { [id: string]: FolderData } = {}; -+ response.forEach((f) => { -+ folders[f.id] = new FolderData(f, userId); -+ }); -+ return await this.folderService.replace(folders); -+ } -+ -+ private async syncCollections(response: CollectionDetailsResponse[]) { -+ const collections: { [id: string]: CollectionData } = {}; -+ response.forEach((c) => { -+ collections[c.id] = new CollectionData(c); -+ }); -+ return await this.collectionService.replace(collections); -+ } -+ -+ private async syncCiphers(userId: string, response: CipherResponse[]) { -+ const ciphers: { [id: string]: CipherData } = {}; -+ response.forEach((c) => { -+ ciphers[c.id] = new CipherData(c, userId); -+ }); -+ return await this.cipherService.replace(ciphers); -+ } -+ -+ private async syncSends(userId: string, response: SendResponse[]) { -+ const sends: { [id: string]: SendData } = {}; -+ response.forEach((s) => { -+ sends[s.id] = new SendData(s, userId); -+ }); -+ return await this.sendService.replace(sends); -+ } -+ -+ private async syncSettings(response: DomainsResponse) { -+ let eqDomains: string[][] = []; -+ if (response != null && response.equivalentDomains != null) { -+ eqDomains = eqDomains.concat(response.equivalentDomains); - } - -- private async syncSettings(userId: string, response: DomainsResponse) { -- let eqDomains: string[][] = []; -- if (response != null && response.equivalentDomains != null) { -- eqDomains = eqDomains.concat(response.equivalentDomains); -- } -- -- if (response != null && response.globalEquivalentDomains != null) { -- response.globalEquivalentDomains.forEach(global => { -- if (global.domains.length > 0) { -- eqDomains.push(global.domains); -- } -- }); -+ if (response != null && response.globalEquivalentDomains != null) { -+ response.globalEquivalentDomains.forEach((global) => { -+ if (global.domains.length > 0) { -+ eqDomains.push(global.domains); - } -- -- return this.settingsService.setEquivalentDomains(eqDomains); -+ }); - } - -- private async syncPolicies(response: PolicyResponse[]) { -- const policies: { [id: string]: PolicyData; } = {}; -- if (response != null) { -- response.forEach(p => { -- policies[p.id] = new PolicyData(p); -- }); -- } -- return await this.policyService.replace(policies); -+ return this.settingsService.setEquivalentDomains(eqDomains); -+ } -+ -+ private async syncPolicies(response: PolicyResponse[]) { -+ const policies: { [id: string]: PolicyData } = {}; -+ if (response != null) { -+ response.forEach((p) => { -+ policies[p.id] = new PolicyData(p); -+ }); - } -+ return await this.policyService.replace(policies); -+ } - } -diff --git a/jslib/common/src/services/system.service.ts b/jslib/common/src/services/system.service.ts -index ada91813..4c84b792 100644 ---- a/jslib/common/src/services/system.service.ts -+++ b/jslib/common/src/services/system.service.ts -@@ -1,89 +1,91 @@ --import { MessagingService } from '../abstractions/messaging.service'; --import { PlatformUtilsService } from '../abstractions/platformUtils.service'; --import { StorageService } from '../abstractions/storage.service'; --import { SystemService as SystemServiceAbstraction } from '../abstractions/system.service'; --import { VaultTimeoutService } from '../abstractions/vaultTimeout.service'; -+import { MessagingService } from "../abstractions/messaging.service"; -+import { PlatformUtilsService } from "../abstractions/platformUtils.service"; -+import { StateService } from "../abstractions/state.service"; -+import { SystemService as SystemServiceAbstraction } from "../abstractions/system.service"; - --import { ConstantsService } from './constants.service'; -- --import { Utils } from '../misc/utils'; -+import { Utils } from "../misc/utils"; - - export class SystemService implements SystemServiceAbstraction { -- private reloadInterval: any = null; -- private clearClipboardTimeout: any = null; -- private clearClipboardTimeoutFunction: () => Promise = null; -+ private reloadInterval: any = null; -+ private clearClipboardTimeout: any = null; -+ private clearClipboardTimeoutFunction: () => Promise = null; - -- constructor(private storageService: StorageService, private vaultTimeoutService: VaultTimeoutService, -- private messagingService: MessagingService, private platformUtilsService: PlatformUtilsService, -- private reloadCallback: () => Promise = null) { -- } -+ constructor( -+ private messagingService: MessagingService, -+ private platformUtilsService: PlatformUtilsService, -+ private reloadCallback: () => Promise = null, -+ private stateService: StateService -+ ) {} - -- startProcessReload(): void { -- if (this.vaultTimeoutService.pinProtectedKey != null || -- this.vaultTimeoutService.biometricLocked || -- this.reloadInterval != null) { -- return; -- } -- this.cancelProcessReload(); -- this.reloadInterval = setInterval(async () => { -- let doRefresh = false; -- const lastActive = await this.storageService.get(ConstantsService.lastActiveKey); -- if (lastActive != null) { -- const diffSeconds = (new Date()).getTime() - lastActive; -- // Don't refresh if they are still active in the window -- doRefresh = diffSeconds >= 5000; -- } -- const biometricLockedFingerprintValidated = -- await this.storageService.get(ConstantsService.biometricFingerprintValidated) && this.vaultTimeoutService.biometricLocked; -- if (doRefresh && !biometricLockedFingerprintValidated) { -- clearInterval(this.reloadInterval); -- this.reloadInterval = null; -- this.messagingService.send('reloadProcess'); -- if (this.reloadCallback != null) { -- await this.reloadCallback(); -- } -- } -- }, 10000); -+ async startProcessReload(): Promise { -+ if ( -+ (await this.stateService.getDecryptedPinProtected()) != null || -+ (await this.stateService.getBiometricLocked()) || -+ this.reloadInterval != null -+ ) { -+ return; - } -- -- cancelProcessReload(): void { -- if (this.reloadInterval != null) { -- clearInterval(this.reloadInterval); -- this.reloadInterval = null; -+ this.cancelProcessReload(); -+ this.reloadInterval = setInterval(async () => { -+ let doRefresh = false; -+ const lastActive = await this.stateService.getLastActive(); -+ if (lastActive != null) { -+ const diffSeconds = new Date().getTime() - lastActive; -+ // Don't refresh if they are still active in the window -+ doRefresh = diffSeconds >= 5000; -+ } -+ const biometricLockedFingerprintValidated = -+ (await this.stateService.getBiometricFingerprintValidated()) && -+ (await this.stateService.getBiometricLocked()); -+ if (doRefresh && !biometricLockedFingerprintValidated) { -+ clearInterval(this.reloadInterval); -+ this.reloadInterval = null; -+ this.messagingService.send("reloadProcess"); -+ if (this.reloadCallback != null) { -+ await this.reloadCallback(); - } -- } -+ } -+ }, 10000); -+ } - -- clearClipboard(clipboardValue: string, timeoutMs: number = null): void { -- if (this.clearClipboardTimeout != null) { -- clearTimeout(this.clearClipboardTimeout); -- this.clearClipboardTimeout = null; -- } -- if (Utils.isNullOrWhitespace(clipboardValue)) { -- return; -- } -- this.storageService.get(ConstantsService.clearClipboardKey).then(clearSeconds => { -- if (clearSeconds == null) { -- return; -- } -- if (timeoutMs == null) { -- timeoutMs = clearSeconds * 1000; -- } -- this.clearClipboardTimeoutFunction = async () => { -- const clipboardValueNow = await this.platformUtilsService.readFromClipboard(); -- if (clipboardValue === clipboardValueNow) { -- this.platformUtilsService.copyToClipboard('', { clearing: true }); -- } -- }; -- this.clearClipboardTimeout = setTimeout(async () => { -- await this.clearPendingClipboard(); -- }, timeoutMs); -- }); -+ cancelProcessReload(): void { -+ if (this.reloadInterval != null) { -+ clearInterval(this.reloadInterval); -+ this.reloadInterval = null; - } -+ } - -- async clearPendingClipboard() { -- if (this.clearClipboardTimeoutFunction != null) { -- await this.clearClipboardTimeoutFunction(); -- this.clearClipboardTimeoutFunction = null; -+ async clearClipboard(clipboardValue: string, timeoutMs: number = null): Promise { -+ if (this.clearClipboardTimeout != null) { -+ clearTimeout(this.clearClipboardTimeout); -+ this.clearClipboardTimeout = null; -+ } -+ if (Utils.isNullOrWhitespace(clipboardValue)) { -+ return; -+ } -+ await this.stateService.getClearClipboard().then((clearSeconds) => { -+ if (clearSeconds == null) { -+ return; -+ } -+ if (timeoutMs == null) { -+ timeoutMs = clearSeconds * 1000; -+ } -+ this.clearClipboardTimeoutFunction = async () => { -+ const clipboardValueNow = await this.platformUtilsService.readFromClipboard(); -+ if (clipboardValue === clipboardValueNow) { -+ this.platformUtilsService.copyToClipboard("", { clearing: true }); - } -+ }; -+ this.clearClipboardTimeout = setTimeout(async () => { -+ await this.clearPendingClipboard(); -+ }, timeoutMs); -+ }); -+ } -+ -+ async clearPendingClipboard() { -+ if (this.clearClipboardTimeoutFunction != null) { -+ await this.clearClipboardTimeoutFunction(); -+ this.clearClipboardTimeoutFunction = null; - } -+ } - } -diff --git a/jslib/common/src/services/token.service.ts b/jslib/common/src/services/token.service.ts -index 58f42e7d..bad5ce62 100644 ---- a/jslib/common/src/services/token.service.ts -+++ b/jslib/common/src/services/token.service.ts -@@ -1,269 +1,221 @@ --import { ConstantsService } from './constants.service'; -+import { StateService } from "../abstractions/state.service"; -+import { TokenService as TokenServiceAbstraction } from "../abstractions/token.service"; - --import { StorageService } from '../abstractions/storage.service'; --import { TokenService as TokenServiceAbstraction } from '../abstractions/token.service'; -- --import { Utils } from '../misc/utils'; -- --const Keys = { -- accessToken: 'accessToken', -- refreshToken: 'refreshToken', -- twoFactorTokenPrefix: 'twoFactorToken_', -- clientId: 'apikey_clientId', -- clientSecret: 'apikey_clientSecret', --}; -+import { Utils } from "../misc/utils"; - - export class TokenService implements TokenServiceAbstraction { -- token: string; -- decodedToken: any; -- refreshToken: string; -- clientId: string; -- clientSecret: string; -- -- constructor(private storageService: StorageService) { -+ constructor(private stateService: StateService) {} -+ -+ async setTokens( -+ accessToken: string, -+ refreshToken: string, -+ clientIdClientSecret: [string, string] -+ ): Promise { -+ await this.setToken(accessToken); -+ await this.setRefreshToken(refreshToken); -+ if (clientIdClientSecret != null) { -+ await this.setClientId(clientIdClientSecret[0]); -+ await this.setClientSecret(clientIdClientSecret[1]); - } -+ } - -- async setTokens(accessToken: string, refreshToken: string, clientIdClientSecret: [string, string]): Promise { -- await this.setToken(accessToken); -- await this.setRefreshToken(refreshToken); -- if (clientIdClientSecret != null) { -- await this.setClientId(clientIdClientSecret[0]); -- await this.setClientSecret(clientIdClientSecret[1]); -- } -+ async setClientId(clientId: string): Promise { -+ if ((await this.skipTokenStorage()) || clientId == null) { -+ return; - } -+ return await this.stateService.setApiKeyClientId(clientId); -+ } - -- async setClientId(clientId: string): Promise { -- this.clientId = clientId; -- return this.storeTokenValue(Keys.clientId, clientId); -- } -- -- async getClientId(): Promise { -- if (this.clientId != null) { -- return this.clientId; -- } -+ async getClientId(): Promise { -+ return await this.stateService.getApiKeyClientId(); -+ } - -- this.clientId = await this.storageService.get(Keys.clientId); -- return this.clientId; -+ async setClientSecret(clientSecret: string): Promise { -+ if ((await this.skipTokenStorage()) || clientSecret == null) { -+ return; - } -+ return await this.stateService.setApiKeyClientSecret(clientSecret); -+ } - -- async setClientSecret(clientSecret: string): Promise { -- this.clientSecret = clientSecret; -- return this.storeTokenValue(Keys.clientSecret, clientSecret); -- } -+ async getClientSecret(): Promise { -+ return await this.stateService.getApiKeyClientSecret(); -+ } - -- async getClientSecret(): Promise { -- if (this.clientSecret != null) { -- return this.clientSecret; -- } -+ async setToken(token: string): Promise { -+ await this.stateService.setAccessToken(token); -+ } - -- this.clientSecret = await this.storageService.get(Keys.clientSecret); -- return this.clientSecret; -- } -+ async getToken(): Promise { -+ return await this.stateService.getAccessToken(); -+ } - -- async setToken(token: string): Promise { -- this.token = token; -- this.decodedToken = null; -- return this.storeTokenValue(Keys.accessToken, token); -+ async setRefreshToken(refreshToken: string): Promise { -+ if (await this.skipTokenStorage()) { -+ return; - } -+ return await this.stateService.setRefreshToken(refreshToken); -+ } - -- async getToken(): Promise { -- if (this.token != null) { -- return this.token; -- } -+ async getRefreshToken(): Promise { -+ return await this.stateService.getRefreshToken(); -+ } - -- this.token = await this.storageService.get(Keys.accessToken); -- return this.token; -- } -+ async toggleTokens(): Promise { -+ const token = await this.getToken(); -+ const refreshToken = await this.getRefreshToken(); -+ const clientId = await this.getClientId(); -+ const clientSecret = await this.getClientSecret(); -+ const timeout = await this.stateService.getVaultTimeout(); -+ const action = await this.stateService.getVaultTimeoutAction(); - -- async setRefreshToken(refreshToken: string): Promise { -- this.refreshToken = refreshToken; -- return this.storeTokenValue(Keys.refreshToken, refreshToken); -+ if ((timeout != null || timeout === 0) && action === "logOut") { -+ // if we have a vault timeout and the action is log out, reset tokens -+ await this.clearToken(); - } - -- async getRefreshToken(): Promise { -- if (this.refreshToken != null) { -- return this.refreshToken; -- } -+ await this.setToken(token); -+ await this.setRefreshToken(refreshToken); -+ await this.setClientId(clientId); -+ await this.setClientSecret(clientSecret); -+ } - -- this.refreshToken = await this.storageService.get(Keys.refreshToken); -- return this.refreshToken; -- } -+ async setTwoFactorToken(token: string): Promise { -+ return await this.stateService.setTwoFactorToken(token); -+ } - -- async toggleTokens(): Promise { -- const token = await this.getToken(); -- const refreshToken = await this.getRefreshToken(); -- const clientId = await this.getClientId(); -- const clientSecret = await this.getClientSecret(); -- const timeout = await this.storageService.get(ConstantsService.vaultTimeoutKey); -- const action = await this.storageService.get(ConstantsService.vaultTimeoutActionKey); -- if ((timeout != null || timeout === 0) && action === 'logOut') { -- // if we have a vault timeout and the action is log out, reset tokens -- await this.clearToken(); -- this.token = token; -- this.refreshToken = refreshToken; -- this.clientId = clientId; -- this.clientSecret = clientSecret; -- return; -- } -- -- await this.setToken(token); -- await this.setRefreshToken(refreshToken); -- await this.setClientId(clientId); -- await this.setClientSecret(clientSecret); -- } -+ async getTwoFactorToken(): Promise { -+ return await this.stateService.getTwoFactorToken(); -+ } - -- setTwoFactorToken(token: string, email: string): Promise { -- return this.storageService.save(Keys.twoFactorTokenPrefix + email, token); -- } -+ async clearTwoFactorToken(): Promise { -+ return await this.stateService.setTwoFactorToken(null); -+ } - -- getTwoFactorToken(email: string): Promise { -- return this.storageService.get(Keys.twoFactorTokenPrefix + email); -- } -+ async clearToken(userId?: string): Promise { -+ await this.stateService.setAccessToken(null, { userId: userId }); -+ await this.stateService.setRefreshToken(null, { userId: userId }); -+ await this.stateService.setApiKeyClientId(null, { userId: userId }); -+ await this.stateService.setApiKeyClientSecret(null, { userId: userId }); -+ } - -- clearTwoFactorToken(email: string): Promise { -- return this.storageService.remove(Keys.twoFactorTokenPrefix + email); -- } -+ // jwthelper methods -+ // ref https://github.com/auth0/angular-jwt/blob/master/src/angularJwt/services/jwt.js - -- async clearToken(): Promise { -- this.token = null; -- this.decodedToken = null; -- this.refreshToken = null; -- this.clientId = null; -- this.clientSecret = null; -- -- await this.storageService.remove(Keys.accessToken); -- await this.storageService.remove(Keys.refreshToken); -- await this.storageService.remove(Keys.clientId); -- await this.storageService.remove(Keys.clientSecret); -+ async decodeToken(token?: string): Promise { -+ const storedToken = await this.stateService.getDecodedToken(); -+ if (token === null && storedToken != null) { -+ return storedToken; - } - -- // jwthelper methods -- // ref https://github.com/auth0/angular-jwt/blob/master/src/angularJwt/services/jwt.js -- -- decodeToken(): any { -- if (this.decodedToken) { -- return this.decodedToken; -- } -+ token = token ?? (await this.stateService.getAccessToken()); - -- if (this.token == null) { -- throw new Error('Token not found.'); -- } -- -- const parts = this.token.split('.'); -- if (parts.length !== 3) { -- throw new Error('JWT must have 3 parts'); -- } -+ if (token == null) { -+ throw new Error("Token not found."); -+ } - -- const decoded = Utils.fromUrlB64ToUtf8(parts[1]); -- if (decoded == null) { -- throw new Error('Cannot decode the token'); -- } -+ const parts = token.split("."); -+ if (parts.length !== 3) { -+ throw new Error("JWT must have 3 parts"); -+ } - -- this.decodedToken = JSON.parse(decoded); -- return this.decodedToken; -+ const decoded = Utils.fromUrlB64ToUtf8(parts[1]); -+ if (decoded == null) { -+ throw new Error("Cannot decode the token"); - } - -- getTokenExpirationDate(): Date { -- const decoded = this.decodeToken(); -- if (typeof decoded.exp === 'undefined') { -- return null; -- } -+ const decodedToken = JSON.parse(decoded); -+ return decodedToken; -+ } - -- const d = new Date(0); // The 0 here is the key, which sets the date to the epoch -- d.setUTCSeconds(decoded.exp); -- return d; -+ async getTokenExpirationDate(): Promise { -+ const decoded = await this.decodeToken(); -+ if (typeof decoded.exp === "undefined") { -+ return null; - } - -- tokenSecondsRemaining(offsetSeconds: number = 0): number { -- const d = this.getTokenExpirationDate(); -- if (d == null) { -- return 0; -- } -+ const d = new Date(0); // The 0 here is the key, which sets the date to the epoch -+ d.setUTCSeconds(decoded.exp); -+ return d; -+ } - -- const msRemaining = d.valueOf() - (new Date().valueOf() + (offsetSeconds * 1000)); -- return Math.round(msRemaining / 1000); -+ async tokenSecondsRemaining(offsetSeconds: number = 0): Promise { -+ const d = await this.getTokenExpirationDate(); -+ if (d == null) { -+ return 0; - } - -- tokenNeedsRefresh(minutes: number = 5): boolean { -- const sRemaining = this.tokenSecondsRemaining(); -- return sRemaining < (60 * minutes); -- } -+ const msRemaining = d.valueOf() - (new Date().valueOf() + offsetSeconds * 1000); -+ return Math.round(msRemaining / 1000); -+ } - -- getUserId(): string { -- const decoded = this.decodeToken(); -- if (typeof decoded.sub === 'undefined') { -- throw new Error('No user id found'); -- } -+ async tokenNeedsRefresh(minutes: number = 5): Promise { -+ const sRemaining = await this.tokenSecondsRemaining(); -+ return sRemaining < 60 * minutes; -+ } - -- return decoded.sub as string; -+ async getUserId(): Promise { -+ const decoded = await this.decodeToken(); -+ if (typeof decoded.sub === "undefined") { -+ throw new Error("No user id found"); - } - -- getEmail(): string { -- const decoded = this.decodeToken(); -- if (typeof decoded.email === 'undefined') { -- throw new Error('No email found'); -- } -+ return decoded.sub as string; -+ } - -- return decoded.email as string; -+ async getEmail(): Promise { -+ const decoded = await this.decodeToken(); -+ if (typeof decoded.email === "undefined") { -+ throw new Error("No email found"); - } - -- getEmailVerified(): boolean { -- const decoded = this.decodeToken(); -- if (typeof decoded.email_verified === 'undefined') { -- throw new Error('No email verification found'); -- } -+ return decoded.email as string; -+ } - -- return decoded.email_verified as boolean; -+ async getEmailVerified(): Promise { -+ const decoded = await this.decodeToken(); -+ if (typeof decoded.email_verified === "undefined") { -+ throw new Error("No email verification found"); - } - -- getName(): string { -- const decoded = this.decodeToken(); -- if (typeof decoded.name === 'undefined') { -- return null; -- } -+ return decoded.email_verified as boolean; -+ } - -- return decoded.name as string; -+ async getName(): Promise { -+ const decoded = await this.decodeToken(); -+ if (typeof decoded.name === "undefined") { -+ return null; - } - -- getPremium(): boolean { -- const decoded = this.decodeToken(); -- if (typeof decoded.premium === 'undefined') { -- return false; -- } -+ return decoded.name as string; -+ } - -- return decoded.premium as boolean; -+ async getPremium(): Promise { -+ const decoded = await this.decodeToken(); -+ if (typeof decoded.premium === "undefined") { -+ return false; - } - -- getIssuer(): string { -- const decoded = this.decodeToken(); -- if (typeof decoded.iss === 'undefined') { -- throw new Error('No issuer found'); -- } -+ return decoded.premium as boolean; -+ } - -- return decoded.iss as string; -+ async getIssuer(): Promise { -+ const decoded = await this.decodeToken(); -+ if (typeof decoded.iss === "undefined") { -+ throw new Error("No issuer found"); - } - -- getIsExternal(): boolean { -- const decoded = this.decodeToken(); -- if (!Array.isArray(decoded.amr)) { -- throw new Error('No amr found'); -- } -+ return decoded.iss as string; -+ } - -- return decoded.amr.includes('external'); -- } -+ async getIsExternal(): Promise { -+ const decoded = await this.decodeToken(); - -- private async storeTokenValue(key: string, value: string) { -- if (await this.skipTokenStorage()) { -- // if we have a vault timeout and the action is log out, don't store token -- return; -- } -+ return Array.isArray(decoded.amr) && decoded.amr.includes("external"); -+ } - -- return this.storageService.save(key, value); -- } -- -- private async skipTokenStorage(): Promise { -- const timeout = await this.storageService.get(ConstantsService.vaultTimeoutKey); -- const action = await this.storageService.get(ConstantsService.vaultTimeoutActionKey); -- return timeout != null && action === 'logOut'; -- } -+ private async skipTokenStorage(): Promise { -+ const timeout = await this.stateService.getVaultTimeout(); -+ const action = await this.stateService.getVaultTimeoutAction(); -+ return timeout != null && action === "logOut"; -+ } - } -diff --git a/jslib/common/src/services/totp.service.ts b/jslib/common/src/services/totp.service.ts -index 50c8c4eb..e3d653c0 100644 ---- a/jslib/common/src/services/totp.service.ts -+++ b/jslib/common/src/services/totp.service.ts -@@ -1,170 +1,178 @@ --import { ConstantsService } from './constants.service'; -+import { CryptoFunctionService } from "../abstractions/cryptoFunction.service"; -+import { LogService } from "../abstractions/log.service"; -+import { StateService } from "../abstractions/state.service"; -+import { TotpService as TotpServiceAbstraction } from "../abstractions/totp.service"; - --import { CryptoFunctionService } from '../abstractions/cryptoFunction.service'; --import { LogService } from '../abstractions/log.service'; --import { StorageService } from '../abstractions/storage.service'; --import { TotpService as TotpServiceAbstraction } from '../abstractions/totp.service'; -+import { Utils } from "../misc/utils"; - --import { Utils } from '../misc/utils'; -- --const B32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; --const SteamChars = '23456789BCDFGHJKMNPQRTVWXY'; -+const B32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; -+const SteamChars = "23456789BCDFGHJKMNPQRTVWXY"; - - export class TotpService implements TotpServiceAbstraction { -- constructor(private storageService: StorageService, private cryptoFunctionService: CryptoFunctionService, -- private logService: LogService) { } -- -- async getCode(key: string): Promise { -- if (key == null) { -- return null; -- } -- let period = 30; -- let alg: 'sha1' | 'sha256' | 'sha512' = 'sha1'; -- let digits = 6; -- let keyB32 = key; -- const isOtpAuth = key.toLowerCase().indexOf('otpauth://') === 0; -- const isSteamAuth = !isOtpAuth && key.toLowerCase().indexOf('steam://') === 0; -- if (isOtpAuth) { -- const params = Utils.getQueryParams(key); -- if (params.has('digits') && params.get('digits') != null) { -- try { -- const digitParams = parseInt(params.get('digits').trim(), null); -- if (digitParams > 10) { -- digits = 10; -- } else if (digitParams > 0) { -- digits = digitParams; -- } -- } catch { -- this.logService.error('Invalid digits param.'); -- } -- } -- if (params.has('period') && params.get('period') != null) { -- try { -- const periodParam = parseInt(params.get('period').trim(), null); -- if (periodParam > 0) { -- period = periodParam; -- } -- } catch { -- this.logService.error('Invalid period param.'); -- } -- } -- if (params.has('secret') && params.get('secret') != null) { -- keyB32 = params.get('secret'); -- } -- if (params.has('algorithm') && params.get('algorithm') != null) { -- const algParam = params.get('algorithm').toLowerCase(); -- if (algParam === 'sha1' || algParam === 'sha256' || algParam === 'sha512') { -- alg = algParam; -- } -- } -- } else if (isSteamAuth) { -- keyB32 = key.substr('steam://'.length); -- digits = 5; -+ constructor( -+ private cryptoFunctionService: CryptoFunctionService, -+ private logService: LogService, -+ private stateService: StateService -+ ) {} -+ -+ async getCode(key: string): Promise { -+ if (key == null) { -+ return null; -+ } -+ let period = 30; -+ let alg: "sha1" | "sha256" | "sha512" = "sha1"; -+ let digits = 6; -+ let keyB32 = key; -+ const isOtpAuth = key.toLowerCase().indexOf("otpauth://") === 0; -+ const isSteamAuth = !isOtpAuth && key.toLowerCase().indexOf("steam://") === 0; -+ if (isOtpAuth) { -+ const params = Utils.getQueryParams(key); -+ if (params.has("digits") && params.get("digits") != null) { -+ try { -+ const digitParams = parseInt(params.get("digits").trim(), null); -+ if (digitParams > 10) { -+ digits = 10; -+ } else if (digitParams > 0) { -+ digits = digitParams; -+ } -+ } catch { -+ this.logService.error("Invalid digits param."); - } -- -- const epoch = Math.round(new Date().getTime() / 1000.0); -- const timeHex = this.leftPad(this.decToHex(Math.floor(epoch / period)), 16, '0'); -- const timeBytes = Utils.fromHexToArray(timeHex); -- const keyBytes = this.b32ToBytes(keyB32); -- -- if (!keyBytes.length || !timeBytes.length) { -- return null; -+ } -+ if (params.has("period") && params.get("period") != null) { -+ try { -+ const periodParam = parseInt(params.get("period").trim(), null); -+ if (periodParam > 0) { -+ period = periodParam; -+ } -+ } catch { -+ this.logService.error("Invalid period param."); - } -- -- const hash = await this.sign(keyBytes, timeBytes, alg); -- if (hash.length === 0) { -- return null; -+ } -+ if (params.has("secret") && params.get("secret") != null) { -+ keyB32 = params.get("secret"); -+ } -+ if (params.has("algorithm") && params.get("algorithm") != null) { -+ const algParam = params.get("algorithm").toLowerCase(); -+ if (algParam === "sha1" || algParam === "sha256" || algParam === "sha512") { -+ alg = algParam; - } -+ } -+ } else if (isSteamAuth) { -+ keyB32 = key.substr("steam://".length); -+ digits = 5; -+ } - -- /* tslint:disable */ -- const offset = (hash[hash.length - 1] & 0xf); -- const binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16) | -- ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff); -- /* tslint:enable */ -- -- let otp = ''; -- if (isSteamAuth) { -- // tslint:disable-next-line -- let fullCode = binary & 0x7fffffff; -- for (let i = 0; i < digits; i++) { -- otp += SteamChars[fullCode % SteamChars.length]; -- fullCode = Math.trunc(fullCode / SteamChars.length); -- } -- } else { -- otp = (binary % Math.pow(10, digits)).toString(); -- otp = this.leftPad(otp, digits, '0'); -- } -+ const epoch = Math.round(new Date().getTime() / 1000.0); -+ const timeHex = this.leftPad(this.decToHex(Math.floor(epoch / period)), 16, "0"); -+ const timeBytes = Utils.fromHexToArray(timeHex); -+ const keyBytes = this.b32ToBytes(keyB32); - -- return otp; -+ if (!keyBytes.length || !timeBytes.length) { -+ return null; - } - -- getTimeInterval(key: string): number { -- let period = 30; -- if (key != null && key.toLowerCase().indexOf('otpauth://') === 0) { -- const params = Utils.getQueryParams(key); -- if (params.has('period') && params.get('period') != null) { -- try { -- period = parseInt(params.get('period').trim(), null); -- } catch { -- this.logService.error('Invalid period param.'); -- } -- } -- } -- return period; -+ const hash = await this.sign(keyBytes, timeBytes, alg); -+ if (hash.length === 0) { -+ return null; - } - -- async isAutoCopyEnabled(): Promise { -- return !(await this.storageService.get(ConstantsService.disableAutoTotpCopyKey)); -+ /* tslint:disable */ -+ const offset = hash[hash.length - 1] & 0xf; -+ const binary = -+ ((hash[offset] & 0x7f) << 24) | -+ ((hash[offset + 1] & 0xff) << 16) | -+ ((hash[offset + 2] & 0xff) << 8) | -+ (hash[offset + 3] & 0xff); -+ /* tslint:enable */ -+ -+ let otp = ""; -+ if (isSteamAuth) { -+ // tslint:disable-next-line -+ let fullCode = binary & 0x7fffffff; -+ for (let i = 0; i < digits; i++) { -+ otp += SteamChars[fullCode % SteamChars.length]; -+ fullCode = Math.trunc(fullCode / SteamChars.length); -+ } -+ } else { -+ otp = (binary % Math.pow(10, digits)).toString(); -+ otp = this.leftPad(otp, digits, "0"); - } - -- // Helpers -- -- private leftPad(s: string, l: number, p: string): string { -- if (l + 1 >= s.length) { -- s = Array(l + 1 - s.length).join(p) + s; -+ return otp; -+ } -+ -+ getTimeInterval(key: string): number { -+ let period = 30; -+ if (key != null && key.toLowerCase().indexOf("otpauth://") === 0) { -+ const params = Utils.getQueryParams(key); -+ if (params.has("period") && params.get("period") != null) { -+ try { -+ period = parseInt(params.get("period").trim(), null); -+ } catch { -+ this.logService.error("Invalid period param."); - } -- return s; -+ } - } -+ return period; -+ } -+ -+ async isAutoCopyEnabled(): Promise { -+ return !(await this.stateService.getDisableAutoTotpCopy()); -+ } -+ -+ // Helpers - -- private decToHex(d: number): string { -- return (d < 15.5 ? '0' : '') + Math.round(d).toString(16); -+ private leftPad(s: string, l: number, p: string): string { -+ if (l + 1 >= s.length) { -+ s = Array(l + 1 - s.length).join(p) + s; - } -+ return s; -+ } - -- private b32ToHex(s: string): string { -- s = s.toUpperCase(); -- let cleanedInput = ''; -+ private decToHex(d: number): string { -+ return (d < 15.5 ? "0" : "") + Math.round(d).toString(16); -+ } - -- for (let i = 0; i < s.length; i++) { -- if (B32Chars.indexOf(s[i]) < 0) { -- continue; -- } -+ private b32ToHex(s: string): string { -+ s = s.toUpperCase(); -+ let cleanedInput = ""; - -- cleanedInput += s[i]; -- } -- s = cleanedInput; -- -- let bits = ''; -- let hex = ''; -- for (let i = 0; i < s.length; i++) { -- const byteIndex = B32Chars.indexOf(s.charAt(i)); -- if (byteIndex < 0) { -- continue; -- } -- bits += this.leftPad(byteIndex.toString(2), 5, '0'); -- } -- for (let i = 0; i + 4 <= bits.length; i += 4) { -- const chunk = bits.substr(i, 4); -- hex = hex + parseInt(chunk, 2).toString(16); -- } -- return hex; -- } -+ for (let i = 0; i < s.length; i++) { -+ if (B32Chars.indexOf(s[i]) < 0) { -+ continue; -+ } - -- private b32ToBytes(s: string): Uint8Array { -- return Utils.fromHexToArray(this.b32ToHex(s)); -+ cleanedInput += s[i]; - } -- -- private async sign(keyBytes: Uint8Array, timeBytes: Uint8Array, alg: 'sha1' | 'sha256' | 'sha512') { -- const signature = await this.cryptoFunctionService.hmac(timeBytes.buffer, keyBytes.buffer, alg); -- return new Uint8Array(signature); -+ s = cleanedInput; -+ -+ let bits = ""; -+ let hex = ""; -+ for (let i = 0; i < s.length; i++) { -+ const byteIndex = B32Chars.indexOf(s.charAt(i)); -+ if (byteIndex < 0) { -+ continue; -+ } -+ bits += this.leftPad(byteIndex.toString(2), 5, "0"); -+ } -+ for (let i = 0; i + 4 <= bits.length; i += 4) { -+ const chunk = bits.substr(i, 4); -+ hex = hex + parseInt(chunk, 2).toString(16); - } -+ return hex; -+ } -+ -+ private b32ToBytes(s: string): Uint8Array { -+ return Utils.fromHexToArray(this.b32ToHex(s)); -+ } -+ -+ private async sign( -+ keyBytes: Uint8Array, -+ timeBytes: Uint8Array, -+ alg: "sha1" | "sha256" | "sha512" -+ ) { -+ const signature = await this.cryptoFunctionService.hmac(timeBytes.buffer, keyBytes.buffer, alg); -+ return new Uint8Array(signature); -+ } - } -diff --git a/jslib/common/src/services/user.service.ts b/jslib/common/src/services/user.service.ts -deleted file mode 100644 -index a572691a..00000000 ---- a/jslib/common/src/services/user.service.ts -+++ /dev/null -@@ -1,238 +0,0 @@ --import { StorageService } from '../abstractions/storage.service'; --import { TokenService } from '../abstractions/token.service'; --import { UserService as UserServiceAbstraction } from '../abstractions/user.service'; -- --import { OrganizationData } from '../models/data/organizationData'; --import { Organization } from '../models/domain/organization'; -- --import { KdfType } from '../enums/kdfType'; -- --import { ProviderData } from '../models/data/providerData'; --import { Provider } from '../models/domain/provider'; -- --const Keys = { -- userId: 'userId', -- userEmail: 'userEmail', -- stamp: 'securityStamp', -- kdf: 'kdf', -- kdfIterations: 'kdfIterations', -- organizationsPrefix: 'organizations_', -- providersPrefix: 'providers_', -- emailVerified: 'emailVerified', -- forcePasswordReset: 'forcePasswordReset', --}; -- --export class UserService implements UserServiceAbstraction { -- private userId: string; -- private email: string; -- private stamp: string; -- private kdf: KdfType; -- private kdfIterations: number; -- private emailVerified: boolean; -- private forcePasswordReset: boolean; -- -- constructor(private tokenService: TokenService, private storageService: StorageService) { } -- -- async setInformation(userId: string, email: string, kdf: KdfType, kdfIterations: number): Promise { -- this.email = email; -- this.userId = userId; -- this.kdf = kdf; -- this.kdfIterations = kdfIterations; -- -- await this.storageService.save(Keys.userEmail, email); -- await this.storageService.save(Keys.userId, userId); -- await this.storageService.save(Keys.kdf, kdf); -- await this.storageService.save(Keys.kdfIterations, kdfIterations); -- } -- -- setSecurityStamp(stamp: string): Promise { -- this.stamp = stamp; -- return this.storageService.save(Keys.stamp, stamp); -- } -- -- setEmailVerified(emailVerified: boolean) { -- this.emailVerified = emailVerified; -- return this.storageService.save(Keys.emailVerified, emailVerified); -- } -- -- setForcePasswordReset(forcePasswordReset: boolean) { -- this.forcePasswordReset = forcePasswordReset; -- return this.storageService.save(Keys.forcePasswordReset, forcePasswordReset); -- } -- -- async getUserId(): Promise { -- if (this.userId == null) { -- this.userId = await this.storageService.get(Keys.userId); -- } -- return this.userId; -- } -- -- async getEmail(): Promise { -- if (this.email == null) { -- this.email = await this.storageService.get(Keys.userEmail); -- } -- return this.email; -- } -- -- async getSecurityStamp(): Promise { -- if (this.stamp == null) { -- this.stamp = await this.storageService.get(Keys.stamp); -- } -- return this.stamp; -- } -- -- async getKdf(): Promise { -- if (this.kdf == null) { -- this.kdf = await this.storageService.get(Keys.kdf); -- } -- return this.kdf; -- } -- -- async getKdfIterations(): Promise { -- if (this.kdfIterations == null) { -- this.kdfIterations = await this.storageService.get(Keys.kdfIterations); -- } -- return this.kdfIterations; -- } -- -- async getEmailVerified(): Promise { -- if (this.emailVerified == null) { -- this.emailVerified = await this.storageService.get(Keys.emailVerified); -- } -- return this.emailVerified; -- } -- -- async getForcePasswordReset(): Promise { -- if (this.forcePasswordReset == null) { -- this.forcePasswordReset = await this.storageService.get(Keys.forcePasswordReset); -- } -- return this.forcePasswordReset; -- } -- -- async clear(): Promise { -- const userId = await this.getUserId(); -- -- await this.storageService.remove(Keys.userId); -- await this.storageService.remove(Keys.userEmail); -- await this.storageService.remove(Keys.stamp); -- await this.storageService.remove(Keys.kdf); -- await this.storageService.remove(Keys.kdfIterations); -- await this.storageService.remove(Keys.forcePasswordReset); -- await this.clearOrganizations(userId); -- await this.clearProviders(userId); -- -- this.userId = this.email = this.stamp = null; -- this.kdf = null; -- this.kdfIterations = null; -- } -- -- async isAuthenticated(): Promise { -- const token = await this.tokenService.getToken(); -- if (token == null) { -- return false; -- } -- -- const userId = await this.getUserId(); -- return userId != null; -- } -- -- async canAccessPremium(): Promise { -- const authed = await this.isAuthenticated(); -- if (!authed) { -- return false; -- } -- -- const tokenPremium = this.tokenService.getPremium(); -- if (tokenPremium) { -- return true; -- } -- -- const orgs = await this.getAllOrganizations(); -- for (let i = 0; i < orgs.length; i++) { -- if (orgs[i].usersGetPremium && orgs[i].enabled) { -- return true; -- } -- } -- return false; -- } -- -- async canManageSponsorships(): Promise { -- const orgs = await this.getAllOrganizations(); -- return orgs.some(o => o.familySponsorshipAvailable || o.familySponsorshipFriendlyName !== null); -- } -- -- async getOrganization(id: string): Promise { -- const userId = await this.getUserId(); -- const organizations = await this.storageService.get<{ [id: string]: OrganizationData; }>( -- Keys.organizationsPrefix + userId); -- if (organizations == null || !organizations.hasOwnProperty(id)) { -- return null; -- } -- -- return new Organization(organizations[id]); -- } -- -- async getOrganizationByIdentifier(identifier: string): Promise { -- const organizations = await this.getAllOrganizations(); -- if (organizations == null || organizations.length === 0) { -- return null; -- } -- -- return organizations.find(o => o.identifier === identifier); -- } -- -- async getAllOrganizations(): Promise { -- const userId = await this.getUserId(); -- const organizations = await this.storageService.get<{ [id: string]: OrganizationData; }>( -- Keys.organizationsPrefix + userId); -- const response: Organization[] = []; -- for (const id in organizations) { -- if (organizations.hasOwnProperty(id) && !organizations[id].isProviderUser) { -- response.push(new Organization(organizations[id])); -- } -- } -- return response; -- } -- -- async replaceOrganizations(organizations: { [id: string]: OrganizationData; }): Promise { -- const userId = await this.getUserId(); -- await this.storageService.save(Keys.organizationsPrefix + userId, organizations); -- } -- -- async clearOrganizations(userId: string): Promise { -- await this.storageService.remove(Keys.organizationsPrefix + userId); -- } -- -- async getProvider(id: string): Promise { -- const userId = await this.getUserId(); -- const providers = await this.storageService.get<{ [id: string]: ProviderData; }>( -- Keys.providersPrefix + userId); -- if (providers == null || !providers.hasOwnProperty(id)) { -- return null; -- } -- -- return new Provider(providers[id]); -- } -- -- async getAllProviders(): Promise { -- const userId = await this.getUserId(); -- const providers = await this.storageService.get<{ [id: string]: ProviderData; }>( -- Keys.providersPrefix + userId); -- const response: Provider[] = []; -- for (const id in providers) { -- if (providers.hasOwnProperty(id)) { -- response.push(new Provider(providers[id])); -- } -- } -- return response; -- } -- -- async replaceProviders(providers: { [id: string]: ProviderData; }): Promise { -- const userId = await this.getUserId(); -- await this.storageService.save(Keys.providersPrefix + userId, providers); -- } -- -- async clearProviders(userId: string): Promise { -- await this.storageService.remove(Keys.providersPrefix + userId); -- } --} -diff --git a/jslib/common/src/services/userVerification.service.ts b/jslib/common/src/services/userVerification.service.ts -index 2b94ffae..f5189a4d 100644 ---- a/jslib/common/src/services/userVerification.service.ts -+++ b/jslib/common/src/services/userVerification.service.ts -@@ -1,69 +1,77 @@ --import { UserVerificationService as UserVerificationServiceAbstraction } from '../abstractions/userVerification.service'; -+import { UserVerificationService as UserVerificationServiceAbstraction } from "../abstractions/userVerification.service"; - --import { ApiService } from '../abstractions/api.service'; --import { CryptoService } from '../abstractions/crypto.service'; --import { I18nService } from '../abstractions/i18n.service'; -+import { ApiService } from "../abstractions/api.service"; -+import { CryptoService } from "../abstractions/crypto.service"; -+import { I18nService } from "../abstractions/i18n.service"; - --import { VerificationType } from '../enums/verificationType'; -+import { VerificationType } from "../enums/verificationType"; - --import { VerifyOTPRequest } from '../models/request/account/verifyOTPRequest'; --import { SecretVerificationRequest } from '../models/request/secretVerificationRequest'; -+import { VerifyOTPRequest } from "../models/request/account/verifyOTPRequest"; -+import { SecretVerificationRequest } from "../models/request/secretVerificationRequest"; - --import { Verification } from '../types/verification'; -+import { Verification } from "../types/verification"; - - export class UserVerificationService implements UserVerificationServiceAbstraction { -- constructor(private cryptoService: CryptoService, private i18nService: I18nService, -- private apiService: ApiService) { } -+ constructor( -+ private cryptoService: CryptoService, -+ private i18nService: I18nService, -+ private apiService: ApiService -+ ) {} - -- async buildRequest(verification: Verification, -- requestClass?: new () => T, alreadyHashed?: boolean) { -- this.validateInput(verification); -+ async buildRequest( -+ verification: Verification, -+ requestClass?: new () => T, -+ alreadyHashed?: boolean -+ ) { -+ this.validateInput(verification); - -- const request = requestClass != null -- ? new requestClass() -- : new SecretVerificationRequest() as T; -+ const request = -+ requestClass != null ? new requestClass() : (new SecretVerificationRequest() as T); - -- if (verification.type === VerificationType.OTP) { -- request.otp = verification.secret; -- } else { -- request.masterPasswordHash = alreadyHashed -- ? verification.secret -- : await this.cryptoService.hashPassword(verification.secret, null); -- } -- -- return request; -+ if (verification.type === VerificationType.OTP) { -+ request.otp = verification.secret; -+ } else { -+ request.masterPasswordHash = alreadyHashed -+ ? verification.secret -+ : await this.cryptoService.hashPassword(verification.secret, null); - } - -- async verifyUser(verification: Verification): Promise { -- this.validateInput(verification); -+ return request; -+ } - -- if (verification.type === VerificationType.OTP) { -- const request = new VerifyOTPRequest(verification.secret); -- try { -- await this.apiService.postAccountVerifyOTP(request); -- } catch (e) { -- throw new Error(this.i18nService.t('invalidVerificationCode')); -- } -- } else { -- const passwordValid = await this.cryptoService.compareAndUpdateKeyHash(verification.secret, null); -- if (!passwordValid) { -- throw new Error(this.i18nService.t('invalidMasterPassword')); -- } -- } -- return true; -- } -+ async verifyUser(verification: Verification): Promise { -+ this.validateInput(verification); - -- async requestOTP() { -- await this.apiService.postAccountRequestOTP(); -+ if (verification.type === VerificationType.OTP) { -+ const request = new VerifyOTPRequest(verification.secret); -+ try { -+ await this.apiService.postAccountVerifyOTP(request); -+ } catch (e) { -+ throw new Error(this.i18nService.t("invalidVerificationCode")); -+ } -+ } else { -+ const passwordValid = await this.cryptoService.compareAndUpdateKeyHash( -+ verification.secret, -+ null -+ ); -+ if (!passwordValid) { -+ throw new Error(this.i18nService.t("invalidMasterPassword")); -+ } - } -+ return true; -+ } -+ -+ async requestOTP() { -+ await this.apiService.postAccountRequestOTP(); -+ } - -- private validateInput(verification: Verification) { -- if (verification?.secret == null || verification.secret === '') { -- if (verification.type === VerificationType.OTP) { -- throw new Error(this.i18nService.t('verificationCodeRequired')); -- } else { -- throw new Error(this.i18nService.t('masterPassRequired')); -- } -- } -+ private validateInput(verification: Verification) { -+ if (verification?.secret == null || verification.secret === "") { -+ if (verification.type === VerificationType.OTP) { -+ throw new Error(this.i18nService.t("verificationCodeRequired")); -+ } else { -+ throw new Error(this.i18nService.t("masterPassRequired")); -+ } - } -+ } - } -diff --git a/jslib/common/src/services/vaultTimeout.service.ts b/jslib/common/src/services/vaultTimeout.service.ts -index 54d288b1..544e6bc2 100644 ---- a/jslib/common/src/services/vaultTimeout.service.ts -+++ b/jslib/common/src/services/vaultTimeout.service.ts -@@ -1,180 +1,203 @@ --import { ConstantsService } from './constants.service'; -- --import { CipherService } from '../abstractions/cipher.service'; --import { CollectionService } from '../abstractions/collection.service'; --import { CryptoService } from '../abstractions/crypto.service'; --import { FolderService } from '../abstractions/folder.service'; --import { KeyConnectorService } from '../abstractions/keyConnector.service'; --import { MessagingService } from '../abstractions/messaging.service'; --import { PlatformUtilsService } from '../abstractions/platformUtils.service'; --import { PolicyService } from '../abstractions/policy.service'; --import { SearchService } from '../abstractions/search.service'; --import { StorageService } from '../abstractions/storage.service'; --import { TokenService } from '../abstractions/token.service'; --import { UserService } from '../abstractions/user.service'; --import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from '../abstractions/vaultTimeout.service'; -- --import { PolicyType } from '../enums/policyType'; --import { EncString } from '../models/domain/encString'; -+import { CipherService } from "../abstractions/cipher.service"; -+import { CollectionService } from "../abstractions/collection.service"; -+import { CryptoService } from "../abstractions/crypto.service"; -+import { FolderService } from "../abstractions/folder.service"; -+import { KeyConnectorService } from "../abstractions/keyConnector.service"; -+import { MessagingService } from "../abstractions/messaging.service"; -+import { PlatformUtilsService } from "../abstractions/platformUtils.service"; -+import { PolicyService } from "../abstractions/policy.service"; -+import { SearchService } from "../abstractions/search.service"; -+import { StateService } from "../abstractions/state.service"; -+import { TokenService } from "../abstractions/token.service"; -+import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "../abstractions/vaultTimeout.service"; -+import { KeySuffixOptions } from "../enums/keySuffixOptions"; -+ -+import { PolicyType } from "../enums/policyType"; - - export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { -- pinProtectedKey: EncString = null; -- biometricLocked: boolean = true; -- everBeenUnlocked: boolean = false; -- -- private inited = false; -- -- constructor(private cipherService: CipherService, private folderService: FolderService, -- private collectionService: CollectionService, private cryptoService: CryptoService, -- protected platformUtilsService: PlatformUtilsService, private storageService: StorageService, -- private messagingService: MessagingService, private searchService: SearchService, -- private userService: UserService, private tokenService: TokenService, private policyService: PolicyService, -- private keyConnectorService: KeyConnectorService, -- private lockedCallback: () => Promise = null, private loggedOutCallback: () => Promise = null) { -+ private inited = false; -+ -+ constructor( -+ private cipherService: CipherService, -+ private folderService: FolderService, -+ private collectionService: CollectionService, -+ private cryptoService: CryptoService, -+ protected platformUtilsService: PlatformUtilsService, -+ private messagingService: MessagingService, -+ private searchService: SearchService, -+ private tokenService: TokenService, -+ private policyService: PolicyService, -+ private keyConnectorService: KeyConnectorService, -+ private stateService: StateService, -+ private lockedCallback: () => Promise = null, -+ private loggedOutCallback: (userId?: string) => Promise = null -+ ) {} -+ -+ init(checkOnInterval: boolean) { -+ if (this.inited) { -+ return; - } - -- init(checkOnInterval: boolean) { -- if (this.inited) { -- return; -- } -- -- this.inited = true; -- if (checkOnInterval) { -- this.startCheck(); -- } -+ this.inited = true; -+ if (checkOnInterval) { -+ this.startCheck(); - } -- -- startCheck() { -- this.checkVaultTimeout(); -- setInterval(() => this.checkVaultTimeout(), 10 * 1000); // check every 10 seconds -+ } -+ -+ startCheck() { -+ this.checkVaultTimeout(); -+ setInterval(() => this.checkVaultTimeout(), 10 * 1000); // check every 10 seconds -+ } -+ -+ // Keys aren't stored for a device that is locked or logged out. -+ async isLocked(userId?: string): Promise { -+ const neverLock = -+ (await this.cryptoService.hasKeyStored(KeySuffixOptions.Auto, userId)) && -+ !(await this.stateService.getEverBeenUnlocked({ userId: userId })); -+ if (neverLock) { -+ // TODO: This also _sets_ the key so when we check memory in the next line it finds a key. -+ // We should refactor here. -+ await this.cryptoService.getKey(KeySuffixOptions.Auto, userId); - } - -- // Keys aren't stored for a device that is locked or logged out. -- async isLocked(): Promise { -- // Handle never lock startup situation -- if (await this.cryptoService.hasKeyStored('auto') && !this.everBeenUnlocked) { -- await this.cryptoService.getKey('auto'); -- } -+ return !(await this.cryptoService.hasKeyInMemory(userId)); -+ } - -- return !this.cryptoService.hasKeyInMemory(); -+ async checkVaultTimeout(): Promise { -+ if (await this.platformUtilsService.isViewOpen()) { -+ return; - } - -- async checkVaultTimeout(): Promise { -- if (await this.platformUtilsService.isViewOpen()) { -- // Do not lock -- return; -- } -- -- // "is logged out check" - similar to isLocked, below -- const authed = await this.userService.isAuthenticated(); -- if (!authed) { -- return; -- } -- -- if (await this.isLocked()) { -- return; -- } -- -- const vaultTimeout = await this.getVaultTimeout(); -- if (vaultTimeout == null || vaultTimeout < 0) { -- return; -- } -- -- const lastActive = await this.storageService.get(ConstantsService.lastActiveKey); -- if (lastActive == null) { -- return; -- } -- -- const vaultTimeoutSeconds = vaultTimeout * 60; -- const diffSeconds = ((new Date()).getTime() - lastActive) / 1000; -- if (diffSeconds >= vaultTimeoutSeconds) { -- // Pivot based on the saved vault timeout action -- const timeoutAction = await this.storageService.get(ConstantsService.vaultTimeoutActionKey); -- timeoutAction === 'logOut' ? await this.logOut() : await this.lock(true); -- } -+ for (const userId in this.stateService.accounts.getValue()) { -+ if (userId != null && (await this.shouldLock(userId))) { -+ await this.executeTimeoutAction(userId); -+ } - } -+ } - -- async lock(allowSoftLock = false): Promise { -- const authed = await this.userService.isAuthenticated(); -- if (!authed) { -- return; -- } -- -- if (await this.keyConnectorService.getUsesKeyConnector()) { -- const pinSet = await this.isPinLockSet(); -- const pinLock = (pinSet[0] && this.pinProtectedKey != null) || pinSet[1]; -- -- if (!pinLock && !await this.isBiometricLockSet()) { -- await this.logOut(); -- } -- } -- -- this.biometricLocked = true; -- this.everBeenUnlocked = true; -- await this.cryptoService.clearKey(false); -- await this.cryptoService.clearOrgKeys(true); -- await this.cryptoService.clearKeyPair(true); -- await this.cryptoService.clearEncKey(true); -- -- this.folderService.clearCache(); -- this.cipherService.clearCache(); -- this.collectionService.clearCache(); -- this.searchService.clearIndex(); -- this.messagingService.send('locked'); -- if (this.lockedCallback != null) { -- await this.lockedCallback(); -- } -+ async lock(allowSoftLock = false, userId?: string): Promise { -+ const authed = await this.stateService.getIsAuthenticated({ userId: userId }); -+ if (!authed) { -+ return; - } - -- async logOut(): Promise { -- if (this.loggedOutCallback != null) { -- await this.loggedOutCallback(); -- } -+ if (await this.keyConnectorService.getUsesKeyConnector()) { -+ const pinSet = await this.isPinLockSet(); -+ const pinLock = -+ (pinSet[0] && (await this.stateService.getDecryptedPinProtected()) != null) || pinSet[1]; -+ -+ if (!pinLock && !(await this.isBiometricLockSet())) { -+ await this.logOut(); -+ } - } - -- async setVaultTimeoutOptions(timeout: number, action: string): Promise { -- await this.storageService.save(ConstantsService.vaultTimeoutKey, timeout); -- await this.storageService.save(ConstantsService.vaultTimeoutActionKey, action); -- await this.cryptoService.toggleKey(); -- await this.tokenService.toggleTokens(); -+ if (userId == null || userId === (await this.stateService.getUserId())) { -+ this.searchService.clearIndex(); - } - -- async isPinLockSet(): Promise<[boolean, boolean]> { -- const protectedPin = await this.storageService.get(ConstantsService.protectedPin); -- const pinProtectedKey = await this.storageService.get(ConstantsService.pinProtectedKey); -- return [protectedPin != null, pinProtectedKey != null]; -+ await this.stateService.setEverBeenUnlocked(true, { userId: userId }); -+ await this.stateService.setBiometricLocked(true, { userId: userId }); -+ -+ await this.cryptoService.clearKey(false, userId); -+ await this.cryptoService.clearOrgKeys(true, userId); -+ await this.cryptoService.clearKeyPair(true, userId); -+ await this.cryptoService.clearEncKey(true, userId); -+ -+ await this.folderService.clearCache(userId); -+ await this.cipherService.clearCache(userId); -+ await this.collectionService.clearCache(userId); -+ -+ this.messagingService.send("locked", { userId: userId }); -+ -+ if (this.lockedCallback != null) { -+ await this.lockedCallback(); - } -+ } - -- async isBiometricLockSet(): Promise { -- return await this.storageService.get(ConstantsService.biometricUnlockKey); -+ async logOut(userId?: string): Promise { -+ if (this.loggedOutCallback != null) { -+ await this.loggedOutCallback(userId); -+ } -+ } -+ -+ async setVaultTimeoutOptions(timeout: number, action: string): Promise { -+ await this.stateService.setVaultTimeout(timeout); -+ await this.stateService.setVaultTimeoutAction(action); -+ await this.cryptoService.toggleKey(); -+ await this.tokenService.toggleTokens(); -+ } -+ -+ async isPinLockSet(): Promise<[boolean, boolean]> { -+ const protectedPin = await this.stateService.getProtectedPin(); -+ const pinProtectedKey = await this.stateService.getEncryptedPinProtected(); -+ return [protectedPin != null, pinProtectedKey != null]; -+ } -+ -+ async isBiometricLockSet(): Promise { -+ return await this.stateService.getBiometricUnlock(); -+ } -+ -+ async getVaultTimeout(userId?: string): Promise { -+ const vaultTimeout = await this.stateService.getVaultTimeout({ userId: userId }); -+ -+ if ( -+ await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout, null, userId) -+ ) { -+ const policy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout, userId); -+ // Remove negative values, and ensure it's smaller than maximum allowed value according to policy -+ let timeout = Math.min(vaultTimeout, policy[0].data.minutes); -+ -+ if (vaultTimeout == null || timeout < 0) { -+ timeout = policy[0].data.minutes; -+ } -+ -+ // We really shouldn't need to set the value here, but multiple services relies on this value being correct. -+ if (vaultTimeout !== timeout) { -+ await this.stateService.setVaultTimeout(timeout, { userId: userId }); -+ } -+ -+ return timeout; - } - -- async getVaultTimeout(): Promise { -- const vaultTimeout = await this.storageService.get(ConstantsService.vaultTimeoutKey); -+ return vaultTimeout; -+ } - -- if (await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout)) { -- const policy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout); -- // Remove negative values, and ensure it's smaller than maximum allowed value according to policy -- let timeout = Math.min(vaultTimeout, policy[0].data.minutes); -+ async clear(userId?: string): Promise { -+ await this.stateService.setEverBeenUnlocked(false, { userId: userId }); -+ await this.stateService.setDecryptedPinProtected(null, { userId: userId }); -+ await this.stateService.setProtectedPin(null, { userId: userId }); -+ } - -- if (vaultTimeout == null || timeout < 0) { -- timeout = policy[0].data.minutes; -- } -+ private async isLoggedOut(userId?: string): Promise { -+ return !(await this.stateService.getIsAuthenticated({ userId: userId })); -+ } - -- // We really shouldn't need to set the value here, but multiple services relies on this value being correct. -- if (vaultTimeout !== timeout) { -- await this.storageService.save(ConstantsService.vaultTimeoutKey, timeout); -- } -+ private async shouldLock(userId: string): Promise { -+ if (await this.isLoggedOut(userId)) { -+ return false; -+ } - -- return timeout; -- } -+ if (await this.isLocked(userId)) { -+ return false; -+ } - -- return vaultTimeout; -+ const vaultTimeout = await this.getVaultTimeout(userId); -+ if (vaultTimeout == null || vaultTimeout < 0) { -+ return false; - } - -- clear(): Promise { -- this.everBeenUnlocked = false; -- this.pinProtectedKey = null; -- return this.storageService.remove(ConstantsService.protectedPin); -+ const lastActive = await this.stateService.getLastActive({ userId: userId }); -+ if (lastActive == null) { -+ return false; - } -+ -+ const vaultTimeoutSeconds = vaultTimeout * 60; -+ const diffSeconds = (new Date().getTime() - lastActive) / 1000; -+ return diffSeconds >= vaultTimeoutSeconds; -+ } -+ -+ private async executeTimeoutAction(userId: string): Promise { -+ const timeoutAction = await this.stateService.getVaultTimeoutAction({ userId: userId }); -+ timeoutAction === "logOut" ? await this.logOut() : await this.lock(true, userId); -+ } - } -diff --git a/jslib/common/src/services/webCryptoFunction.service.ts b/jslib/common/src/services/webCryptoFunction.service.ts -index 2cabb96d..8f7a8019 100644 ---- a/jslib/common/src/services/webCryptoFunction.service.ts -+++ b/jslib/common/src/services/webCryptoFunction.service.ts -@@ -1,332 +1,389 @@ --import * as forge from 'node-forge'; -+import * as forge from "node-forge"; - --import { CryptoFunctionService } from '../abstractions/cryptoFunction.service'; --import { PlatformUtilsService } from '../abstractions/platformUtils.service'; -+import { CryptoFunctionService } from "../abstractions/cryptoFunction.service"; -+import { PlatformUtilsService } from "../abstractions/platformUtils.service"; - --import { Utils } from '../misc/utils'; -+import { Utils } from "../misc/utils"; - --import { DecryptParameters } from '../models/domain/decryptParameters'; --import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; -+import { DecryptParameters } from "../models/domain/decryptParameters"; -+import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; - - export class WebCryptoFunctionService implements CryptoFunctionService { -- private crypto: Crypto; -- private subtle: SubtleCrypto; -- private isIE: boolean; -- private isOldSafari: boolean; -- -- constructor(private win: Window, private platformUtilsService: PlatformUtilsService) { -- this.crypto = typeof win.crypto !== 'undefined' ? win.crypto : null; -- this.subtle = (!!this.crypto && typeof win.crypto.subtle !== 'undefined') ? win.crypto.subtle : null; -- this.isIE = platformUtilsService.isIE(); -- const ua = win.navigator.userAgent; -- this.isOldSafari = platformUtilsService.isSafari() && -- (ua.indexOf(' Version/10.') > -1 || ua.indexOf(' Version/9.') > -1); -+ private crypto: Crypto; -+ private subtle: SubtleCrypto; -+ private isIE: boolean; -+ private isOldSafari: boolean; -+ -+ constructor(private win: Window, private platformUtilsService: PlatformUtilsService) { -+ this.crypto = typeof win.crypto !== "undefined" ? win.crypto : null; -+ this.subtle = -+ !!this.crypto && typeof win.crypto.subtle !== "undefined" ? win.crypto.subtle : null; -+ this.isIE = platformUtilsService.isIE(); -+ const ua = win.navigator.userAgent; -+ this.isOldSafari = -+ platformUtilsService.isSafari() && -+ (ua.indexOf(" Version/10.") > -1 || ua.indexOf(" Version/9.") > -1); -+ } -+ -+ async pbkdf2( -+ password: string | ArrayBuffer, -+ salt: string | ArrayBuffer, -+ algorithm: "sha256" | "sha512", -+ iterations: number -+ ): Promise { -+ if (this.isIE || this.isOldSafari) { -+ const forgeLen = algorithm === "sha256" ? 32 : 64; -+ const passwordBytes = this.toByteString(password); -+ const saltBytes = this.toByteString(salt); -+ const derivedKeyBytes = (forge as any).pbkdf2( -+ passwordBytes, -+ saltBytes, -+ iterations, -+ forgeLen, -+ algorithm -+ ); -+ return Utils.fromByteStringToArray(derivedKeyBytes).buffer; - } - -- async pbkdf2(password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512', -- iterations: number): Promise { -- if (this.isIE || this.isOldSafari) { -- const forgeLen = algorithm === 'sha256' ? 32 : 64; -- const passwordBytes = this.toByteString(password); -- const saltBytes = this.toByteString(salt); -- const derivedKeyBytes = (forge as any).pbkdf2(passwordBytes, saltBytes, iterations, forgeLen, algorithm); -- return Utils.fromByteStringToArray(derivedKeyBytes).buffer; -- } -- -- const wcLen = algorithm === 'sha256' ? 256 : 512; -- const passwordBuf = this.toBuf(password); -- const saltBuf = this.toBuf(salt); -- -- const pbkdf2Params: Pbkdf2Params = { -- name: 'PBKDF2', -- salt: saltBuf, -- iterations: iterations, -- hash: { name: this.toWebCryptoAlgorithm(algorithm) }, -- }; -- -- const impKey = await this.subtle.importKey('raw', passwordBuf, { name: 'PBKDF2' } as any, -- false, ['deriveBits']); -- return await this.subtle.deriveBits(pbkdf2Params, impKey, wcLen); -+ const wcLen = algorithm === "sha256" ? 256 : 512; -+ const passwordBuf = this.toBuf(password); -+ const saltBuf = this.toBuf(salt); -+ -+ const pbkdf2Params: Pbkdf2Params = { -+ name: "PBKDF2", -+ salt: saltBuf, -+ iterations: iterations, -+ hash: { name: this.toWebCryptoAlgorithm(algorithm) }, -+ }; -+ -+ const impKey = await this.subtle.importKey( -+ "raw", -+ passwordBuf, -+ { name: "PBKDF2" } as any, -+ false, -+ ["deriveBits"] -+ ); -+ return await this.subtle.deriveBits(pbkdf2Params, impKey, wcLen); -+ } -+ -+ async hkdf( -+ ikm: ArrayBuffer, -+ salt: string | ArrayBuffer, -+ info: string | ArrayBuffer, -+ outputByteSize: number, -+ algorithm: "sha256" | "sha512" -+ ): Promise { -+ const saltBuf = this.toBuf(salt); -+ const infoBuf = this.toBuf(info); -+ -+ const hkdfParams: HkdfParams = { -+ name: "HKDF", -+ salt: saltBuf, -+ info: infoBuf, -+ hash: { name: this.toWebCryptoAlgorithm(algorithm) }, -+ }; -+ -+ const impKey = await this.subtle.importKey("raw", ikm, { name: "HKDF" } as any, false, [ -+ "deriveBits", -+ ]); -+ return await this.subtle.deriveBits(hkdfParams as any, impKey, outputByteSize * 8); -+ } -+ -+ // ref: https://tools.ietf.org/html/rfc5869 -+ async hkdfExpand( -+ prk: ArrayBuffer, -+ info: string | ArrayBuffer, -+ outputByteSize: number, -+ algorithm: "sha256" | "sha512" -+ ): Promise { -+ const hashLen = algorithm === "sha256" ? 32 : 64; -+ if (outputByteSize > 255 * hashLen) { -+ throw new Error("outputByteSize is too large."); - } -- -- async hkdf(ikm: ArrayBuffer, salt: string | ArrayBuffer, info: string | ArrayBuffer, -- outputByteSize: number, algorithm: 'sha256' | 'sha512'): Promise { -- const saltBuf = this.toBuf(salt); -- const infoBuf = this.toBuf(info); -- -- const hkdfParams: HkdfParams = { -- name: 'HKDF', -- salt: saltBuf, -- info: infoBuf, -- hash: { name: this.toWebCryptoAlgorithm(algorithm) }, -- }; -- -- const impKey = await this.subtle.importKey('raw', ikm, { name: 'HKDF' } as any, -- false, ['deriveBits']); -- return await this.subtle.deriveBits(hkdfParams as any, impKey, outputByteSize * 8); -- } -- -- // ref: https://tools.ietf.org/html/rfc5869 -- async hkdfExpand(prk: ArrayBuffer, info: string | ArrayBuffer, outputByteSize: number, -- algorithm: 'sha256' | 'sha512'): Promise { -- const hashLen = algorithm === 'sha256' ? 32 : 64; -- if (outputByteSize > 255 * hashLen) { -- throw new Error('outputByteSize is too large.'); -- } -- const prkArr = new Uint8Array(prk); -- if (prkArr.length < hashLen) { -- throw new Error('prk is too small.'); -- } -- const infoBuf = this.toBuf(info); -- const infoArr = new Uint8Array(infoBuf); -- let runningOkmLength = 0; -- let previousT = new Uint8Array(0); -- const n = Math.ceil(outputByteSize / hashLen); -- const okm = new Uint8Array(n * hashLen); -- for (let i = 0; i < n; i++) { -- const t = new Uint8Array(previousT.length + infoArr.length + 1); -- t.set(previousT); -- t.set(infoArr, previousT.length); -- t.set([i + 1], t.length - 1); -- previousT = new Uint8Array(await this.hmac(t.buffer, prk, algorithm)); -- okm.set(previousT, runningOkmLength); -- runningOkmLength += previousT.length; -- if (runningOkmLength >= outputByteSize) { -- break; -- } -- } -- return okm.slice(0, outputByteSize).buffer; -+ const prkArr = new Uint8Array(prk); -+ if (prkArr.length < hashLen) { -+ throw new Error("prk is too small."); - } -- -- async hash(value: string | ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512' | 'md5'): Promise { -- if ((this.isIE && algorithm === 'sha1') || algorithm === 'md5') { -- const md = algorithm === 'md5' ? forge.md.md5.create() : forge.md.sha1.create(); -- const valueBytes = this.toByteString(value); -- md.update(valueBytes, 'raw'); -- return Utils.fromByteStringToArray(md.digest().data).buffer; -- } -- -- const valueBuf = this.toBuf(value); -- return await this.subtle.digest({ name: this.toWebCryptoAlgorithm(algorithm) }, valueBuf); -- } -- -- async hmac(value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512'): Promise { -- if (this.isIE && algorithm === 'sha512') { -- const hmac = (forge as any).hmac.create(); -- const keyBytes = this.toByteString(key); -- const valueBytes = this.toByteString(value); -- hmac.start(algorithm, keyBytes); -- hmac.update(valueBytes, 'raw'); -- return Utils.fromByteStringToArray(hmac.digest().data).buffer; -- } -- -- const signingAlgorithm = { -- name: 'HMAC', -- hash: { name: this.toWebCryptoAlgorithm(algorithm) }, -- }; -- -- const impKey = await this.subtle.importKey('raw', key, signingAlgorithm, false, ['sign']); -- return await this.subtle.sign(signingAlgorithm, impKey, value); -+ const infoBuf = this.toBuf(info); -+ const infoArr = new Uint8Array(infoBuf); -+ let runningOkmLength = 0; -+ let previousT = new Uint8Array(0); -+ const n = Math.ceil(outputByteSize / hashLen); -+ const okm = new Uint8Array(n * hashLen); -+ for (let i = 0; i < n; i++) { -+ const t = new Uint8Array(previousT.length + infoArr.length + 1); -+ t.set(previousT); -+ t.set(infoArr, previousT.length); -+ t.set([i + 1], t.length - 1); -+ previousT = new Uint8Array(await this.hmac(t.buffer, prk, algorithm)); -+ okm.set(previousT, runningOkmLength); -+ runningOkmLength += previousT.length; -+ if (runningOkmLength >= outputByteSize) { -+ break; -+ } - } -- -- // Safely compare two values in a way that protects against timing attacks (Double HMAC Verification). -- // ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/ -- // ref: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy -- async compare(a: ArrayBuffer, b: ArrayBuffer): Promise { -- const macKey = await this.randomBytes(32); -- const signingAlgorithm = { -- name: 'HMAC', -- hash: { name: 'SHA-256' }, -- }; -- const impKey = await this.subtle.importKey('raw', macKey, signingAlgorithm, false, ['sign']); -- const mac1 = await this.subtle.sign(signingAlgorithm, impKey, a); -- const mac2 = await this.subtle.sign(signingAlgorithm, impKey, b); -- -- if (mac1.byteLength !== mac2.byteLength) { -- return false; -- } -- -- const arr1 = new Uint8Array(mac1); -- const arr2 = new Uint8Array(mac2); -- for (let i = 0; i < arr2.length; i++) { -- if (arr1[i] !== arr2[i]) { -- return false; -- } -- } -- -- return true; -+ return okm.slice(0, outputByteSize).buffer; -+ } -+ -+ async hash( -+ value: string | ArrayBuffer, -+ algorithm: "sha1" | "sha256" | "sha512" | "md5" -+ ): Promise { -+ if ((this.isIE && algorithm === "sha1") || algorithm === "md5") { -+ const md = algorithm === "md5" ? forge.md.md5.create() : forge.md.sha1.create(); -+ const valueBytes = this.toByteString(value); -+ md.update(valueBytes, "raw"); -+ return Utils.fromByteStringToArray(md.digest().data).buffer; - } - -- hmacFast(value: string, key: string, algorithm: 'sha1' | 'sha256' | 'sha512'): Promise { -- const hmac = (forge as any).hmac.create(); -- hmac.start(algorithm, key); -- hmac.update(value); -- const bytes = hmac.digest().getBytes(); -- return Promise.resolve(bytes); -+ const valueBuf = this.toBuf(value); -+ return await this.subtle.digest({ name: this.toWebCryptoAlgorithm(algorithm) }, valueBuf); -+ } -+ -+ async hmac( -+ value: ArrayBuffer, -+ key: ArrayBuffer, -+ algorithm: "sha1" | "sha256" | "sha512" -+ ): Promise { -+ if (this.isIE && algorithm === "sha512") { -+ const hmac = (forge as any).hmac.create(); -+ const keyBytes = this.toByteString(key); -+ const valueBytes = this.toByteString(value); -+ hmac.start(algorithm, keyBytes); -+ hmac.update(valueBytes, "raw"); -+ return Utils.fromByteStringToArray(hmac.digest().data).buffer; - } - -- async compareFast(a: string, b: string): Promise { -- const rand = await this.randomBytes(32); -- const bytes = new Uint32Array(rand); -- const buffer = forge.util.createBuffer(); -- for (let i = 0; i < bytes.length; i++) { -- buffer.putInt32(bytes[i]); -- } -- const macKey = buffer.getBytes(); -- -- const hmac = (forge as any).hmac.create(); -- hmac.start('sha256', macKey); -- hmac.update(a); -- const mac1 = hmac.digest().getBytes(); -- -- hmac.start(null, null); -- hmac.update(b); -- const mac2 = hmac.digest().getBytes(); -- -- const equals = mac1 === mac2; -- return equals; -+ const signingAlgorithm = { -+ name: "HMAC", -+ hash: { name: this.toWebCryptoAlgorithm(algorithm) }, -+ }; -+ -+ const impKey = await this.subtle.importKey("raw", key, signingAlgorithm, false, ["sign"]); -+ return await this.subtle.sign(signingAlgorithm, impKey, value); -+ } -+ -+ // Safely compare two values in a way that protects against timing attacks (Double HMAC Verification). -+ // ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/ -+ // ref: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy -+ async compare(a: ArrayBuffer, b: ArrayBuffer): Promise { -+ const macKey = await this.randomBytes(32); -+ const signingAlgorithm = { -+ name: "HMAC", -+ hash: { name: "SHA-256" }, -+ }; -+ const impKey = await this.subtle.importKey("raw", macKey, signingAlgorithm, false, ["sign"]); -+ const mac1 = await this.subtle.sign(signingAlgorithm, impKey, a); -+ const mac2 = await this.subtle.sign(signingAlgorithm, impKey, b); -+ -+ if (mac1.byteLength !== mac2.byteLength) { -+ return false; - } - -- async aesEncrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise { -- const impKey = await this.subtle.importKey('raw', key, { name: 'AES-CBC' } as any, false, ['encrypt']); -- return await this.subtle.encrypt({ name: 'AES-CBC', iv: iv }, impKey, data); -+ const arr1 = new Uint8Array(mac1); -+ const arr2 = new Uint8Array(mac2); -+ for (let i = 0; i < arr2.length; i++) { -+ if (arr1[i] !== arr2[i]) { -+ return false; -+ } - } - -- aesDecryptFastParameters(data: string, iv: string, mac: string, key: SymmetricCryptoKey): -- DecryptParameters { -- const p = new DecryptParameters(); -- if (key.meta != null) { -- p.encKey = key.meta.encKeyByteString; -- p.macKey = key.meta.macKeyByteString; -- } -- -- if (p.encKey == null) { -- p.encKey = forge.util.decode64(key.encKeyB64); -- } -- p.data = forge.util.decode64(data); -- p.iv = forge.util.decode64(iv); -- p.macData = p.iv + p.data; -- if (p.macKey == null && key.macKeyB64 != null) { -- p.macKey = forge.util.decode64(key.macKeyB64); -- } -- if (mac != null) { -- p.mac = forge.util.decode64(mac); -- } -- -- // cache byte string keys for later -- if (key.meta == null) { -- key.meta = {}; -- } -- if (key.meta.encKeyByteString == null) { -- key.meta.encKeyByteString = p.encKey; -- } -- if (p.macKey != null && key.meta.macKeyByteString == null) { -- key.meta.macKeyByteString = p.macKey; -- } -- -- return p; -+ return true; -+ } -+ -+ hmacFast(value: string, key: string, algorithm: "sha1" | "sha256" | "sha512"): Promise { -+ const hmac = (forge as any).hmac.create(); -+ hmac.start(algorithm, key); -+ hmac.update(value); -+ const bytes = hmac.digest().getBytes(); -+ return Promise.resolve(bytes); -+ } -+ -+ async compareFast(a: string, b: string): Promise { -+ const rand = await this.randomBytes(32); -+ const bytes = new Uint32Array(rand); -+ const buffer = forge.util.createBuffer(); -+ for (let i = 0; i < bytes.length; i++) { -+ buffer.putInt32(bytes[i]); - } -- -- aesDecryptFast(parameters: DecryptParameters): Promise { -- const dataBuffer = (forge as any).util.createBuffer(parameters.data); -- const decipher = (forge as any).cipher.createDecipher('AES-CBC', parameters.encKey); -- decipher.start({ iv: parameters.iv }); -- decipher.update(dataBuffer); -- decipher.finish(); -- const val = decipher.output.toString('utf8'); -- return Promise.resolve(val); -+ const macKey = buffer.getBytes(); -+ -+ const hmac = (forge as any).hmac.create(); -+ hmac.start("sha256", macKey); -+ hmac.update(a); -+ const mac1 = hmac.digest().getBytes(); -+ -+ hmac.start(null, null); -+ hmac.update(b); -+ const mac2 = hmac.digest().getBytes(); -+ -+ const equals = mac1 === mac2; -+ return equals; -+ } -+ -+ async aesEncrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise { -+ const impKey = await this.subtle.importKey("raw", key, { name: "AES-CBC" } as any, false, [ -+ "encrypt", -+ ]); -+ return await this.subtle.encrypt({ name: "AES-CBC", iv: iv }, impKey, data); -+ } -+ -+ aesDecryptFastParameters( -+ data: string, -+ iv: string, -+ mac: string, -+ key: SymmetricCryptoKey -+ ): DecryptParameters { -+ const p = new DecryptParameters(); -+ if (key.meta != null) { -+ p.encKey = key.meta.encKeyByteString; -+ p.macKey = key.meta.macKeyByteString; - } - -- async aesDecrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise { -- const impKey = await this.subtle.importKey('raw', key, { name: 'AES-CBC' } as any, false, ['decrypt']); -- return await this.subtle.decrypt({ name: 'AES-CBC', iv: iv }, impKey, data); -+ if (p.encKey == null) { -+ p.encKey = forge.util.decode64(key.encKeyB64); - } -- -- async rsaEncrypt(data: ArrayBuffer, publicKey: ArrayBuffer, algorithm: 'sha1' | 'sha256'): Promise { -- // Note: Edge browser requires that we specify name and hash for both key import and decrypt. -- // We cannot use the proper types here. -- const rsaParams = { -- name: 'RSA-OAEP', -- hash: { name: this.toWebCryptoAlgorithm(algorithm) }, -- }; -- const impKey = await this.subtle.importKey('spki', publicKey, rsaParams, false, ['encrypt']); -- return await this.subtle.encrypt(rsaParams, impKey, data); -+ p.data = forge.util.decode64(data); -+ p.iv = forge.util.decode64(iv); -+ p.macData = p.iv + p.data; -+ if (p.macKey == null && key.macKeyB64 != null) { -+ p.macKey = forge.util.decode64(key.macKeyB64); - } -- -- async rsaDecrypt(data: ArrayBuffer, privateKey: ArrayBuffer, algorithm: 'sha1' | 'sha256'): Promise { -- // Note: Edge browser requires that we specify name and hash for both key import and decrypt. -- // We cannot use the proper types here. -- const rsaParams = { -- name: 'RSA-OAEP', -- hash: { name: this.toWebCryptoAlgorithm(algorithm) }, -- }; -- const impKey = await this.subtle.importKey('pkcs8', privateKey, rsaParams, false, ['decrypt']); -- return await this.subtle.decrypt(rsaParams, impKey, data); -+ if (mac != null) { -+ p.mac = forge.util.decode64(mac); - } - -- async rsaExtractPublicKey(privateKey: ArrayBuffer): Promise { -- const rsaParams = { -- name: 'RSA-OAEP', -- // Have to specify some algorithm -- hash: { name: this.toWebCryptoAlgorithm('sha1') }, -- }; -- const impPrivateKey = await this.subtle.importKey('pkcs8', privateKey, rsaParams, true, ['decrypt']); -- const jwkPrivateKey = await this.subtle.exportKey('jwk', impPrivateKey); -- const jwkPublicKeyParams = { -- kty: 'RSA', -- e: jwkPrivateKey.e, -- n: jwkPrivateKey.n, -- alg: 'RSA-OAEP', -- ext: true, -- }; -- const impPublicKey = await this.subtle.importKey('jwk', jwkPublicKeyParams, rsaParams, true, ['encrypt']); -- return await this.subtle.exportKey('spki', impPublicKey); -+ // cache byte string keys for later -+ if (key.meta == null) { -+ key.meta = {}; - } -- -- async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[ArrayBuffer, ArrayBuffer]> { -- const rsaParams = { -- name: 'RSA-OAEP', -- modulusLength: length, -- publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 65537 -- // Have to specify some algorithm -- hash: { name: this.toWebCryptoAlgorithm('sha1') }, -- }; -- const keyPair = (await this.subtle.generateKey(rsaParams, true, ['encrypt', 'decrypt'])) as CryptoKeyPair; -- const publicKey = await this.subtle.exportKey('spki', keyPair.publicKey); -- const privateKey = await this.subtle.exportKey('pkcs8', keyPair.privateKey); -- return [publicKey, privateKey]; -+ if (key.meta.encKeyByteString == null) { -+ key.meta.encKeyByteString = p.encKey; - } -- -- randomBytes(length: number): Promise { -- const arr = new Uint8Array(length); -- this.crypto.getRandomValues(arr); -- return Promise.resolve(arr.buffer); -+ if (p.macKey != null && key.meta.macKeyByteString == null) { -+ key.meta.macKeyByteString = p.macKey; - } - -- private toBuf(value: string | ArrayBuffer): ArrayBuffer { -- let buf: ArrayBuffer; -- if (typeof (value) === 'string') { -- buf = Utils.fromUtf8ToArray(value).buffer; -- } else { -- buf = value; -- } -- return buf; -+ return p; -+ } -+ -+ aesDecryptFast(parameters: DecryptParameters): Promise { -+ const dataBuffer = (forge as any).util.createBuffer(parameters.data); -+ const decipher = (forge as any).cipher.createDecipher("AES-CBC", parameters.encKey); -+ decipher.start({ iv: parameters.iv }); -+ decipher.update(dataBuffer); -+ decipher.finish(); -+ const val = decipher.output.toString("utf8"); -+ return Promise.resolve(val); -+ } -+ -+ async aesDecrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise { -+ const impKey = await this.subtle.importKey("raw", key, { name: "AES-CBC" } as any, false, [ -+ "decrypt", -+ ]); -+ return await this.subtle.decrypt({ name: "AES-CBC", iv: iv }, impKey, data); -+ } -+ -+ async rsaEncrypt( -+ data: ArrayBuffer, -+ publicKey: ArrayBuffer, -+ algorithm: "sha1" | "sha256" -+ ): Promise { -+ // Note: Edge browser requires that we specify name and hash for both key import and decrypt. -+ // We cannot use the proper types here. -+ const rsaParams = { -+ name: "RSA-OAEP", -+ hash: { name: this.toWebCryptoAlgorithm(algorithm) }, -+ }; -+ const impKey = await this.subtle.importKey("spki", publicKey, rsaParams, false, ["encrypt"]); -+ return await this.subtle.encrypt(rsaParams, impKey, data); -+ } -+ -+ async rsaDecrypt( -+ data: ArrayBuffer, -+ privateKey: ArrayBuffer, -+ algorithm: "sha1" | "sha256" -+ ): Promise { -+ // Note: Edge browser requires that we specify name and hash for both key import and decrypt. -+ // We cannot use the proper types here. -+ const rsaParams = { -+ name: "RSA-OAEP", -+ hash: { name: this.toWebCryptoAlgorithm(algorithm) }, -+ }; -+ const impKey = await this.subtle.importKey("pkcs8", privateKey, rsaParams, false, ["decrypt"]); -+ return await this.subtle.decrypt(rsaParams, impKey, data); -+ } -+ -+ async rsaExtractPublicKey(privateKey: ArrayBuffer): Promise { -+ const rsaParams = { -+ name: "RSA-OAEP", -+ // Have to specify some algorithm -+ hash: { name: this.toWebCryptoAlgorithm("sha1") }, -+ }; -+ const impPrivateKey = await this.subtle.importKey("pkcs8", privateKey, rsaParams, true, [ -+ "decrypt", -+ ]); -+ const jwkPrivateKey = await this.subtle.exportKey("jwk", impPrivateKey); -+ const jwkPublicKeyParams = { -+ kty: "RSA", -+ e: jwkPrivateKey.e, -+ n: jwkPrivateKey.n, -+ alg: "RSA-OAEP", -+ ext: true, -+ }; -+ const impPublicKey = await this.subtle.importKey("jwk", jwkPublicKeyParams, rsaParams, true, [ -+ "encrypt", -+ ]); -+ return await this.subtle.exportKey("spki", impPublicKey); -+ } -+ -+ async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[ArrayBuffer, ArrayBuffer]> { -+ const rsaParams = { -+ name: "RSA-OAEP", -+ modulusLength: length, -+ publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 65537 -+ // Have to specify some algorithm -+ hash: { name: this.toWebCryptoAlgorithm("sha1") }, -+ }; -+ const keyPair = (await this.subtle.generateKey(rsaParams, true, [ -+ "encrypt", -+ "decrypt", -+ ])) as CryptoKeyPair; -+ const publicKey = await this.subtle.exportKey("spki", keyPair.publicKey); -+ const privateKey = await this.subtle.exportKey("pkcs8", keyPair.privateKey); -+ return [publicKey, privateKey]; -+ } -+ -+ randomBytes(length: number): Promise { -+ const arr = new Uint8Array(length); -+ this.crypto.getRandomValues(arr); -+ return Promise.resolve(arr.buffer); -+ } -+ -+ private toBuf(value: string | ArrayBuffer): ArrayBuffer { -+ let buf: ArrayBuffer; -+ if (typeof value === "string") { -+ buf = Utils.fromUtf8ToArray(value).buffer; -+ } else { -+ buf = value; - } -- -- private toByteString(value: string | ArrayBuffer): string { -- let bytes: string; -- if (typeof (value) === 'string') { -- bytes = forge.util.encodeUtf8(value); -- } else { -- bytes = Utils.fromBufferToByteString(value); -- } -- return bytes; -+ return buf; -+ } -+ -+ private toByteString(value: string | ArrayBuffer): string { -+ let bytes: string; -+ if (typeof value === "string") { -+ bytes = forge.util.encodeUtf8(value); -+ } else { -+ bytes = Utils.fromBufferToByteString(value); - } -+ return bytes; -+ } - -- private toWebCryptoAlgorithm(algorithm: 'sha1' | 'sha256' | 'sha512' | 'md5'): string { -- if (algorithm === 'md5') { -- throw new Error('MD5 is not supported in WebCrypto.'); -- } -- return algorithm === 'sha1' ? 'SHA-1' : algorithm === 'sha256' ? 'SHA-256' : 'SHA-512'; -+ private toWebCryptoAlgorithm(algorithm: "sha1" | "sha256" | "sha512" | "md5"): string { -+ if (algorithm === "md5") { -+ throw new Error("MD5 is not supported in WebCrypto."); - } -+ return algorithm === "sha1" ? "SHA-1" : algorithm === "sha256" ? "SHA-256" : "SHA-512"; -+ } - } -diff --git a/jslib/common/src/types/verification.ts b/jslib/common/src/types/verification.ts -index 72ee8b0a..07ca4bbf 100644 ---- a/jslib/common/src/types/verification.ts -+++ b/jslib/common/src/types/verification.ts -@@ -1,6 +1,6 @@ --import { VerificationType } from '../enums/verificationType'; -+import { VerificationType } from "../enums/verificationType"; - - export type Verification = { -- type: VerificationType, -- secret: string, -+ type: VerificationType; -+ secret: string; - }; -diff --git a/jslib/common/tsconfig.json b/jslib/common/tsconfig.json -index 96033322..2fbb1d8d 100644 ---- a/jslib/common/tsconfig.json -+++ b/jslib/common/tsconfig.json -@@ -1,28 +1,5 @@ - { -- "compilerOptions": { -- "pretty": true, -- "moduleResolution": "node", -- "noImplicitAny": true, -- "target": "ES6", -- "module": "commonjs", -- "lib": ["es5", "es6", "es7", "dom"], -- "sourceMap": true, -- "declaration": true, -- "allowSyntheticDefaultImports": true, -- "experimentalDecorators": true, -- "emitDecoratorMetadata": true, -- "declarationDir": "dist/types", -- "outDir": "dist", -- "typeRoots": [ -- "node_modules/@types" -- ] -- }, -- "include": [ -- "src", -- "spec" -- ], -- "exclude": [ -- "node_modules", -- "dist" -- ] -+ "extends": "../shared/tsconfig", -+ "include": ["src", "spec"], -+ "exclude": ["node_modules", "dist"] - } -diff --git a/jslib/electron/package-lock.json b/jslib/electron/package-lock.json -index 1201fd87..9ba8c4e1 100644 ---- a/jslib/electron/package-lock.json -+++ b/jslib/electron/package-lock.json -@@ -11,17 +11,17 @@ - "dependencies": { - "@bitwarden/jslib-common": "file:../common", - "@nodert-win10-rs4/windows.security.credentials.ui": "^0.4.4", -- "electron": "14.2.0", -+ "electron": "16.0.7", - "electron-log": "4.4.1", - "electron-store": "8.0.1", -- "electron-updater": "4.3.9", -+ "electron-updater": "4.6.1", - "forcefocus": "^1.1.0", - "keytar": "7.7.0" - }, - "devDependencies": { -- "@types/node": "^14.17.1", -+ "@types/node": "^16.11.12", - "rimraf": "^3.0.2", -- "typescript": "4.1.5" -+ "typescript": "4.3.5" - } - }, - "../common": { -@@ -36,19 +36,19 @@ - "lunr": "^2.3.9", - "node-forge": "^0.10.0", - "papaparse": "^5.3.0", -- "rxjs": "6.6.7", -+ "rxjs": "^7.4.0", - "tldjs": "^2.3.1", - "zxcvbn": "^4.4.2" - }, - "devDependencies": { - "@types/lunr": "^2.3.3", -- "@types/node": "^14.17.1", -+ "@types/node": "^16.11.12", - "@types/node-forge": "^0.9.7", - "@types/papaparse": "^5.2.5", - "@types/tldjs": "^2.3.0", - "@types/zxcvbn": "^4.4.1", - "rimraf": "^3.0.2", -- "typescript": "4.1.5" -+ "typescript": "4.3.5" - } - }, - "node_modules/@bitwarden/jslib-common": { -@@ -56,9 +56,9 @@ - "link": true - }, - "node_modules/@electron/get": { -- "version": "1.13.0", -- "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.13.0.tgz", -- "integrity": "sha512-+SjZhRuRo+STTO1Fdhzqnv9D2ZhjxXP6egsJ9kiO8dtP68cDx7dFCwWi64dlMQV7sWcfW1OYCW4wviEBzmRsfQ==", -+ "version": "1.13.1", -+ "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.13.1.tgz", -+ "integrity": "sha512-U5vkXDZ9DwXtkPqlB45tfYnnYBN8PePp1z/XDCupnSpdrxT8/ThCv9WCwPLf9oqiSGZTkH6dx2jDUPuoXpjkcA==", - "dependencies": { - "debug": "^4.1.1", - "env-paths": "^2.2.0", -@@ -72,7 +72,7 @@ - "node": ">=8.6" - }, - "optionalDependencies": { -- "global-agent": "^2.0.2", -+ "global-agent": "^3.0.0", - "global-tunnel-ng": "^2.7.1" - } - }, -@@ -105,19 +105,20 @@ - } - }, - "node_modules/@types/node": { -- "version": "14.17.19", -- "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.19.tgz", -- "integrity": "sha512-jjYI6NkyfXykucU6ELEoT64QyKOdvaA6enOqKtP4xUsGY0X0ZUZz29fUmrTRo+7v7c6TgDu82q3GHHaCEkqZwA==" -+ "version": "16.11.12", -+ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", -+ "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==", -+ "dev": true - }, - "node_modules/@types/semver": { -- "version": "7.3.8", -- "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.8.tgz", -- "integrity": "sha512-D/2EJvAlCEtYFEYmmlGwbGXuK886HzyCc3nZX/tkFTQdEU8jZDAgiv08P162yB17y4ZXZoq7yFAnW4GDBb9Now==" -+ "version": "7.3.9", -+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", -+ "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==" - }, - "node_modules/ajv": { -- "version": "8.6.3", -- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", -- "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", -+ "version": "8.8.2", -+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", -+ "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", -@@ -289,9 +290,9 @@ - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "node_modules/builder-util-runtime": { -- "version": "8.7.5", -- "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.7.5.tgz", -- "integrity": "sha512-fgUFHKtMNjdvH6PDRFntdIGUPgwZ69sXsAqEulCtoiqgWes5agrMq/Ud274zjJRTbckYh2PHh8/1CpFc6dpsbQ==", -+ "version": "8.9.1", -+ "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.9.1.tgz", -+ "integrity": "sha512-c8a8J3wK6BIVLW7ls+7TRK9igspTbzWmUqxFbgK0m40Ggm6efUbxtWVCGIjc+dtchyr5qAMAUL6iEGRdS/6vwg==", - "dependencies": { - "debug": "^4.3.2", - "sax": "^1.2.4" -@@ -381,9 +382,9 @@ - } - }, - "node_modules/conf": { -- "version": "10.0.3", -- "resolved": "https://registry.npmjs.org/conf/-/conf-10.0.3.tgz", -- "integrity": "sha512-4gtQ/Q36qVxBzMe6B7gWOAfni1VdhuHkIzxydHkclnwGmgN+eW4bb6jj73vigCfr7d3WlmqawvhZrpCUCTPYxQ==", -+ "version": "10.1.1", -+ "resolved": "https://registry.npmjs.org/conf/-/conf-10.1.1.tgz", -+ "integrity": "sha512-z2civwq/k8TMYtcn3SVP0Peso4otIWnHtcTuHhQ0zDZDdP4NTxqEc8owfkz4zBsdMYdn/LFcE+ZhbCeqkhtq3Q==", - "dependencies": { - "ajv": "^8.6.3", - "ajv-formats": "^2.1.1", -@@ -432,17 +433,6 @@ - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, -- "node_modules/core-js": { -- "version": "3.18.1", -- "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.18.1.tgz", -- "integrity": "sha512-vJlUi/7YdlCZeL6fXvWNaLUPh/id12WXj3MbkMw5uOyF0PfWPBNOCNbs53YqgrvtujLNlt9JQpruyIKkUZ+PKA==", -- "hasInstallScript": true, -- "optional": true, -- "funding": { -- "type": "opencollective", -- "url": "https://opencollective.com/core-js" -- } -- }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", -@@ -463,9 +453,9 @@ - } - }, - "node_modules/debug": { -- "version": "4.3.2", -- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", -- "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", -+ "version": "4.3.3", -+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", -+ "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dependencies": { - "ms": "2.1.2" - }, -@@ -556,13 +546,12 @@ - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, - "node_modules/electron": { -- "version": "14.2.0", -- "resolved": "https://registry.npmjs.org/electron/-/electron-14.2.0.tgz", -- "integrity": "sha512-6CmAv1P0xcwK3FQOSA27fHI36/wctSFVgj46VODn56srXXQWeolkK1VzeAFNE613iAuuH9jJdHvE3gz+c7XkNA==", -+ "version": "16.0.7", -+ "resolved": "https://registry.npmjs.org/electron/-/electron-16.0.7.tgz", -+ "integrity": "sha512-/IMwpBf2svhA1X/7Q58RV+Nn0fvUJsHniG4TizaO7q4iKFYSQ6hBvsLz+cylcZ8hRMKmVy5G1XaMNJID2ah23w==", - "hasInstallScript": true, -- "license": "MIT", - "dependencies": { -- "@electron/get": "^1.0.1", -+ "@electron/get": "^1.13.0", - "@types/node": "^14.6.2", - "extract-zip": "^1.0.3" - }, -@@ -591,15 +580,15 @@ - } - }, - "node_modules/electron-updater": { -- "version": "4.3.9", -- "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.3.9.tgz", -- "integrity": "sha512-LCNfedSwZfS4Hza+pDyPR05LqHtGorCStaBgVpRnfKxOlZcvpYEX0AbMeH5XUtbtGRoH2V8osbbf2qKPNb7AsA==", -+ "version": "4.6.1", -+ "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.6.1.tgz", -+ "integrity": "sha512-YsU1mHqXLrXXmBMsxhxy24PrbaB8rnpZDPmFa2gOkTYk/Ch13+R0fjsRSpPYvqtskVVY0ux8fu+HnUkVkqc7og==", - "dependencies": { -- "@types/semver": "^7.3.5", -- "builder-util-runtime": "8.7.5", -+ "@types/semver": "^7.3.6", -+ "builder-util-runtime": "8.9.1", - "fs-extra": "^10.0.0", - "js-yaml": "^4.1.0", -- "lazy-val": "^1.0.4", -+ "lazy-val": "^1.0.5", - "lodash.escaperegexp": "^4.1.2", - "lodash.isequal": "^4.5.0", - "semver": "^7.3.5" -@@ -651,6 +640,11 @@ - "node": ">= 10.0.0" - } - }, -+ "node_modules/electron/node_modules/@types/node": { -+ "version": "14.18.0", -+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.0.tgz", -+ "integrity": "sha512-0GeIl2kmVMXEnx8tg1SlG6Gg8vkqirrW752KqolYo1PHevhhZN3bhJ67qHj+bQaINhX0Ra3TlWwRvMCd9iEfNQ==" -+ }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", -@@ -847,13 +841,12 @@ - } - }, - "node_modules/global-agent": { -- "version": "2.2.0", -- "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.2.0.tgz", -- "integrity": "sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg==", -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", -+ "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", - "optional": true, - "dependencies": { - "boolean": "^3.0.1", -- "core-js": "^3.6.5", - "es6-error": "^4.1.1", - "matcher": "^3.0.0", - "roarr": "^2.15.3", -@@ -1621,9 +1614,9 @@ - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "node_modules/signal-exit": { -- "version": "3.0.4", -- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.4.tgz", -- "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==" -+ "version": "3.0.6", -+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", -+ "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" - }, - "node_modules/simple-concat": { - "version": "1.0.1", -@@ -1817,9 +1810,9 @@ - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, - "node_modules/typescript": { -- "version": "4.1.5", -- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz", -- "integrity": "sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==", -+ "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", -@@ -1867,11 +1860,11 @@ - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" - }, - "node_modules/wide-align": { -- "version": "1.1.3", -- "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", -- "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", -+ "version": "1.1.5", -+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", -+ "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dependencies": { -- "string-width": "^1.0.2 || 2" -+ "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/wrappy": { -@@ -1901,7 +1894,7 @@ - "@microsoft/signalr": "5.0.10", - "@microsoft/signalr-protocol-msgpack": "5.0.10", - "@types/lunr": "^2.3.3", -- "@types/node": "^14.17.1", -+ "@types/node": "^16.11.12", - "@types/node-forge": "^0.9.7", - "@types/papaparse": "^5.2.5", - "@types/tldjs": "^2.3.0", -@@ -1912,21 +1905,21 @@ - "node-forge": "^0.10.0", - "papaparse": "^5.3.0", - "rimraf": "^3.0.2", -- "rxjs": "6.6.7", -+ "rxjs": "^7.4.0", - "tldjs": "^2.3.1", -- "typescript": "4.1.5", -+ "typescript": "4.3.5", - "zxcvbn": "^4.4.2" - } - }, - "@electron/get": { -- "version": "1.13.0", -- "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.13.0.tgz", -- "integrity": "sha512-+SjZhRuRo+STTO1Fdhzqnv9D2ZhjxXP6egsJ9kiO8dtP68cDx7dFCwWi64dlMQV7sWcfW1OYCW4wviEBzmRsfQ==", -+ "version": "1.13.1", -+ "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.13.1.tgz", -+ "integrity": "sha512-U5vkXDZ9DwXtkPqlB45tfYnnYBN8PePp1z/XDCupnSpdrxT8/ThCv9WCwPLf9oqiSGZTkH6dx2jDUPuoXpjkcA==", - "requires": { - "debug": "^4.1.1", - "env-paths": "^2.2.0", - "fs-extra": "^8.1.0", -- "global-agent": "^2.0.2", -+ "global-agent": "^3.0.0", - "global-tunnel-ng": "^2.7.1", - "got": "^9.6.0", - "progress": "^2.0.3", -@@ -1956,19 +1949,20 @@ - } - }, - "@types/node": { -- "version": "14.17.19", -- "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.19.tgz", -- "integrity": "sha512-jjYI6NkyfXykucU6ELEoT64QyKOdvaA6enOqKtP4xUsGY0X0ZUZz29fUmrTRo+7v7c6TgDu82q3GHHaCEkqZwA==" -+ "version": "16.11.12", -+ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", -+ "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==", -+ "dev": true - }, - "@types/semver": { -- "version": "7.3.8", -- "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.8.tgz", -- "integrity": "sha512-D/2EJvAlCEtYFEYmmlGwbGXuK886HzyCc3nZX/tkFTQdEU8jZDAgiv08P162yB17y4ZXZoq7yFAnW4GDBb9Now==" -+ "version": "7.3.9", -+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", -+ "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==" - }, - "ajv": { -- "version": "8.6.3", -- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", -- "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", -+ "version": "8.8.2", -+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", -+ "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", -@@ -2090,9 +2084,9 @@ - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "builder-util-runtime": { -- "version": "8.7.5", -- "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.7.5.tgz", -- "integrity": "sha512-fgUFHKtMNjdvH6PDRFntdIGUPgwZ69sXsAqEulCtoiqgWes5agrMq/Ud274zjJRTbckYh2PHh8/1CpFc6dpsbQ==", -+ "version": "8.9.1", -+ "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.9.1.tgz", -+ "integrity": "sha512-c8a8J3wK6BIVLW7ls+7TRK9igspTbzWmUqxFbgK0m40Ggm6efUbxtWVCGIjc+dtchyr5qAMAUL6iEGRdS/6vwg==", - "requires": { - "debug": "^4.3.2", - "sax": "^1.2.4" -@@ -2163,9 +2157,9 @@ - } - }, - "conf": { -- "version": "10.0.3", -- "resolved": "https://registry.npmjs.org/conf/-/conf-10.0.3.tgz", -- "integrity": "sha512-4gtQ/Q36qVxBzMe6B7gWOAfni1VdhuHkIzxydHkclnwGmgN+eW4bb6jj73vigCfr7d3WlmqawvhZrpCUCTPYxQ==", -+ "version": "10.1.1", -+ "resolved": "https://registry.npmjs.org/conf/-/conf-10.1.1.tgz", -+ "integrity": "sha512-z2civwq/k8TMYtcn3SVP0Peso4otIWnHtcTuHhQ0zDZDdP4NTxqEc8owfkz4zBsdMYdn/LFcE+ZhbCeqkhtq3Q==", - "requires": { - "ajv": "^8.6.3", - "ajv-formats": "^2.1.1", -@@ -2204,12 +2198,6 @@ - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, -- "core-js": { -- "version": "3.18.1", -- "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.18.1.tgz", -- "integrity": "sha512-vJlUi/7YdlCZeL6fXvWNaLUPh/id12WXj3MbkMw5uOyF0PfWPBNOCNbs53YqgrvtujLNlt9JQpruyIKkUZ+PKA==", -- "optional": true -- }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", -@@ -2224,9 +2212,9 @@ - } - }, - "debug": { -- "version": "4.3.2", -- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", -- "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", -+ "version": "4.3.3", -+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", -+ "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "requires": { - "ms": "2.1.2" - } -@@ -2288,13 +2276,20 @@ - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, - "electron": { -- "version": "14.2.0", -- "resolved": "https://registry.npmjs.org/electron/-/electron-14.2.0.tgz", -- "integrity": "sha512-6CmAv1P0xcwK3FQOSA27fHI36/wctSFVgj46VODn56srXXQWeolkK1VzeAFNE613iAuuH9jJdHvE3gz+c7XkNA==", -+ "version": "16.0.7", -+ "resolved": "https://registry.npmjs.org/electron/-/electron-16.0.7.tgz", -+ "integrity": "sha512-/IMwpBf2svhA1X/7Q58RV+Nn0fvUJsHniG4TizaO7q4iKFYSQ6hBvsLz+cylcZ8hRMKmVy5G1XaMNJID2ah23w==", - "requires": { -- "@electron/get": "^1.0.1", -+ "@electron/get": "^1.13.0", - "@types/node": "^14.6.2", - "extract-zip": "^1.0.3" -+ }, -+ "dependencies": { -+ "@types/node": { -+ "version": "14.18.0", -+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.0.tgz", -+ "integrity": "sha512-0GeIl2kmVMXEnx8tg1SlG6Gg8vkqirrW752KqolYo1PHevhhZN3bhJ67qHj+bQaINhX0Ra3TlWwRvMCd9iEfNQ==" -+ } - } - }, - "electron-log": { -@@ -2312,15 +2307,15 @@ - } - }, - "electron-updater": { -- "version": "4.3.9", -- "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.3.9.tgz", -- "integrity": "sha512-LCNfedSwZfS4Hza+pDyPR05LqHtGorCStaBgVpRnfKxOlZcvpYEX0AbMeH5XUtbtGRoH2V8osbbf2qKPNb7AsA==", -+ "version": "4.6.1", -+ "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.6.1.tgz", -+ "integrity": "sha512-YsU1mHqXLrXXmBMsxhxy24PrbaB8rnpZDPmFa2gOkTYk/Ch13+R0fjsRSpPYvqtskVVY0ux8fu+HnUkVkqc7og==", - "requires": { -- "@types/semver": "^7.3.5", -- "builder-util-runtime": "8.7.5", -+ "@types/semver": "^7.3.6", -+ "builder-util-runtime": "8.9.1", - "fs-extra": "^10.0.0", - "js-yaml": "^4.1.0", -- "lazy-val": "^1.0.4", -+ "lazy-val": "^1.0.5", - "lodash.escaperegexp": "^4.1.2", - "lodash.isequal": "^4.5.0", - "semver": "^7.3.5" -@@ -2521,13 +2516,12 @@ - } - }, - "global-agent": { -- "version": "2.2.0", -- "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.2.0.tgz", -- "integrity": "sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg==", -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", -+ "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", - "optional": true, - "requires": { - "boolean": "^3.0.1", -- "core-js": "^3.6.5", - "es6-error": "^4.1.1", - "matcher": "^3.0.0", - "roarr": "^2.15.3", -@@ -3131,9 +3125,9 @@ - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "signal-exit": { -- "version": "3.0.4", -- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.4.tgz", -- "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==" -+ "version": "3.0.6", -+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", -+ "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" - }, - "simple-concat": { - "version": "1.0.1", -@@ -3275,9 +3269,9 @@ - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, - "typescript": { -- "version": "4.1.5", -- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz", -- "integrity": "sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==", -+ "version": "4.3.5", -+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", -+ "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", - "dev": true - }, - "universalify": { -@@ -3312,11 +3306,11 @@ - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" - }, - "wide-align": { -- "version": "1.1.3", -- "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", -- "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", -+ "version": "1.1.5", -+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", -+ "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "requires": { -- "string-width": "^1.0.2 || 2" -+ "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "wrappy": { -diff --git a/jslib/electron/package.json b/jslib/electron/package.json -index ffd2c2f7..88bedfb9 100644 ---- a/jslib/electron/package.json -+++ b/jslib/electron/package.json -@@ -20,17 +20,17 @@ - "lint:fix": "tslint 'src/**/*.ts' 'spec/**/*.ts' --fix" - }, - "devDependencies": { -- "@types/node": "^14.17.1", -+ "@types/node": "^16.11.12", - "rimraf": "^3.0.2", -- "typescript": "4.1.5" -+ "typescript": "4.3.5" - }, - "dependencies": { - "@bitwarden/jslib-common": "file:../common", - "@nodert-win10-rs4/windows.security.credentials.ui": "^0.4.4", -- "electron": "14.2.0", -+ "electron": "16.0.7", - "electron-log": "4.4.1", - "electron-store": "8.0.1", -- "electron-updater": "4.3.9", -+ "electron-updater": "4.6.1", - "forcefocus": "^1.1.0", - "keytar": "7.7.0" - } -diff --git a/jslib/electron/src/baseMenu.ts b/jslib/electron/src/baseMenu.ts -index 7a2c66bf..f52db2a5 100644 ---- a/jslib/electron/src/baseMenu.ts -+++ b/jslib/electron/src/baseMenu.ts -@@ -1,229 +1,224 @@ --import { -- app, -- clipboard, -- dialog, -- Menu, -- MenuItemConstructorOptions, --} from 'electron'; -+import { app, clipboard, dialog, Menu, MenuItemConstructorOptions } from "electron"; - --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { WindowMain } from './window.main'; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { WindowMain } from "./window.main"; - - export class BaseMenu { -- protected editMenuItemOptions: MenuItemConstructorOptions; -- protected viewSubMenuItemOptions: MenuItemConstructorOptions[]; -- protected windowMenuItemOptions: MenuItemConstructorOptions; -- protected macAppMenuItemOptions: MenuItemConstructorOptions[]; -- protected macWindowSubmenuOptions: MenuItemConstructorOptions[]; -+ protected editMenuItemOptions: MenuItemConstructorOptions; -+ protected viewSubMenuItemOptions: MenuItemConstructorOptions[]; -+ protected windowMenuItemOptions: MenuItemConstructorOptions; -+ protected macAppMenuItemOptions: MenuItemConstructorOptions[]; -+ protected macWindowSubmenuOptions: MenuItemConstructorOptions[]; - -- constructor(protected i18nService: I18nService, protected windowMain: WindowMain) { } -+ constructor(protected i18nService: I18nService, protected windowMain: WindowMain) {} - -- protected initProperties() { -- this.editMenuItemOptions = { -- label: this.i18nService.t('edit'), -- submenu: [ -- { -- label: this.i18nService.t('undo'), -- role: 'undo', -- }, -- { -- label: this.i18nService.t('redo'), -- role: 'redo', -- }, -- { type: 'separator' }, -- { -- label: this.i18nService.t('cut'), -- role: 'cut', -- }, -- { -- label: this.i18nService.t('copy'), -- role: 'copy', -- }, -- { -- label: this.i18nService.t('paste'), -- role: 'paste', -- }, -- { type: 'separator' }, -- { -- label: this.i18nService.t('selectAll'), -- role: 'selectAll', -- }, -- ], -- }; -+ protected initProperties() { -+ this.editMenuItemOptions = { -+ label: this.i18nService.t("edit"), -+ submenu: [ -+ { -+ label: this.i18nService.t("undo"), -+ role: "undo", -+ }, -+ { -+ label: this.i18nService.t("redo"), -+ role: "redo", -+ }, -+ { type: "separator" }, -+ { -+ label: this.i18nService.t("cut"), -+ role: "cut", -+ }, -+ { -+ label: this.i18nService.t("copy"), -+ role: "copy", -+ }, -+ { -+ label: this.i18nService.t("paste"), -+ role: "paste", -+ }, -+ { type: "separator" }, -+ { -+ label: this.i18nService.t("selectAll"), -+ role: "selectAll", -+ }, -+ ], -+ }; - -- this.viewSubMenuItemOptions = [ -- { -- label: this.i18nService.t('zoomIn'), -- role: 'zoomIn', -- accelerator: 'CmdOrCtrl+=', -- }, -- { -- label: this.i18nService.t('zoomOut'), -- role: 'zoomOut', -- accelerator: 'CmdOrCtrl+-', -- }, -- { -- label: this.i18nService.t('resetZoom'), -- role: 'resetZoom', -- accelerator: 'CmdOrCtrl+0', -- }, -- { type: 'separator' }, -- { -- label: this.i18nService.t('toggleFullScreen'), -- role: 'togglefullscreen', -- }, -- { type: 'separator' }, -- { -- label: this.i18nService.t('reload'), -- role: 'forceReload', -- }, -- { -- label: this.i18nService.t('toggleDevTools'), -- role: 'toggleDevTools', -- accelerator: 'F12', -- }, -- ]; -+ this.viewSubMenuItemOptions = [ -+ { -+ label: this.i18nService.t("zoomIn"), -+ role: "zoomIn", -+ accelerator: "CmdOrCtrl+=", -+ }, -+ { -+ label: this.i18nService.t("zoomOut"), -+ role: "zoomOut", -+ accelerator: "CmdOrCtrl+-", -+ }, -+ { -+ label: this.i18nService.t("resetZoom"), -+ role: "resetZoom", -+ accelerator: "CmdOrCtrl+0", -+ }, -+ { type: "separator" }, -+ { -+ label: this.i18nService.t("toggleFullScreen"), -+ role: "togglefullscreen", -+ }, -+ { type: "separator" }, -+ { -+ label: this.i18nService.t("reload"), -+ role: "forceReload", -+ }, -+ { -+ label: this.i18nService.t("toggleDevTools"), -+ role: "toggleDevTools", -+ accelerator: "F12", -+ }, -+ ]; - -- this.windowMenuItemOptions = { -- label: this.i18nService.t('window'), -- role: 'window', -- submenu: [ -- { -- label: this.i18nService.t('minimize'), -- role: 'minimize', -- }, -- { -- label: this.i18nService.t('close'), -- role: 'close', -- }, -- ], -- }; -+ this.windowMenuItemOptions = { -+ label: this.i18nService.t("window"), -+ role: "window", -+ submenu: [ -+ { -+ label: this.i18nService.t("minimize"), -+ role: "minimize", -+ }, -+ { -+ label: this.i18nService.t("close"), -+ role: "close", -+ }, -+ ], -+ }; - -- if (process.platform === 'darwin') { -- this.macAppMenuItemOptions = [ -- { -- label: this.i18nService.t('services'), -- role: 'services', submenu: [], -- }, -- { type: 'separator' }, -- { -- label: this.i18nService.t('hideBitwarden'), -- role: 'hide', -- }, -- { -- label: this.i18nService.t('hideOthers'), -- role: 'hideOthers', -- }, -- { -- label: this.i18nService.t('showAll'), -- role: 'unhide', -- }, -- { type: 'separator' }, -- { -- label: this.i18nService.t('quitBitwarden'), -- role: 'quit', -- }, -- ]; -+ if (process.platform === "darwin") { -+ this.macAppMenuItemOptions = [ -+ { -+ label: this.i18nService.t("services"), -+ role: "services", -+ submenu: [], -+ }, -+ { type: "separator" }, -+ { -+ label: this.i18nService.t("hideBitwarden"), -+ role: "hide", -+ }, -+ { -+ label: this.i18nService.t("hideOthers"), -+ role: "hideOthers", -+ }, -+ { -+ label: this.i18nService.t("showAll"), -+ role: "unhide", -+ }, -+ { type: "separator" }, -+ { -+ label: this.i18nService.t("quitBitwarden"), -+ role: "quit", -+ }, -+ ]; - -- this.macWindowSubmenuOptions = [ -- { -- label: this.i18nService.t('minimize'), -- role: 'minimize', -- }, -- { -- label: this.i18nService.t('zoom'), -- role: 'zoom', -- }, -- { type: 'separator' }, -- { -- label: this.i18nService.t('bringAllToFront'), -- role: 'front', -- }, -- { -- label: this.i18nService.t('close'), -- role: 'close', -- }, -- ]; -- } -+ this.macWindowSubmenuOptions = [ -+ { -+ label: this.i18nService.t("minimize"), -+ role: "minimize", -+ }, -+ { -+ label: this.i18nService.t("zoom"), -+ role: "zoom", -+ }, -+ { type: "separator" }, -+ { -+ label: this.i18nService.t("bringAllToFront"), -+ role: "front", -+ }, -+ { -+ label: this.i18nService.t("close"), -+ role: "close", -+ }, -+ ]; - } -+ } - -- protected initContextMenu() { -- if (this.windowMain.win == null) { -- return; -- } -+ protected initContextMenu() { -+ if (this.windowMain.win == null) { -+ return; -+ } - -- const selectionMenu = Menu.buildFromTemplate([ -- { -- label: this.i18nService.t('copy'), -- role: 'copy', -- }, -- { type: 'separator' }, -- { -- label: this.i18nService.t('selectAll'), -- role: 'selectAll', -- }, -- ]); -+ const selectionMenu = Menu.buildFromTemplate([ -+ { -+ label: this.i18nService.t("copy"), -+ role: "copy", -+ }, -+ { type: "separator" }, -+ { -+ label: this.i18nService.t("selectAll"), -+ role: "selectAll", -+ }, -+ ]); - -- const inputMenu = Menu.buildFromTemplate([ -- { -- label: this.i18nService.t('undo'), -- role: 'undo', -- }, -- { -- label: this.i18nService.t('redo'), -- role: 'redo', -- }, -- { type: 'separator' }, -- { -- label: this.i18nService.t('cut'), -- role: 'cut', -- enabled: false, -- }, -- { -- label: this.i18nService.t('copy'), -- role: 'copy', -- enabled: false, -- }, -- { -- label: this.i18nService.t('paste'), -- role: 'paste', -- }, -- { type: 'separator' }, -- { -- label: this.i18nService.t('selectAll'), -- role: 'selectAll', -- }, -- ]); -+ const inputMenu = Menu.buildFromTemplate([ -+ { -+ label: this.i18nService.t("undo"), -+ role: "undo", -+ }, -+ { -+ label: this.i18nService.t("redo"), -+ role: "redo", -+ }, -+ { type: "separator" }, -+ { -+ label: this.i18nService.t("cut"), -+ role: "cut", -+ enabled: false, -+ }, -+ { -+ label: this.i18nService.t("copy"), -+ role: "copy", -+ enabled: false, -+ }, -+ { -+ label: this.i18nService.t("paste"), -+ role: "paste", -+ }, -+ { type: "separator" }, -+ { -+ label: this.i18nService.t("selectAll"), -+ role: "selectAll", -+ }, -+ ]); - -- const inputSelectionMenu = Menu.buildFromTemplate([ -- { -- label: this.i18nService.t('cut'), -- role: 'cut', -- }, -- { -- label: this.i18nService.t('copy'), -- role: 'copy', -- }, -- { -- label: this.i18nService.t('paste'), -- role: 'paste', -- }, -- { type: 'separator' }, -- { -- label: this.i18nService.t('selectAll'), -- role: 'selectAll', -- }, -- ]); -+ const inputSelectionMenu = Menu.buildFromTemplate([ -+ { -+ label: this.i18nService.t("cut"), -+ role: "cut", -+ }, -+ { -+ label: this.i18nService.t("copy"), -+ role: "copy", -+ }, -+ { -+ label: this.i18nService.t("paste"), -+ role: "paste", -+ }, -+ { type: "separator" }, -+ { -+ label: this.i18nService.t("selectAll"), -+ role: "selectAll", -+ }, -+ ]); - -- this.windowMain.win.webContents.on('context-menu', (e, props) => { -- const selected = props.selectionText && props.selectionText.trim() !== ''; -- if (props.isEditable && selected) { -- inputSelectionMenu.popup({ window: this.windowMain.win }); -- } else if (props.isEditable) { -- inputMenu.popup({ window: this.windowMain.win }); -- } else if (selected) { -- selectionMenu.popup({ window: this.windowMain.win }); -- } -- }); -- } -+ this.windowMain.win.webContents.on("context-menu", (e, props) => { -+ const selected = props.selectionText && props.selectionText.trim() !== ""; -+ if (props.isEditable && selected) { -+ inputSelectionMenu.popup({ window: this.windowMain.win }); -+ } else if (props.isEditable) { -+ inputMenu.popup({ window: this.windowMain.win }); -+ } else if (selected) { -+ selectionMenu.popup({ window: this.windowMain.win }); -+ } -+ }); -+ } - } -diff --git a/jslib/electron/src/biometric.darwin.main.ts b/jslib/electron/src/biometric.darwin.main.ts -index 26d3bd74..15588ac0 100644 ---- a/jslib/electron/src/biometric.darwin.main.ts -+++ b/jslib/electron/src/biometric.darwin.main.ts -@@ -1,37 +1,34 @@ --import { ipcMain, systemPreferences } from 'electron'; -+import { ipcMain, systemPreferences } from "electron"; - --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { StorageService } from 'jslib-common/abstractions/storage.service'; --import { ConstantsService } from 'jslib-common/services/constants.service'; -- --import { BiometricMain } from 'jslib-common/abstractions/biometric.main'; --import { ElectronConstants } from './electronConstants'; -+import { BiometricMain } from "jslib-common/abstractions/biometric.main"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; - - export default class BiometricDarwinMain implements BiometricMain { -- isError: boolean = false; -- -- constructor(private storageService: StorageService, private i18nservice: I18nService) {} -- -- async init() { -- this.storageService.save(ElectronConstants.enableBiometric, await this.supportsBiometric()); -- this.storageService.save(ConstantsService.biometricText, 'unlockWithTouchId'); -- this.storageService.save(ElectronConstants.noAutoPromptBiometricsText, 'noAutoPromptTouchId'); -- -- ipcMain.on('biometric', async (event: any, message: any) => { -- event.returnValue = await this.authenticateBiometric(); -- }); -- } -- -- supportsBiometric(): Promise { -- return Promise.resolve(systemPreferences.canPromptTouchID()); -- } -- -- async authenticateBiometric(): Promise { -- try { -- await systemPreferences.promptTouchID(this.i18nservice.t('touchIdConsentMessage')); -- return true; -- } catch { -- return false; -- } -+ isError: boolean = false; -+ -+ constructor(private i18nservice: I18nService, private stateService: StateService) {} -+ -+ async init() { -+ await this.stateService.setEnableBiometric(await this.supportsBiometric()); -+ await this.stateService.setBiometricText("unlockWithTouchId"); -+ await this.stateService.setNoAutoPromptBiometricsText("noAutoPromptTouchId"); -+ -+ ipcMain.on("biometric", async (event: any, message: any) => { -+ event.returnValue = await this.authenticateBiometric(); -+ }); -+ } -+ -+ supportsBiometric(): Promise { -+ return Promise.resolve(systemPreferences.canPromptTouchID()); -+ } -+ -+ async authenticateBiometric(): Promise { -+ try { -+ await systemPreferences.promptTouchID(this.i18nservice.t("touchIdConsentMessage")); -+ return true; -+ } catch { -+ return false; - } -+ } - } -diff --git a/jslib/electron/src/biometric.windows.main.ts b/jslib/electron/src/biometric.windows.main.ts -index d33875b7..bb6288b8 100644 ---- a/jslib/electron/src/biometric.windows.main.ts -+++ b/jslib/electron/src/biometric.windows.main.ts -@@ -1,135 +1,144 @@ --import { ipcMain } from 'electron'; --import forceFocus from 'forcefocus'; -+import { ipcMain } from "electron"; -+import forceFocus from "forcefocus"; - --import { ElectronConstants } from './electronConstants'; --import { WindowMain } from './window.main'; -+import { WindowMain } from "./window.main"; - --import { BiometricMain } from 'jslib-common/abstractions/biometric.main'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { LogService } from 'jslib-common/abstractions/log.service'; --import { StorageService } from 'jslib-common/abstractions/storage.service'; --import { ConstantsService } from 'jslib-common/services/constants.service'; -+import { BiometricMain } from "jslib-common/abstractions/biometric.main"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; - - export default class BiometricWindowsMain implements BiometricMain { -- isError: boolean = false; -- -- private windowsSecurityCredentialsUiModule: any; -- -- constructor(private storageService: StorageService, private i18nservice: I18nService, private windowMain: WindowMain, -- private logService: LogService) { } -- -- async init() { -- this.windowsSecurityCredentialsUiModule = this.getWindowsSecurityCredentialsUiModule(); -- let supportsBiometric = false; -- try { -- supportsBiometric = await this.supportsBiometric(); -- } catch { -- // store error state so we can let the user know on the settings page -- this.isError = true; -- } -- this.storageService.save(ElectronConstants.enableBiometric, supportsBiometric); -- this.storageService.save(ConstantsService.biometricText, 'unlockWithWindowsHello'); -- this.storageService.save(ElectronConstants.noAutoPromptBiometricsText, 'noAutoPromptWindowsHello'); -- -- ipcMain.on('biometric', async (event: any, message: any) => { -- event.returnValue = await this.authenticateBiometric(); -- }); -+ isError: boolean = false; -+ -+ private windowsSecurityCredentialsUiModule: any; -+ -+ constructor( -+ private i18nservice: I18nService, -+ private windowMain: WindowMain, -+ private stateService: StateService, -+ private logService: LogService -+ ) {} -+ -+ async init() { -+ this.windowsSecurityCredentialsUiModule = this.getWindowsSecurityCredentialsUiModule(); -+ let supportsBiometric = false; -+ try { -+ supportsBiometric = await this.supportsBiometric(); -+ } catch { -+ // store error state so we can let the user know on the settings page -+ this.isError = true; - } -+ await this.stateService.setEnableBiometric(supportsBiometric); -+ await this.stateService.setBiometricText("unlockWithWindowsHello"); -+ await this.stateService.setNoAutoPromptBiometricsText("noAutoPromptWindowsHello"); - -- async supportsBiometric(): Promise { -- const availability = await this.checkAvailabilityAsync(); -+ ipcMain.on("biometric", async (event: any, message: any) => { -+ event.returnValue = await this.authenticateBiometric(); -+ }); -+ } - -- return this.getAllowedAvailabilities().includes(availability); -- } -+ async supportsBiometric(): Promise { -+ const availability = await this.checkAvailabilityAsync(); - -- async authenticateBiometric(): Promise { -- const module = this.getWindowsSecurityCredentialsUiModule(); -- if (module == null) { -- return false; -- } -+ return this.getAllowedAvailabilities().includes(availability); -+ } - -- const verification = await this.requestVerificationAsync(this.i18nservice.t('windowsHelloConsentMessage')); -+ async authenticateBiometric(): Promise { -+ const module = this.getWindowsSecurityCredentialsUiModule(); -+ if (module == null) { -+ return false; -+ } - -- return verification === module.UserConsentVerificationResult.verified; -+ const verification = await this.requestVerificationAsync( -+ this.i18nservice.t("windowsHelloConsentMessage") -+ ); -+ -+ return verification === module.UserConsentVerificationResult.verified; -+ } -+ -+ getWindowsSecurityCredentialsUiModule(): any { -+ try { -+ if (this.windowsSecurityCredentialsUiModule == null && this.getWindowsMajorVersion() >= 10) { -+ this.windowsSecurityCredentialsUiModule = require("@nodert-win10-rs4/windows.security.credentials.ui"); -+ } -+ return this.windowsSecurityCredentialsUiModule; -+ } catch { -+ this.isError = true; - } -+ return null; -+ } - -- getWindowsSecurityCredentialsUiModule(): any { -+ async checkAvailabilityAsync(): Promise { -+ const module = this.getWindowsSecurityCredentialsUiModule(); -+ if (module != null) { -+ return new Promise((resolve, reject) => { - try { -- if (this.windowsSecurityCredentialsUiModule == null && this.getWindowsMajorVersion() >= 10) { -- this.windowsSecurityCredentialsUiModule = require('@nodert-win10-rs4/windows.security.credentials.ui'); -+ module.UserConsentVerifier.checkAvailabilityAsync((error: Error, result: any) => { -+ if (error) { -+ return resolve(null); - } -- return this.windowsSecurityCredentialsUiModule; -+ return resolve(result); -+ }); - } catch { -- this.isError = true; -+ this.isError = true; -+ return resolve(null); - } -- return null; -+ }); - } -+ return Promise.resolve(null); -+ } - -- async checkAvailabilityAsync(): Promise { -- const module = this.getWindowsSecurityCredentialsUiModule(); -- if (module != null) { -- return new Promise((resolve, reject) => { -- try { -- module.UserConsentVerifier.checkAvailabilityAsync((error: Error, result: any) => { -- if (error) { -- return resolve(null); -- } -- return resolve(result); -- }); -- } catch { -- this.isError = true; -- return resolve(null); -- } -- }); -- } -- return Promise.resolve(null); -- } -+ async requestVerificationAsync(message: string): Promise { -+ const module = this.getWindowsSecurityCredentialsUiModule(); -+ if (module != null) { -+ return new Promise((resolve, reject) => { -+ try { -+ module.UserConsentVerifier.requestVerificationAsync( -+ message, -+ (error: Error, result: any) => { -+ if (error) { -+ return resolve(null); -+ } -+ return resolve(result); -+ } -+ ); - -- async requestVerificationAsync(message: string): Promise { -- const module = this.getWindowsSecurityCredentialsUiModule(); -- if (module != null) { -- return new Promise((resolve, reject) => { -- try { -- module.UserConsentVerifier.requestVerificationAsync(message, (error: Error, result: any) => { -- if (error) { -- return resolve(null); -- } -- return resolve(result); -- }); -- -- forceFocus.focusWindow(this.windowMain.win); -- } catch (error) { -- this.isError = true; -- return reject(error); -- } -- }); -+ forceFocus.focusWindow(this.windowMain.win); -+ } catch (error) { -+ this.isError = true; -+ return reject(error); - } -- return Promise.resolve(null); -+ }); - } -- -- getAllowedAvailabilities(): any[] { -- try { -- const module = this.getWindowsSecurityCredentialsUiModule(); -- if (module != null) { -- return [ -- module.UserConsentVerifierAvailability.available, -- module.UserConsentVerifierAvailability.deviceBusy, -- ]; -- } -- } catch { /*Ignore error*/ } -- return []; -+ return Promise.resolve(null); -+ } -+ -+ getAllowedAvailabilities(): any[] { -+ try { -+ const module = this.getWindowsSecurityCredentialsUiModule(); -+ if (module != null) { -+ return [ -+ module.UserConsentVerifierAvailability.available, -+ module.UserConsentVerifierAvailability.deviceBusy, -+ ]; -+ } -+ } catch { -+ /*Ignore error*/ - } -+ return []; -+ } - -- getWindowsMajorVersion(): number { -- if (process.platform !== 'win32') { -- return -1; -- } -- try { -- const version = require('os').release(); -- return Number.parseInt(version.split('.')[0], 10); -- } catch { -- this.logService.error('Unable to resolve windows major version number'); -- } -- return -1; -+ getWindowsMajorVersion(): number { -+ if (process.platform !== "win32") { -+ return -1; -+ } -+ try { -+ const version = require("os").release(); -+ return Number.parseInt(version.split(".")[0], 10); -+ } catch { -+ this.logService.error("Unable to resolve windows major version number"); - } -+ return -1; -+ } - } -diff --git a/jslib/electron/src/electronConstants.ts b/jslib/electron/src/electronConstants.ts -deleted file mode 100644 -index 68be1956..00000000 ---- a/jslib/electron/src/electronConstants.ts -+++ /dev/null -@@ -1,14 +0,0 @@ --export class ElectronConstants { -- static readonly enableMinimizeToTrayKey: string = 'enableMinimizeToTray'; -- static readonly enableCloseToTrayKey: string = 'enableCloseToTray'; -- static readonly enableTrayKey: string = 'enableTray'; -- static readonly enableStartToTrayKey: string = 'enableStartToTrayKey'; -- static readonly enableAlwaysOnTopKey: string = 'enableAlwaysOnTopKey'; -- static readonly minimizeOnCopyToClipboardKey: string = 'minimizeOnCopyToClipboardKey'; -- static readonly enableBiometric: string = 'enabledBiometric'; -- static readonly enableBrowserIntegration: string = 'enableBrowserIntegration'; -- static readonly enableBrowserIntegrationFingerprint: string = 'enableBrowserIntegrationFingerprint'; -- static readonly alwaysShowDock: string = 'alwaysShowDock'; -- static readonly openAtLogin: string = 'openAtLogin'; -- static readonly noAutoPromptBiometricsText: string = 'noAutoPromptBiometricsText'; --} -diff --git a/jslib/electron/src/globals.d.ts b/jslib/electron/src/globals.d.ts -index d94bfae2..1b85bb1b 100644 ---- a/jslib/electron/src/globals.d.ts -+++ b/jslib/electron/src/globals.d.ts -@@ -1 +1 @@ --declare module 'forcefocus'; -+declare module "forcefocus"; -diff --git a/jslib/electron/src/keytarStorageListener.ts b/jslib/electron/src/keytarStorageListener.ts -index fb263cb3..a8e8ec4a 100644 ---- a/jslib/electron/src/keytarStorageListener.ts -+++ b/jslib/electron/src/keytarStorageListener.ts -@@ -1,56 +1,52 @@ --import { ipcMain } from 'electron'; -+import { ipcMain } from "electron"; - --import { -- deletePassword, -- getPassword, -- setPassword, --} from 'keytar'; -+import { deletePassword, getPassword, setPassword } from "keytar"; - --import { BiometricMain } from 'jslib-common/abstractions/biometric.main'; -+import { BiometricMain } from "jslib-common/abstractions/biometric.main"; - --const AuthRequiredSuffix = '_biometric'; --const AuthenticatedActions = ['getPassword']; -+const AuthRequiredSuffix = "_biometric"; -+const AuthenticatedActions = ["getPassword"]; - - export class KeytarStorageListener { -- constructor(private serviceName: string, private biometricService: BiometricMain) { } -- -- init() { -- ipcMain.on('keytar', async (event: any, message: any) => { -- try { -- let serviceName = this.serviceName; -- message.keySuffix = '_' + (message.keySuffix ?? ''); -- if (message.keySuffix !== '_') { -- serviceName += message.keySuffix; -- } -- -- const authenticationRequired = AuthenticatedActions.includes(message.action) && -- AuthRequiredSuffix === message.keySuffix; -- const authenticated = !authenticationRequired || await this.authenticateBiometric(); -- -- let val: string | boolean = null; -- if (authenticated && message.action && message.key) { -- if (message.action === 'getPassword') { -- val = await getPassword(serviceName, message.key); -- } else if (message.action === 'hasPassword') { -- const result = await getPassword(serviceName, message.key); -- val = result != null; -- } else if (message.action === 'setPassword' && message.value) { -- await setPassword(serviceName, message.key, message.value); -- } else if (message.action === 'deletePassword') { -- await deletePassword(serviceName, message.key); -- } -- } -- event.returnValue = val; -- } catch { -- event.returnValue = null; -- } -- }); -- } -+ constructor(private serviceName: string, private biometricService: BiometricMain) {} -+ -+ init() { -+ ipcMain.on("keytar", async (event: any, message: any) => { -+ try { -+ let serviceName = this.serviceName; -+ message.keySuffix = "_" + (message.keySuffix ?? ""); -+ if (message.keySuffix !== "_") { -+ serviceName += message.keySuffix; -+ } - -- private async authenticateBiometric(): Promise { -- if (this.biometricService) { -- return await this.biometricService.authenticateBiometric(); -+ const authenticationRequired = -+ AuthenticatedActions.includes(message.action) && AuthRequiredSuffix === message.keySuffix; -+ const authenticated = !authenticationRequired || (await this.authenticateBiometric()); -+ -+ let val: string | boolean = null; -+ if (authenticated && message.action && message.key) { -+ if (message.action === "getPassword") { -+ val = await getPassword(serviceName, message.key); -+ } else if (message.action === "hasPassword") { -+ const result = await getPassword(serviceName, message.key); -+ val = result != null; -+ } else if (message.action === "setPassword" && message.value) { -+ await setPassword(serviceName, message.key, message.value); -+ } else if (message.action === "deletePassword") { -+ await deletePassword(serviceName, message.key); -+ } - } -- return false; -+ event.returnValue = val; -+ } catch { -+ event.returnValue = null; -+ } -+ }); -+ } -+ -+ private async authenticateBiometric(): Promise { -+ if (this.biometricService) { -+ return await this.biometricService.authenticateBiometric(); - } -+ return false; -+ } - } -diff --git a/jslib/electron/src/services/electronCrypto.service.ts b/jslib/electron/src/services/electronCrypto.service.ts -index dc602e0f..7eb01e49 100644 ---- a/jslib/electron/src/services/electronCrypto.service.ts -+++ b/jslib/electron/src/services/electronCrypto.service.ts -@@ -1,66 +1,74 @@ --import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; --import { LogService } from 'jslib-common/abstractions/log.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { KeySuffixOptions, StorageService } from 'jslib-common/abstractions/storage.service'; --import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey'; --import { CryptoService, Keys } from 'jslib-common/services/crypto.service'; -+import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; - --export class ElectronCryptoService extends CryptoService { -+import { CryptoService } from "jslib-common/services/crypto.service"; - -- constructor(storageService: StorageService, secureStorageService: StorageService, -- cryptoFunctionService: CryptoFunctionService, platformUtilService: PlatformUtilsService, -- logService: LogService) { -- super(storageService, secureStorageService, cryptoFunctionService, platformUtilService, logService); -- } -+import { KeySuffixOptions } from "jslib-common/enums/keySuffixOptions"; -+import { StorageLocation } from "jslib-common/enums/storageLocation"; -+import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey"; - -- async hasKeyStored(keySuffix: KeySuffixOptions): Promise { -- await this.upgradeSecurelyStoredKey(); -- return super.hasKeyStored(keySuffix); -- } -+export class ElectronCryptoService extends CryptoService { -+ constructor( -+ cryptoFunctionService: CryptoFunctionService, -+ platformUtilService: PlatformUtilsService, -+ logService: LogService, -+ stateService: StateService -+ ) { -+ super(cryptoFunctionService, platformUtilService, logService, stateService); -+ } - -- protected async storeKey(key: SymmetricCryptoKey) { -- if (await this.shouldStoreKey('auto')) { -- await this.secureStorageService.save(Keys.key, key.keyB64, { keySuffix: 'auto' }); -- } else { -- this.clearStoredKey('auto'); -- } -+ async hasKeyStored(keySuffix: KeySuffixOptions): Promise { -+ await this.upgradeSecurelyStoredKey(); -+ return super.hasKeyStored(keySuffix); -+ } - -- if (await this.shouldStoreKey('biometric')) { -- await this.secureStorageService.save(Keys.key, key.keyB64, { keySuffix: 'biometric' }); -- } else { -- this.clearStoredKey('biometric'); -- } -+ protected async storeKey(key: SymmetricCryptoKey, userId?: string) { -+ if (await this.shouldStoreKey(KeySuffixOptions.Auto, userId)) { -+ await this.stateService.setCryptoMasterKeyAuto(key.keyB64, { userId: userId }); -+ } else { -+ this.clearStoredKey(KeySuffixOptions.Auto); - } - -- protected async retrieveKeyFromStorage(keySuffix: KeySuffixOptions) { -- await this.upgradeSecurelyStoredKey(); -- return super.retrieveKeyFromStorage(keySuffix); -+ if (await this.shouldStoreKey(KeySuffixOptions.Biometric, userId)) { -+ await this.stateService.setCryptoMasterKeyBiometric(key.keyB64, { userId: userId }); -+ } else { -+ this.clearStoredKey(KeySuffixOptions.Biometric); - } -+ } - -- /** -- * @deprecated 4 Jun 2021 This is temporary upgrade method to move from a single shared stored key to -- * multiple, unique stored keys for each use, e.g. never logout vs. biometric authentication. -- */ -- private async upgradeSecurelyStoredKey() { -- // attempt key upgrade, but if we fail just delete it. Keys will be stored property upon unlock anyway. -- const key = await this.secureStorageService.get(Keys.key); -+ protected async retrieveKeyFromStorage(keySuffix: KeySuffixOptions, userId?: string) { -+ await this.upgradeSecurelyStoredKey(); -+ return super.retrieveKeyFromStorage(keySuffix, userId); -+ } - -- if (key == null) { -- return; -- } -+ /** -+ * @deprecated 4 Jun 2021 This is temporary upgrade method to move from a single shared stored key to -+ * multiple, unique stored keys for each use, e.g. never logout vs. biometric authentication. -+ */ -+ private async upgradeSecurelyStoredKey() { -+ // attempt key upgrade, but if we fail just delete it. Keys will be stored property upon unlock anyway. -+ const key = await this.stateService.getCryptoMasterKeyB64(); - -- try { -- if (await this.shouldStoreKey('auto')) { -- await this.secureStorageService.save(Keys.key, key, { keySuffix: 'auto' }); -- } -- if (await this.shouldStoreKey('biometric')) { -- await this.secureStorageService.save(Keys.key, key, { keySuffix: 'biometric' }); -- } -- } catch (e) { -- this.logService.error(`Encountered error while upgrading obsolete Bitwarden secure storage item:`); -- this.logService.error(e); -- } -+ if (key == null) { -+ return; -+ } - -- await this.secureStorageService.remove(Keys.key); -+ try { -+ if (await this.shouldStoreKey(KeySuffixOptions.Auto)) { -+ await this.stateService.setCryptoMasterKeyAuto(key); -+ } -+ if (await this.shouldStoreKey(KeySuffixOptions.Biometric)) { -+ await this.stateService.setCryptoMasterKeyBiometric(key); -+ } -+ } catch (e) { -+ this.logService.error( -+ `Encountered error while upgrading obsolete Bitwarden secure storage item:` -+ ); -+ this.logService.error(e); - } -+ -+ await this.stateService.setCryptoMasterKeyB64(null); -+ } - } -diff --git a/jslib/electron/src/services/electronLog.service.ts b/jslib/electron/src/services/electronLog.service.ts -index 57eeb0d4..008678ae 100644 ---- a/jslib/electron/src/services/electronLog.service.ts -+++ b/jslib/electron/src/services/electronLog.service.ts -@@ -1,46 +1,45 @@ --import log from 'electron-log'; --import * as path from 'path'; -+import log from "electron-log"; -+import * as path from "path"; - --import { isDev } from '../utils'; -+import { isDev } from "../utils"; - --import { LogLevelType } from 'jslib-common/enums/logLevelType'; -+import { LogLevelType } from "jslib-common/enums/logLevelType"; - --import { ConsoleLogService as BaseLogService } from 'jslib-common/services/consoleLog.service'; -+import { ConsoleLogService as BaseLogService } from "jslib-common/services/consoleLog.service"; - - export class ElectronLogService extends BaseLogService { -+ constructor(protected filter: (level: LogLevelType) => boolean = null, logDir: string = null) { -+ super(isDev(), filter); -+ if (log.transports == null) { -+ return; -+ } - -- constructor(protected filter: (level: LogLevelType) => boolean = null, logDir: string = null) { -- super(isDev(), filter); -- if (log.transports == null) { -- return; -- } -- -- log.transports.file.level = 'info'; -- if (logDir != null) { -- log.transports.file.file = path.join(logDir, 'app.log'); -- } -+ log.transports.file.level = "info"; -+ if (logDir != null) { -+ log.transports.file.file = path.join(logDir, "app.log"); - } -+ } - -- write(level: LogLevelType, message: string) { -- if (this.filter != null && this.filter(level)) { -- return; -- } -+ write(level: LogLevelType, message: string) { -+ if (this.filter != null && this.filter(level)) { -+ return; -+ } - -- switch (level) { -- case LogLevelType.Debug: -- log.debug(message); -- break; -- case LogLevelType.Info: -- log.info(message); -- break; -- case LogLevelType.Warning: -- log.warn(message); -- break; -- case LogLevelType.Error: -- log.error(message); -- break; -- default: -- break; -- } -+ switch (level) { -+ case LogLevelType.Debug: -+ log.debug(message); -+ break; -+ case LogLevelType.Info: -+ log.info(message); -+ break; -+ case LogLevelType.Warning: -+ log.warn(message); -+ break; -+ case LogLevelType.Error: -+ log.error(message); -+ break; -+ default: -+ break; - } -+ } - } -diff --git a/jslib/electron/src/services/electronMainMessaging.service.ts b/jslib/electron/src/services/electronMainMessaging.service.ts -index d58d3b09..99dd5b32 100644 ---- a/jslib/electron/src/services/electronMainMessaging.service.ts -+++ b/jslib/electron/src/services/electronMainMessaging.service.ts -@@ -1,58 +1,66 @@ --import { app, dialog, ipcMain, Menu, MenuItem, nativeTheme } from 'electron'; --import { promises as fs } from 'fs'; --import { MessagingService } from 'jslib-common/abstractions/messaging.service'; --import { RendererMenuItem } from '../utils'; -+import { app, dialog, ipcMain, Menu, MenuItem, nativeTheme } from "electron"; -+import { promises as fs } from "fs"; -+import { MessagingService } from "jslib-common/abstractions/messaging.service"; -+import { RendererMenuItem } from "../utils"; - --import { ThemeType } from 'jslib-common/enums/themeType'; -+import { ThemeType } from "jslib-common/enums/themeType"; - --import { WindowMain } from '../window.main'; -+import { WindowMain } from "../window.main"; - - export class ElectronMainMessagingService implements MessagingService { -- constructor(private windowMain: WindowMain, private onMessage: (message: any) => void) { -- ipcMain.handle('appVersion', () => { -- return app.getVersion(); -- }); -+ constructor(private windowMain: WindowMain, private onMessage: (message: any) => void) { -+ ipcMain.handle("appVersion", () => { -+ return app.getVersion(); -+ }); - -- ipcMain.handle('systemTheme', () => { -- return nativeTheme.shouldUseDarkColors ? ThemeType.Dark : ThemeType.Light; -- }); -+ ipcMain.handle("systemTheme", () => { -+ return nativeTheme.shouldUseDarkColors ? ThemeType.Dark : ThemeType.Light; -+ }); - -- ipcMain.handle('showMessageBox', (event, options) => { -- return dialog.showMessageBox(options); -- }); -+ ipcMain.handle("showMessageBox", (event, options) => { -+ return dialog.showMessageBox(options); -+ }); - -- ipcMain.handle('openContextMenu', (event, options: {menu: RendererMenuItem[]}) => { -- return new Promise(resolve => { -- const menu = new Menu(); -- options.menu.forEach((m, index) => { -- menu.append(new MenuItem({ -- label: m.label, -- type: m.type, -- click: () => { -- resolve(index); -- }, -- })); -- }); -- menu.popup({ window: windowMain.win, callback: () => { -- resolve(-1); -- }}); -- }); -+ ipcMain.handle("openContextMenu", (event, options: { menu: RendererMenuItem[] }) => { -+ return new Promise((resolve) => { -+ const menu = new Menu(); -+ options.menu.forEach((m, index) => { -+ menu.append( -+ new MenuItem({ -+ label: m.label, -+ type: m.type, -+ click: () => { -+ resolve(index); -+ }, -+ }) -+ ); - }); -- -- ipcMain.handle('windowVisible', () => { -- return windowMain.win?.isVisible(); -+ menu.popup({ -+ window: windowMain.win, -+ callback: () => { -+ resolve(-1); -+ }, - }); -+ }); -+ }); - -- nativeTheme.on('updated', () => { -- windowMain.win?.webContents.send('systemThemeUpdated', nativeTheme.shouldUseDarkColors ? ThemeType.Dark : ThemeType.Light); -- }); -- } -+ ipcMain.handle("windowVisible", () => { -+ return windowMain.win?.isVisible(); -+ }); -+ -+ nativeTheme.on("updated", () => { -+ windowMain.win?.webContents.send( -+ "systemThemeUpdated", -+ nativeTheme.shouldUseDarkColors ? ThemeType.Dark : ThemeType.Light -+ ); -+ }); -+ } - -- send(subscriber: string, arg: any = {}) { -- const message = Object.assign({}, { command: subscriber }, arg); -- this.onMessage(message); -- if (this.windowMain.win != null) { -- this.windowMain.win.webContents.send('messagingService', message); -- } -+ send(subscriber: string, arg: any = {}) { -+ const message = Object.assign({}, { command: subscriber }, arg); -+ this.onMessage(message); -+ if (this.windowMain.win != null) { -+ this.windowMain.win.webContents.send("messagingService", message); - } -+ } - } -diff --git a/jslib/electron/src/services/electronPlatformUtils.service.ts b/jslib/electron/src/services/electronPlatformUtils.service.ts -index 7ffe3e3f..7ec81327 100644 ---- a/jslib/electron/src/services/electronPlatformUtils.service.ts -+++ b/jslib/electron/src/services/electronPlatformUtils.service.ts -@@ -1,214 +1,218 @@ --import { -- clipboard, -- ipcRenderer, -- shell, --} from 'electron'; -+import { clipboard, ipcRenderer, shell } from "electron"; - --import { -- isDev, -- isMacAppStore, --} from '../utils'; -+import { isDev, isMacAppStore } from "../utils"; - --import { DeviceType } from 'jslib-common/enums/deviceType'; --import { ThemeType } from 'jslib-common/enums/themeType'; -+import { DeviceType } from "jslib-common/enums/deviceType"; -+import { ThemeType } from "jslib-common/enums/themeType"; - --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { MessagingService } from 'jslib-common/abstractions/messaging.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { StorageService } from 'jslib-common/abstractions/storage.service'; -- --import { ConstantsService } from 'jslib-common/services/constants.service'; -- --import { ElectronConstants } from '../electronConstants'; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { MessagingService } from "jslib-common/abstractions/messaging.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; - - export class ElectronPlatformUtilsService implements PlatformUtilsService { -- identityClientId: string; -- -- private deviceCache: DeviceType = null; -- -- constructor(protected i18nService: I18nService, private messagingService: MessagingService, -- private isDesktopApp: boolean, private storageService: StorageService) { -- this.identityClientId = isDesktopApp ? 'desktop' : 'connector'; -- } -- -- getDevice(): DeviceType { -- if (!this.deviceCache) { -- switch (process.platform) { -- case 'win32': -- this.deviceCache = DeviceType.WindowsDesktop; -- break; -- case 'darwin': -- this.deviceCache = DeviceType.MacOsDesktop; -- break; -- case 'linux': -- default: -- this.deviceCache = DeviceType.LinuxDesktop; -- break; -- } -- } -- -- return this.deviceCache; -- } -- -- getDeviceString(): string { -- const device = DeviceType[this.getDevice()].toLowerCase(); -- return device.replace('desktop', ''); -- } -- -- isFirefox(): boolean { -- return false; -- } -- -- isChrome(): boolean { -- return true; -- } -- -- isEdge(): boolean { -- return false; -- } -- -- isOpera(): boolean { -- return false; -- } -- -- isVivaldi(): boolean { -- return false; -- } -- -- isSafari(): boolean { -- return false; -- } -- -- isIE(): boolean { -- return false; -- } -- -- isMacAppStore(): boolean { -- return isMacAppStore(); -- } -- -- isViewOpen(): Promise { -- return Promise.resolve(false); -- } -- -- launchUri(uri: string, options?: any): void { -- shell.openExternal(uri); -- } -- -- saveFile(win: Window, blobData: any, blobOptions: any, fileName: string): void { -- const blob = new Blob([blobData], blobOptions); -- const a = win.document.createElement('a'); -- a.href = URL.createObjectURL(blob); -- a.download = fileName; -- win.document.body.appendChild(a); -- a.click(); -- win.document.body.removeChild(a); -- } -- -- getApplicationVersion(): Promise { -- return ipcRenderer.invoke('appVersion'); -- } -- -- // Temporarily restricted to only Windows until https://github.com/electron/electron/pull/28349 -- // has been merged and an updated electron build is available. -- supportsWebAuthn(win: Window): boolean { -- return process.platform === 'win32'; -- } -- -- supportsDuo(): boolean { -- return true; -- } -- -- showToast(type: 'error' | 'success' | 'warning' | 'info', title: string, text: string | string[], -- options?: any): void { -- this.messagingService.send('showToast', { -- text: text, -- title: title, -- type: type, -- options: options, -- }); -- } -- -- async showDialog(text: string, title?: string, confirmText?: string, cancelText?: string, type?: string): -- Promise { -- const buttons = [confirmText == null ? this.i18nService.t('ok') : confirmText]; -- if (cancelText != null) { -- buttons.push(cancelText); -- } -- -- const result = await ipcRenderer.invoke('showMessageBox', { -- type: type, -- title: title, -- message: title, -- detail: text, -- buttons: buttons, -- cancelId: buttons.length === 2 ? 1 : null, -- defaultId: 0, -- noLink: true, -- }); -- -- return Promise.resolve(result.response === 0); -- } -- -- isDev(): boolean { -- return isDev(); -- } -- -- isSelfHost(): boolean { -- return false; -- } -- -- copyToClipboard(text: string, options?: any): void { -- const type = options ? options.type : null; -- const clearing = options ? !!options.clearing : false; -- const clearMs: number = options && options.clearMs ? options.clearMs : null; -- clipboard.writeText(text, type); -- if (!clearing) { -- this.messagingService.send('copiedToClipboard', { -- clipboardValue: text, -- clearMs: clearMs, -- type: type, -- clearing: clearing, -- }); -- } -- } -- -- readFromClipboard(options?: any): Promise { -- const type = options ? options.type : null; -- return Promise.resolve(clipboard.readText(type)); -- } -- -- supportsBiometric(): Promise { -- return this.storageService.get(ElectronConstants.enableBiometric); -- } -- -- authenticateBiometric(): Promise { -- return new Promise(resolve => { -- const val = ipcRenderer.sendSync('biometric', { -- action: 'authenticate', -- }); -- resolve(val); -- }); -- } -- -- getDefaultSystemTheme() { -- return ipcRenderer.invoke('systemTheme'); -- } -- -- onDefaultSystemThemeChange(callback: ((theme: ThemeType.Light | ThemeType.Dark) => unknown)) { -- ipcRenderer.on('systemThemeUpdated', (event, theme: ThemeType.Light | ThemeType.Dark) => callback(theme)); -- } -- -- async getEffectiveTheme() { -- const theme = await this.storageService.get(ConstantsService.themeKey); -- if (theme == null || theme === ThemeType.System) { -- return this.getDefaultSystemTheme(); -- } else { -- return theme; -- } -- } -- -- supportsSecureStorage(): boolean { -- return true; -- } -+ identityClientId: string; -+ -+ private deviceCache: DeviceType = null; -+ -+ constructor( -+ protected i18nService: I18nService, -+ private messagingService: MessagingService, -+ private isDesktopApp: boolean, -+ private stateService: StateService -+ ) { -+ this.identityClientId = isDesktopApp ? "desktop" : "connector"; -+ } -+ -+ getDevice(): DeviceType { -+ if (!this.deviceCache) { -+ switch (process.platform) { -+ case "win32": -+ this.deviceCache = DeviceType.WindowsDesktop; -+ break; -+ case "darwin": -+ this.deviceCache = DeviceType.MacOsDesktop; -+ break; -+ case "linux": -+ default: -+ this.deviceCache = DeviceType.LinuxDesktop; -+ break; -+ } -+ } -+ -+ return this.deviceCache; -+ } -+ -+ getDeviceString(): string { -+ const device = DeviceType[this.getDevice()].toLowerCase(); -+ return device.replace("desktop", ""); -+ } -+ -+ isFirefox(): boolean { -+ return false; -+ } -+ -+ isChrome(): boolean { -+ return true; -+ } -+ -+ isEdge(): boolean { -+ return false; -+ } -+ -+ isOpera(): boolean { -+ return false; -+ } -+ -+ isVivaldi(): boolean { -+ return false; -+ } -+ -+ isSafari(): boolean { -+ return false; -+ } -+ -+ isIE(): boolean { -+ return false; -+ } -+ -+ isMacAppStore(): boolean { -+ return isMacAppStore(); -+ } -+ -+ isViewOpen(): Promise { -+ return Promise.resolve(false); -+ } -+ -+ launchUri(uri: string, options?: any): void { -+ shell.openExternal(uri); -+ } -+ -+ saveFile(win: Window, blobData: any, blobOptions: any, fileName: string): void { -+ const blob = new Blob([blobData], blobOptions); -+ const a = win.document.createElement("a"); -+ a.href = URL.createObjectURL(blob); -+ a.download = fileName; -+ win.document.body.appendChild(a); -+ a.click(); -+ win.document.body.removeChild(a); -+ } -+ -+ getApplicationVersion(): Promise { -+ return ipcRenderer.invoke("appVersion"); -+ } -+ -+ // Temporarily restricted to only Windows until https://github.com/electron/electron/pull/28349 -+ // has been merged and an updated electron build is available. -+ supportsWebAuthn(win: Window): boolean { -+ return process.platform === "win32"; -+ } -+ -+ supportsDuo(): boolean { -+ return true; -+ } -+ -+ showToast( -+ type: "error" | "success" | "warning" | "info", -+ title: string, -+ text: string | string[], -+ options?: any -+ ): void { -+ this.messagingService.send("showToast", { -+ text: text, -+ title: title, -+ type: type, -+ options: options, -+ }); -+ } -+ -+ async showDialog( -+ text: string, -+ title?: string, -+ confirmText?: string, -+ cancelText?: string, -+ type?: string -+ ): Promise { -+ const buttons = [confirmText == null ? this.i18nService.t("ok") : confirmText]; -+ if (cancelText != null) { -+ buttons.push(cancelText); -+ } -+ -+ const result = await ipcRenderer.invoke("showMessageBox", { -+ type: type, -+ title: title, -+ message: title, -+ detail: text, -+ buttons: buttons, -+ cancelId: buttons.length === 2 ? 1 : null, -+ defaultId: 0, -+ noLink: true, -+ }); -+ -+ return Promise.resolve(result.response === 0); -+ } -+ -+ isDev(): boolean { -+ return isDev(); -+ } -+ -+ isSelfHost(): boolean { -+ return false; -+ } -+ -+ copyToClipboard(text: string, options?: any): void { -+ const type = options ? options.type : null; -+ const clearing = options ? !!options.clearing : false; -+ const clearMs: number = options && options.clearMs ? options.clearMs : null; -+ clipboard.writeText(text, type); -+ if (!clearing) { -+ this.messagingService.send("copiedToClipboard", { -+ clipboardValue: text, -+ clearMs: clearMs, -+ type: type, -+ clearing: clearing, -+ }); -+ } -+ } -+ -+ readFromClipboard(options?: any): Promise { -+ const type = options ? options.type : null; -+ return Promise.resolve(clipboard.readText(type)); -+ } -+ -+ async supportsBiometric(): Promise { -+ return await this.stateService.getEnableBiometric(); -+ } -+ -+ authenticateBiometric(): Promise { -+ return new Promise((resolve) => { -+ const val = ipcRenderer.sendSync("biometric", { -+ action: "authenticate", -+ }); -+ resolve(val); -+ }); -+ } -+ -+ getDefaultSystemTheme() { -+ return ipcRenderer.invoke("systemTheme"); -+ } -+ -+ onDefaultSystemThemeChange(callback: (theme: ThemeType.Light | ThemeType.Dark) => unknown) { -+ ipcRenderer.on("systemThemeUpdated", (event, theme: ThemeType.Light | ThemeType.Dark) => -+ callback(theme) -+ ); -+ } -+ -+ async getEffectiveTheme() { -+ const theme = await this.stateService.getTheme(); -+ if (theme == null || theme === ThemeType.System) { -+ return this.getDefaultSystemTheme(); -+ } else { -+ return theme; -+ } -+ } -+ -+ supportsSecureStorage(): boolean { -+ return true; -+ } - } -diff --git a/jslib/electron/src/services/electronRendererMessaging.service.ts b/jslib/electron/src/services/electronRendererMessaging.service.ts -index c9509074..89350a65 100644 ---- a/jslib/electron/src/services/electronRendererMessaging.service.ts -+++ b/jslib/electron/src/services/electronRendererMessaging.service.ts -@@ -1,26 +1,26 @@ --import { ipcRenderer } from 'electron'; -+import { ipcRenderer } from "electron"; - --import { BroadcasterService } from 'jslib-common/abstractions/broadcaster.service'; --import { MessagingService } from 'jslib-common/abstractions/messaging.service'; -+import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service"; -+import { MessagingService } from "jslib-common/abstractions/messaging.service"; - - export class ElectronRendererMessagingService implements MessagingService { -- constructor(private broadcasterService: BroadcasterService) { -- ipcRenderer.on('messagingService', async (event: any, message: any) => { -- if (message.command) { -- this.sendMessage(message.command, message, false); -- } -- }); -- } -+ constructor(private broadcasterService: BroadcasterService) { -+ ipcRenderer.on("messagingService", async (event: any, message: any) => { -+ if (message.command) { -+ this.sendMessage(message.command, message, false); -+ } -+ }); -+ } - -- send(subscriber: string, arg: any = {}) { -- this.sendMessage(subscriber, arg, true); -- } -+ send(subscriber: string, arg: any = {}) { -+ this.sendMessage(subscriber, arg, true); -+ } - -- private sendMessage(subscriber: string, arg: any = {}, toMain: boolean) { -- const message = Object.assign({}, { command: subscriber }, arg); -- this.broadcasterService.send(message); -- if (toMain) { -- ipcRenderer.send('messagingService', message); -- } -+ private sendMessage(subscriber: string, arg: any = {}, toMain: boolean) { -+ const message = Object.assign({}, { command: subscriber }, arg); -+ this.broadcasterService.send(message); -+ if (toMain) { -+ ipcRenderer.send("messagingService", message); - } -+ } - } -diff --git a/jslib/electron/src/services/electronRendererSecureStorage.service.ts b/jslib/electron/src/services/electronRendererSecureStorage.service.ts -index fb718058..5d293350 100644 ---- a/jslib/electron/src/services/electronRendererSecureStorage.service.ts -+++ b/jslib/electron/src/services/electronRendererSecureStorage.service.ts -@@ -1,42 +1,44 @@ --import { ipcRenderer } from 'electron'; -+import { ipcRenderer } from "electron"; - --import { StorageService, StorageServiceOptions } from 'jslib-common/abstractions/storage.service'; -+import { StorageService } from "jslib-common/abstractions/storage.service"; -+ -+import { StorageOptions } from "jslib-common/models/domain/storageOptions"; - - export class ElectronRendererSecureStorageService implements StorageService { -- async get(key: string, options?: StorageServiceOptions): Promise { -- const val = ipcRenderer.sendSync('keytar', { -- action: 'getPassword', -- key: key, -- keySuffix: options?.keySuffix ?? '', -- }); -- return Promise.resolve(val != null ? JSON.parse(val) as T : null); -- } -+ async get(key: string, options?: StorageOptions): Promise { -+ const val = ipcRenderer.sendSync("keytar", { -+ action: "getPassword", -+ key: key, -+ keySuffix: options?.keySuffix ?? "", -+ }); -+ return Promise.resolve(val != null ? (JSON.parse(val) as T) : null); -+ } - -- async has(key: string, options?: StorageServiceOptions): Promise { -- const val = ipcRenderer.sendSync('keytar', { -- action: 'hasPassword', -- key: key, -- keySuffix: options?.keySuffix ?? '', -- }); -- return Promise.resolve(!!val); -- } -+ async has(key: string, options?: StorageOptions): Promise { -+ const val = ipcRenderer.sendSync("keytar", { -+ action: "hasPassword", -+ key: key, -+ keySuffix: options?.keySuffix ?? "", -+ }); -+ return Promise.resolve(!!val); -+ } - -- async save(key: string, obj: any, options?: StorageServiceOptions): Promise { -- ipcRenderer.sendSync('keytar', { -- action: 'setPassword', -- key: key, -- keySuffix: options?.keySuffix ?? '', -- value: JSON.stringify(obj), -- }); -- return Promise.resolve(); -- } -+ async save(key: string, obj: any, options?: StorageOptions): Promise { -+ ipcRenderer.sendSync("keytar", { -+ action: "setPassword", -+ key: key, -+ keySuffix: options?.keySuffix ?? "", -+ value: JSON.stringify(obj), -+ }); -+ return Promise.resolve(); -+ } - -- async remove(key: string, options?: StorageServiceOptions): Promise { -- ipcRenderer.sendSync('keytar', { -- action: 'deletePassword', -- key: key, -- keySuffix: options?.keySuffix ?? '', -- }); -- return Promise.resolve(); -- } -+ async remove(key: string, options?: StorageOptions): Promise { -+ ipcRenderer.sendSync("keytar", { -+ action: "deletePassword", -+ key: key, -+ keySuffix: options?.keySuffix ?? "", -+ }); -+ return Promise.resolve(); -+ } - } -diff --git a/jslib/electron/src/services/electronRendererStorage.service.ts b/jslib/electron/src/services/electronRendererStorage.service.ts -index e1554a7e..69436bfe 100644 ---- a/jslib/electron/src/services/electronRendererStorage.service.ts -+++ b/jslib/electron/src/services/electronRendererStorage.service.ts -@@ -1,34 +1,34 @@ --import { ipcRenderer } from 'electron'; -+import { ipcRenderer } from "electron"; - --import { StorageService } from 'jslib-common/abstractions/storage.service'; -+import { StorageService } from "jslib-common/abstractions/storage.service"; - - export class ElectronRendererStorageService implements StorageService { -- get(key: string): Promise { -- return ipcRenderer.invoke('storageService', { -- action: 'get', -- key: key, -- }); -- } -+ get(key: string): Promise { -+ return ipcRenderer.invoke("storageService", { -+ action: "get", -+ key: key, -+ }); -+ } - -- has(key: string): Promise { -- return ipcRenderer.invoke('storageService', { -- action: 'has', -- key: key, -- }); -- } -+ has(key: string): Promise { -+ return ipcRenderer.invoke("storageService", { -+ action: "has", -+ key: key, -+ }); -+ } - -- save(key: string, obj: any): Promise { -- return ipcRenderer.invoke('storageService', { -- action: 'save', -- key: key, -- obj: obj, -- }); -- } -+ save(key: string, obj: any): Promise { -+ return ipcRenderer.invoke("storageService", { -+ action: "save", -+ key: key, -+ obj: obj, -+ }); -+ } - -- remove(key: string): Promise { -- return ipcRenderer.invoke('storageService', { -- action: 'remove', -- key: key, -- }); -- } -+ remove(key: string): Promise { -+ return ipcRenderer.invoke("storageService", { -+ action: "remove", -+ key: key, -+ }); -+ } - } -diff --git a/jslib/electron/src/services/electronStorage.service.ts b/jslib/electron/src/services/electronStorage.service.ts -index 5bf8d26c..08d49ed0 100644 ---- a/jslib/electron/src/services/electronStorage.service.ts -+++ b/jslib/electron/src/services/electronStorage.service.ts -@@ -1,60 +1,60 @@ --import { ipcMain, ipcRenderer } from 'electron'; --import * as fs from 'fs'; -+import { ipcMain, ipcRenderer } from "electron"; -+import * as fs from "fs"; - --import { StorageService } from 'jslib-common/abstractions/storage.service'; -+import { StorageService } from "jslib-common/abstractions/storage.service"; - --import { NodeUtils } from 'jslib-common/misc/nodeUtils'; -+import { NodeUtils } from "jslib-common/misc/nodeUtils"; - - // tslint:disable-next-line --const Store = require('electron-store'); -+const Store = require("electron-store"); - - export class ElectronStorageService implements StorageService { -- private store: any; -- -- constructor(dir: string, defaults = {}) { -- if (!fs.existsSync(dir)) { -- NodeUtils.mkdirpSync(dir, '700'); -- } -- const storeConfig: any = { -- defaults: defaults, -- name: 'data', -- }; -- this.store = new Store(storeConfig); -- -- ipcMain.handle('storageService', (event, options) => { -- switch (options.action) { -- case 'get': -- return this.get(options.key); -- case 'has': -- return this.has(options.key); -- case 'save': -- return this.save(options.key, options.obj); -- case 'remove': -- return this.remove(options.key); -- } -- }); -- } -- -- get(key: string): Promise { -- const val = this.store.get(key) as T; -- return Promise.resolve(val != null ? val : null); -- } -+ private store: any; - -- has(key: string): Promise { -- const val = this.store.get(key); -- return Promise.resolve(val != null); -+ constructor(dir: string, defaults = {}) { -+ if (!fs.existsSync(dir)) { -+ NodeUtils.mkdirpSync(dir, "700"); - } -- -- save(key: string, obj: any): Promise { -- if (obj instanceof Set) { -- obj = Array.from(obj); -- } -- this.store.set(key, obj); -- return Promise.resolve(); -- } -- -- remove(key: string): Promise { -- this.store.delete(key); -- return Promise.resolve(); -+ const storeConfig: any = { -+ defaults: defaults, -+ name: "data", -+ }; -+ this.store = new Store(storeConfig); -+ -+ ipcMain.handle("storageService", (event, options) => { -+ switch (options.action) { -+ case "get": -+ return this.get(options.key); -+ case "has": -+ return this.has(options.key); -+ case "save": -+ return this.save(options.key, options.obj); -+ case "remove": -+ return this.remove(options.key); -+ } -+ }); -+ } -+ -+ get(key: string): Promise { -+ const val = this.store.get(key) as T; -+ return Promise.resolve(val != null ? val : null); -+ } -+ -+ has(key: string): Promise { -+ const val = this.store.get(key); -+ return Promise.resolve(val != null); -+ } -+ -+ save(key: string, obj: any): Promise { -+ if (obj instanceof Set) { -+ obj = Array.from(obj); - } -+ this.store.set(key, obj); -+ return Promise.resolve(); -+ } -+ -+ remove(key: string): Promise { -+ this.store.delete(key); -+ return Promise.resolve(); -+ } - } -diff --git a/jslib/electron/src/tray.main.ts b/jslib/electron/src/tray.main.ts -index 30df8410..c5c3f661 100644 ---- a/jslib/electron/src/tray.main.ts -+++ b/jslib/electron/src/tray.main.ts -@@ -1,187 +1,185 @@ --import { -- app, -- BrowserWindow, -- Menu, -- MenuItem, -- MenuItemConstructorOptions, -- nativeImage, -- Tray, --} from 'electron'; --import * as path from 'path'; -- --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { StorageService } from 'jslib-common/abstractions/storage.service'; -- --import { ElectronConstants } from './electronConstants'; --import { WindowMain } from './window.main'; -+import { app, BrowserWindow, Menu, MenuItemConstructorOptions, nativeImage, Tray } from "electron"; -+import * as path from "path"; - --export class TrayMain { -- contextMenu: Menu; -- -- private appName: string; -- private tray: Tray; -- private icon: string | Electron.NativeImage; -- private pressedIcon: Electron.NativeImage; -- -- constructor(private windowMain: WindowMain, private i18nService: I18nService, -- private storageService: StorageService) { -- if (process.platform === 'win32') { -- this.icon = path.join(__dirname, '/images/icon.ico'); -- } else if (process.platform === 'darwin') { -- const nImage = nativeImage.createFromPath(path.join(__dirname, '/images/icon-template.png')); -- nImage.setTemplateImage(true); -- this.icon = nImage; -- this.pressedIcon = nativeImage.createFromPath(path.join(__dirname, '/images/icon-highlight.png')); -- } else { -- this.icon = path.join(__dirname, '/images/icon.png'); -- } -- } -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; - -- async init(appName: string, additionalMenuItems: MenuItemConstructorOptions[] = null) { -- this.appName = appName; -- -- const menuItemOptions: MenuItemConstructorOptions[] = [{ -- label: this.i18nService.t('showHide'), -- click: () => this.toggleWindow(), -- }, -- { type: 'separator' }, -- { -- label: this.i18nService.t('exit'), -- click: () => this.closeWindow(), -- }]; -- -- if (additionalMenuItems != null) { -- menuItemOptions.splice(1, 0, ...additionalMenuItems); -- } -+import { WindowMain } from "./window.main"; - -- this.contextMenu = Menu.buildFromTemplate(menuItemOptions); -- if (await this.storageService.get(ElectronConstants.enableTrayKey)) { -- this.showTray(); -- } -+export class TrayMain { -+ contextMenu: Menu; -+ -+ private appName: string; -+ private tray: Tray; -+ private icon: string | Electron.NativeImage; -+ private pressedIcon: Electron.NativeImage; -+ -+ constructor( -+ private windowMain: WindowMain, -+ private i18nService: I18nService, -+ private stateService: StateService -+ ) { -+ if (process.platform === "win32") { -+ this.icon = path.join(__dirname, "/images/icon.ico"); -+ } else if (process.platform === "darwin") { -+ const nImage = nativeImage.createFromPath(path.join(__dirname, "/images/icon-template.png")); -+ nImage.setTemplateImage(true); -+ this.icon = nImage; -+ this.pressedIcon = nativeImage.createFromPath( -+ path.join(__dirname, "/images/icon-highlight.png") -+ ); -+ } else { -+ this.icon = path.join(__dirname, "/images/icon.png"); - } -- -- setupWindowListeners(win: BrowserWindow) { -- win.on('minimize', async (e: Event) => { -- if (await this.storageService.get(ElectronConstants.enableMinimizeToTrayKey)) { -- e.preventDefault(); -- this.hideToTray(); -- } -- }); -- -- win.on('close', async (e: Event) => { -- if (await this.storageService.get(ElectronConstants.enableCloseToTrayKey)) { -- if (!this.windowMain.isQuitting) { -- e.preventDefault(); -- this.hideToTray(); -- } -- } -- }); -- -- win.on('show', async (e: Event) => { -- const enableTray = await this.storageService.get(ElectronConstants.enableTrayKey); -- if (!enableTray) { -- setTimeout(() => this.removeTray(false), 100); -- } -- }); -+ } -+ -+ async init(appName: string, additionalMenuItems: MenuItemConstructorOptions[] = null) { -+ this.appName = appName; -+ -+ const menuItemOptions: MenuItemConstructorOptions[] = [ -+ { -+ label: this.i18nService.t("showHide"), -+ click: () => this.toggleWindow(), -+ }, -+ { type: "separator" }, -+ { -+ label: this.i18nService.t("exit"), -+ click: () => this.closeWindow(), -+ }, -+ ]; -+ -+ if (additionalMenuItems != null) { -+ menuItemOptions.splice(1, 0, ...additionalMenuItems); - } - -- removeTray(showWindow = true) { -- // Due to https://github.com/electron/electron/issues/17622 -- // we cannot destroy the tray icon on linux. -- if (this.tray != null && process.platform !== 'linux') { -- this.tray.destroy(); -- this.tray = null; -- } -- -- if (showWindow && this.windowMain.win != null && !this.windowMain.win.isVisible()) { -- this.windowMain.win.show(); -- } -+ this.contextMenu = Menu.buildFromTemplate(menuItemOptions); -+ if (await this.stateService.getEnableTray()) { -+ this.showTray(); - } -- -- async hideToTray() { -- this.showTray(); -- if (this.windowMain.win != null) { -- this.windowMain.win.hide(); -- } -- if (this.isDarwin() && !await this.storageService.get(ElectronConstants.alwaysShowDock)) { -- this.hideDock(); -+ } -+ -+ setupWindowListeners(win: BrowserWindow) { -+ win.on("minimize", async (e: Event) => { -+ if (await this.stateService.getEnableMinimizeToTray()) { -+ e.preventDefault(); -+ this.hideToTray(); -+ } -+ }); -+ -+ win.on("close", async (e: Event) => { -+ if (await this.stateService.getEnableCloseToTray()) { -+ if (!this.windowMain.isQuitting) { -+ e.preventDefault(); -+ this.hideToTray(); - } -+ } -+ }); -+ -+ win.on("show", async (e: Event) => { -+ const enableTray = await this.stateService.getEnableTray(); -+ if (!enableTray) { -+ setTimeout(() => this.removeTray(false), 100); -+ } -+ }); -+ } -+ -+ removeTray(showWindow = true) { -+ // Due to https://github.com/electron/electron/issues/17622 -+ // we cannot destroy the tray icon on linux. -+ if (this.tray != null && process.platform !== "linux") { -+ this.tray.destroy(); -+ this.tray = null; - } - -- restoreFromTray() { -- if (this.windowMain.win == null || !this.windowMain.win.isVisible()) { -- this.toggleWindow(); -- } -+ if (showWindow && this.windowMain.win != null && !this.windowMain.win.isVisible()) { -+ this.windowMain.win.show(); - } -+ } - -- showTray() { -- if (this.tray != null) { -- return; -- } -- -- this.tray = new Tray(this.icon); -- this.tray.setToolTip(this.appName); -- this.tray.on('click', () => this.toggleWindow()); -- this.tray.on('right-click', () => this.tray.popUpContextMenu(this.contextMenu)); -- -- if (this.pressedIcon != null) { -- this.tray.setPressedImage(this.pressedIcon); -- } -- if (this.contextMenu != null && !this.isDarwin()) { -- this.tray.setContextMenu(this.contextMenu); -- } -+ async hideToTray() { -+ this.showTray(); -+ if (this.windowMain.win != null) { -+ this.windowMain.win.hide(); - } -- -- updateContextMenu() { -- if (this.contextMenu != null && this.isLinux()) { -- this.tray.setContextMenu(this.contextMenu); -- } -+ if (this.isDarwin() && !(await this.stateService.getAlwaysShowDock())) { -+ this.hideDock(); - } -+ } - -- private hideDock() { -- app.dock.hide(); -+ restoreFromTray() { -+ if (this.windowMain.win == null || !this.windowMain.win.isVisible()) { -+ this.toggleWindow(); - } -+ } - -- private showDock() { -- app.dock.show(); -+ showTray() { -+ if (this.tray != null) { -+ return; - } - -- private isDarwin() { -- return process.platform === 'darwin'; -- } -+ this.tray = new Tray(this.icon); -+ this.tray.setToolTip(this.appName); -+ this.tray.on("click", () => this.toggleWindow()); -+ this.tray.on("right-click", () => this.tray.popUpContextMenu(this.contextMenu)); - -- private isLinux() { -- return process.platform === 'linux'; -+ if (this.pressedIcon != null) { -+ this.tray.setPressedImage(this.pressedIcon); -+ } -+ if (this.contextMenu != null && !this.isDarwin()) { -+ this.tray.setContextMenu(this.contextMenu); - } -+ } - -- private async toggleWindow() { -- if (this.windowMain.win == null) { -- if (this.isDarwin()) { -- // On MacOS, closing the window via the red button destroys the BrowserWindow instance. -- this.windowMain.createWindow().then(() => { -- this.windowMain.win.show(); -- this.showDock(); -- }); -- } -- return; -- } -- if (this.windowMain.win.isVisible()) { -- this.windowMain.win.hide(); -- if (this.isDarwin() && !await this.storageService.get(ElectronConstants.alwaysShowDock)) { -- this.hideDock(); -- } -- } else { -- this.windowMain.win.show(); -- if (this.isDarwin()) { -- this.showDock(); -- } -- } -+ updateContextMenu() { -+ if (this.contextMenu != null && this.isLinux()) { -+ this.tray.setContextMenu(this.contextMenu); - } -+ } -+ -+ private hideDock() { -+ app.dock.hide(); -+ } -+ -+ private showDock() { -+ app.dock.show(); -+ } -+ -+ private isDarwin() { -+ return process.platform === "darwin"; -+ } -+ -+ private isLinux() { -+ return process.platform === "linux"; -+ } -+ -+ private async toggleWindow() { -+ if (this.windowMain.win == null) { -+ if (this.isDarwin()) { -+ // On MacOS, closing the window via the red button destroys the BrowserWindow instance. -+ this.windowMain.createWindow().then(() => { -+ this.windowMain.win.show(); -+ this.showDock(); -+ }); -+ } -+ return; -+ } -+ if (this.windowMain.win.isVisible()) { -+ this.windowMain.win.hide(); -+ if (this.isDarwin() && !(await this.stateService.getAlwaysShowDock())) { -+ this.hideDock(); -+ } -+ } else { -+ this.windowMain.win.show(); -+ if (this.isDarwin()) { -+ this.showDock(); -+ } -+ } -+ } - -- private closeWindow() { -- this.windowMain.isQuitting = true; -- if (this.windowMain.win != null) { -- this.windowMain.win.close(); -- } -+ private closeWindow() { -+ this.windowMain.isQuitting = true; -+ if (this.windowMain.win != null) { -+ this.windowMain.win.close(); - } -+ } - } -diff --git a/jslib/electron/src/updater.main.ts b/jslib/electron/src/updater.main.ts -index 712a79a4..bc0fb4b4 100644 ---- a/jslib/electron/src/updater.main.ts -+++ b/jslib/electron/src/updater.main.ts -@@ -1,155 +1,154 @@ --import { -- dialog, -- Menu, -- MenuItem, -- shell, --} from 'electron'; --import log from 'electron-log'; --import { autoUpdater } from 'electron-updater'; -- --import { -- isAppImage, -- isDev, -- isMacAppStore, -- isWindowsPortable, -- isWindowsStore, --} from './utils'; -- --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { WindowMain } from './window.main'; -+import { dialog, Menu, MenuItem, shell } from "electron"; -+import log from "electron-log"; -+import { autoUpdater } from "electron-updater"; -+ -+import { isAppImage, isDev, isMacAppStore, isWindowsPortable, isWindowsStore } from "./utils"; -+ -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { WindowMain } from "./window.main"; - - const UpdaterCheckInitalDelay = 5 * 1000; // 5 seconds - const UpdaterCheckInterval = 12 * 60 * 60 * 1000; // 12 hours - - export class UpdaterMain { -- private doingUpdateCheck = false; -- private doingUpdateCheckWithFeedback = false; -- private canUpdate = false; -- -- constructor(private i18nService: I18nService, private windowMain: WindowMain, -- private gitHubProject: string, private onCheckingForUpdate: () => void = null, -- private onReset: () => void = null, private onUpdateDownloaded: () => void = null, -- private projectName: string) { -- autoUpdater.logger = log; -- -- const linuxCanUpdate = process.platform === 'linux' && isAppImage(); -- const windowsCanUpdate = process.platform === 'win32' && !isWindowsStore() && !isWindowsPortable(); -- const macCanUpdate = process.platform === 'darwin' && !isMacAppStore(); -- this.canUpdate = process.env.ELECTRON_NO_UPDATER !== '1' && -- (linuxCanUpdate || windowsCanUpdate || macCanUpdate); -- } -- -- async init() { -- global.setTimeout(async () => await this.checkForUpdate(), UpdaterCheckInitalDelay); -- global.setInterval(async () => await this.checkForUpdate(), UpdaterCheckInterval); -- -- autoUpdater.on('checking-for-update', () => { -- if (this.onCheckingForUpdate != null) { -- this.onCheckingForUpdate(); -- } -- this.doingUpdateCheck = true; -- }); -- -- autoUpdater.on('update-available', async () => { -- if (this.doingUpdateCheckWithFeedback) { -- if (this.windowMain.win == null) { -- this.reset(); -- return; -- } -- -- const result = await dialog.showMessageBox(this.windowMain.win, { -- type: 'info', -- title: this.i18nService.t(this.projectName) + ' - ' + this.i18nService.t('updateAvailable'), -- message: this.i18nService.t('updateAvailable'), -- detail: this.i18nService.t('updateAvailableDesc'), -- buttons: [this.i18nService.t('yes'), this.i18nService.t('no')], -- cancelId: 1, -- defaultId: 0, -- noLink: true, -- }); -- -- if (result.response === 0) { -- autoUpdater.downloadUpdate(); -- } else { -- this.reset(); -- } -- } -- }); -- -- autoUpdater.on('update-not-available', () => { -- if (this.doingUpdateCheckWithFeedback && this.windowMain.win != null) { -- dialog.showMessageBox(this.windowMain.win, { -- message: this.i18nService.t('noUpdatesAvailable'), -- buttons: [this.i18nService.t('ok')], -- defaultId: 0, -- noLink: true, -- }); -- } -- -- this.reset(); -- }); -+ private doingUpdateCheck = false; -+ private doingUpdateCheckWithFeedback = false; -+ private canUpdate = false; -+ -+ constructor( -+ private i18nService: I18nService, -+ private windowMain: WindowMain, -+ private gitHubProject: string, -+ private onCheckingForUpdate: () => void = null, -+ private onReset: () => void = null, -+ private onUpdateDownloaded: () => void = null, -+ private projectName: string -+ ) { -+ autoUpdater.logger = log; -+ -+ const linuxCanUpdate = process.platform === "linux" && isAppImage(); -+ const windowsCanUpdate = -+ process.platform === "win32" && !isWindowsStore() && !isWindowsPortable(); -+ const macCanUpdate = process.platform === "darwin" && !isMacAppStore(); -+ this.canUpdate = -+ process.env.ELECTRON_NO_UPDATER !== "1" && -+ (linuxCanUpdate || windowsCanUpdate || macCanUpdate); -+ } -+ -+ async init() { -+ global.setTimeout(async () => await this.checkForUpdate(), UpdaterCheckInitalDelay); -+ global.setInterval(async () => await this.checkForUpdate(), UpdaterCheckInterval); -+ -+ autoUpdater.on("checking-for-update", () => { -+ if (this.onCheckingForUpdate != null) { -+ this.onCheckingForUpdate(); -+ } -+ this.doingUpdateCheck = true; -+ }); -+ -+ autoUpdater.on("update-available", async () => { -+ if (this.doingUpdateCheckWithFeedback) { -+ if (this.windowMain.win == null) { -+ this.reset(); -+ return; -+ } - -- autoUpdater.on('update-downloaded', async info => { -- if (this.onUpdateDownloaded != null) { -- this.onUpdateDownloaded(); -- } -- -- if (this.windowMain.win == null) { -- return; -- } -- -- const result = await dialog.showMessageBox(this.windowMain.win, { -- type: 'info', -- title: this.i18nService.t(this.projectName) + ' - ' + this.i18nService.t('restartToUpdate'), -- message: this.i18nService.t('restartToUpdate'), -- detail: this.i18nService.t('restartToUpdateDesc', info.version), -- buttons: [this.i18nService.t('restart'), this.i18nService.t('later')], -- cancelId: 1, -- defaultId: 0, -- noLink: true, -- }); -- -- if (result.response === 0) { -- autoUpdater.quitAndInstall(false, true); -- } -+ const result = await dialog.showMessageBox(this.windowMain.win, { -+ type: "info", -+ title: -+ this.i18nService.t(this.projectName) + " - " + this.i18nService.t("updateAvailable"), -+ message: this.i18nService.t("updateAvailable"), -+ detail: this.i18nService.t("updateAvailableDesc"), -+ buttons: [this.i18nService.t("yes"), this.i18nService.t("no")], -+ cancelId: 1, -+ defaultId: 0, -+ noLink: true, - }); - -- autoUpdater.on('error', error => { -- if (this.doingUpdateCheckWithFeedback) { -- dialog.showErrorBox(this.i18nService.t('updateError'), -- error == null ? this.i18nService.t('unknown') : (error.stack || error).toString()); -- } -- -- this.reset(); -+ if (result.response === 0) { -+ autoUpdater.downloadUpdate(); -+ } else { -+ this.reset(); -+ } -+ } -+ }); -+ -+ autoUpdater.on("update-not-available", () => { -+ if (this.doingUpdateCheckWithFeedback && this.windowMain.win != null) { -+ dialog.showMessageBox(this.windowMain.win, { -+ message: this.i18nService.t("noUpdatesAvailable"), -+ buttons: [this.i18nService.t("ok")], -+ defaultId: 0, -+ noLink: true, - }); -+ } -+ -+ this.reset(); -+ }); -+ -+ autoUpdater.on("update-downloaded", async (info) => { -+ if (this.onUpdateDownloaded != null) { -+ this.onUpdateDownloaded(); -+ } -+ -+ if (this.windowMain.win == null) { -+ return; -+ } -+ -+ const result = await dialog.showMessageBox(this.windowMain.win, { -+ type: "info", -+ title: this.i18nService.t(this.projectName) + " - " + this.i18nService.t("restartToUpdate"), -+ message: this.i18nService.t("restartToUpdate"), -+ detail: this.i18nService.t("restartToUpdateDesc", info.version), -+ buttons: [this.i18nService.t("restart"), this.i18nService.t("later")], -+ cancelId: 1, -+ defaultId: 0, -+ noLink: true, -+ }); -+ -+ if (result.response === 0) { -+ autoUpdater.quitAndInstall(false, true); -+ } -+ }); -+ -+ autoUpdater.on("error", (error) => { -+ if (this.doingUpdateCheckWithFeedback) { -+ dialog.showErrorBox( -+ this.i18nService.t("updateError"), -+ error == null ? this.i18nService.t("unknown") : (error.stack || error).toString() -+ ); -+ } -+ -+ this.reset(); -+ }); -+ } -+ -+ async checkForUpdate(withFeedback: boolean = false) { -+ if (this.doingUpdateCheck || isDev()) { -+ return; - } - -- async checkForUpdate(withFeedback: boolean = false) { -- if (this.doingUpdateCheck || isDev()) { -- return; -- } -- -- if (!this.canUpdate) { -- if (withFeedback) { -- shell.openExternal('https://github.com/bitwarden/' + this.gitHubProject + '/releases'); -- } -- -- return; -- } -+ if (!this.canUpdate) { -+ if (withFeedback) { -+ shell.openExternal("https://github.com/bitwarden/" + this.gitHubProject + "/releases"); -+ } - -- this.doingUpdateCheckWithFeedback = withFeedback; -- if (withFeedback) { -- autoUpdater.autoDownload = false; -- } -+ return; -+ } - -- await autoUpdater.checkForUpdates(); -+ this.doingUpdateCheckWithFeedback = withFeedback; -+ if (withFeedback) { -+ autoUpdater.autoDownload = false; - } - -- private reset() { -- if (this.onReset != null) { -- this.onReset(); -- } -- autoUpdater.autoDownload = true; -- this.doingUpdateCheck = false; -+ await autoUpdater.checkForUpdates(); -+ } -+ -+ private reset() { -+ if (this.onReset != null) { -+ this.onReset(); - } -+ autoUpdater.autoDownload = true; -+ this.doingUpdateCheck = false; -+ } - } -diff --git a/jslib/electron/src/utils.ts b/jslib/electron/src/utils.ts -index ce4de605..a29a27a2 100644 ---- a/jslib/electron/src/utils.ts -+++ b/jslib/electron/src/utils.ts -@@ -1,64 +1,76 @@ --import { ipcRenderer } from 'electron'; -+import { ipcRenderer } from "electron"; - --export type RendererMenuItem = {label?: string, type?: ('normal' | 'separator' | 'submenu' | 'checkbox' | 'radio'), click?: () => any}; -+export type RendererMenuItem = { -+ label?: string; -+ type?: "normal" | "separator" | "submenu" | "checkbox" | "radio"; -+ click?: () => any; -+}; - - export function invokeMenu(menu: RendererMenuItem[]) { -- const menuWithoutClick = menu.map(m => { -- return { label: m.label, type: m.type }; -- }); -- ipcRenderer.invoke('openContextMenu', { menu: menuWithoutClick }).then((i: number) => { -- if (i !== -1) { -- menu[i].click(); -- } -- }); -+ const menuWithoutClick = menu.map((m) => { -+ return { label: m.label, type: m.type }; -+ }); -+ ipcRenderer.invoke("openContextMenu", { menu: menuWithoutClick }).then((i: number) => { -+ if (i !== -1) { -+ menu[i].click(); -+ } -+ }); - } - - export function isDev() { -- // ref: https://github.com/sindresorhus/electron-is-dev -- if ('ELECTRON_IS_DEV' in process.env) { -- return parseInt(process.env.ELECTRON_IS_DEV, 10) === 1; -- } -- return (process.defaultApp || /node_modules[\\/]electron[\\/]/.test(process.execPath)); -+ // ref: https://github.com/sindresorhus/electron-is-dev -+ if ("ELECTRON_IS_DEV" in process.env) { -+ return parseInt(process.env.ELECTRON_IS_DEV, 10) === 1; -+ } -+ return process.defaultApp || /node_modules[\\/]electron[\\/]/.test(process.execPath); - } - - export function isAppImage() { -- return process.platform === 'linux' && 'APPIMAGE' in process.env; -+ return process.platform === "linux" && "APPIMAGE" in process.env; -+} -+ -+export function isMac() { -+ return process.platform === "darwin"; - } - - export function isMacAppStore() { -- return process.platform === 'darwin' && process.mas && process.mas === true; -+ return isMac() && process.mas && process.mas === true; - } - - export function isWindowsStore() { -- const isWindows = process.platform === 'win32'; -- let windowsStore = process.windowsStore; -- if (isWindows && !windowsStore && -- process.resourcesPath.indexOf('8bitSolutionsLLC.bitwardendesktop_') > -1) { -- windowsStore = true; -- } -- return isWindows && windowsStore === true; -+ const isWindows = process.platform === "win32"; -+ let windowsStore = process.windowsStore; -+ if ( -+ isWindows && -+ !windowsStore && -+ process.resourcesPath.indexOf("8bitSolutionsLLC.bitwardendesktop_") > -1 -+ ) { -+ windowsStore = true; -+ } -+ return isWindows && windowsStore === true; - } - - export function isSnapStore() { -- return process.platform === 'linux' && process.env.SNAP_USER_DATA != null; -+ return process.platform === "linux" && process.env.SNAP_USER_DATA != null; - } - - export function isWindowsPortable() { -- return process.platform === 'win32' && process.env.PORTABLE_EXECUTABLE_DIR != null; -+ return process.platform === "win32" && process.env.PORTABLE_EXECUTABLE_DIR != null; - } - - /** - * Sanitize user agent so external resources used by the app can't built data on our users. - */ - export function cleanUserAgent(userAgent: string): string { -- const userAgentItem = (startString: string, endString: string) => { -- const startIndex = userAgent.indexOf(startString); -- return userAgent.substring(startIndex, userAgent.indexOf(endString, startIndex) + 1); -- }; -- const systemInformation = '(Windows NT 10.0; Win64; x64)'; -+ const userAgentItem = (startString: string, endString: string) => { -+ const startIndex = userAgent.indexOf(startString); -+ return userAgent.substring(startIndex, userAgent.indexOf(endString, startIndex) + 1); -+ }; -+ const systemInformation = "(Windows NT 10.0; Win64; x64)"; - -- // Set system information, remove bitwarden, and electron information -- return userAgent.replace(userAgentItem('(', ')'), systemInformation) -- .replace(userAgentItem('Bitwarden', ' '), '') -- .replace(userAgentItem('Electron', ' '), ''); -+ // Set system information, remove bitwarden, and electron information -+ return userAgent -+ .replace(userAgentItem("(", ")"), systemInformation) -+ .replace(userAgentItem("Bitwarden", " "), "") -+ .replace(userAgentItem("Electron", " "), ""); - } -diff --git a/jslib/electron/src/window.main.ts b/jslib/electron/src/window.main.ts -index f51dc114..2dc1709e 100644 ---- a/jslib/electron/src/window.main.ts -+++ b/jslib/electron/src/window.main.ts -@@ -1,290 +1,291 @@ --import { -- app, -- BrowserWindow, -- screen, --} from 'electron'; --import * as path from 'path'; --import * as url from 'url'; -- --import { LogService } from 'jslib-common/abstractions/log.service'; --import { StorageService } from 'jslib-common/abstractions/storage.service'; -- --import { ElectronConstants } from './electronConstants'; --import { -- cleanUserAgent, -- isDev, -- isMacAppStore, -- isSnapStore, --} from './utils'; -+import { app, BrowserWindow, screen } from "electron"; -+import * as path from "path"; -+import * as url from "url"; - --const WindowEventHandlingDelay = 100; --const Keys = { -- mainWindowSize: 'mainWindowSize', --}; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; -+ -+import { cleanUserAgent, isDev, isMacAppStore, isSnapStore } from "./utils"; - -+const mainWindowSizeKey = "mainWindowSize"; -+const WindowEventHandlingDelay = 100; - export class WindowMain { -- win: BrowserWindow; -- isQuitting: boolean = false; -- -- private windowStateChangeTimer: NodeJS.Timer; -- private windowStates: { [key: string]: any; } = {}; -- private enableAlwaysOnTop: boolean = false; -- -- constructor(private storageService: StorageService, private logService: LogService, -- private hideTitleBar = false, private defaultWidth = 950, private defaultHeight = 600, -- private argvCallback: (argv: string[]) => void = null, -- private createWindowCallback: (win: BrowserWindow) => void) { } -- -- init(): Promise { -- return new Promise((resolve, reject) => { -- try { -- if (!isMacAppStore() && !isSnapStore()) { -- const gotTheLock = app.requestSingleInstanceLock(); -- if (!gotTheLock) { -- app.quit(); -- return; -- } else { -- app.on('second-instance', (event, argv, workingDirectory) => { -- // Someone tried to run a second instance, we should focus our window. -- if (this.win != null) { -- if (this.win.isMinimized() || !this.win.isVisible()) { -- this.win.show(); -- } -- this.win.focus(); -- } -- if (process.platform === 'win32' || process.platform === 'linux') { -- if (this.argvCallback != null) { -- this.argvCallback(argv); -- } -- } -- }); -- } -+ win: BrowserWindow; -+ isQuitting: boolean = false; -+ -+ private windowStateChangeTimer: NodeJS.Timer; -+ private windowStates: { [key: string]: any } = {}; -+ private enableAlwaysOnTop: boolean = false; -+ -+ constructor( -+ private stateService: StateService, -+ private logService: LogService, -+ private hideTitleBar = false, -+ private defaultWidth = 950, -+ private defaultHeight = 600, -+ private argvCallback: (argv: string[]) => void = null, -+ private createWindowCallback: (win: BrowserWindow) => void -+ ) {} -+ -+ init(): Promise { -+ return new Promise((resolve, reject) => { -+ try { -+ if (!isMacAppStore() && !isSnapStore()) { -+ const gotTheLock = app.requestSingleInstanceLock(); -+ if (!gotTheLock) { -+ app.quit(); -+ return; -+ } else { -+ app.on("second-instance", (event, argv, workingDirectory) => { -+ // Someone tried to run a second instance, we should focus our window. -+ if (this.win != null) { -+ if (this.win.isMinimized() || !this.win.isVisible()) { -+ this.win.show(); - } -- -- // This method will be called when Electron is shutting -- // down the application. -- app.on('before-quit', () => { -- this.isQuitting = true; -- }); -- -- // This method will be called when Electron has finished -- // initialization and is ready to create browser windows. -- // Some APIs can only be used after this event occurs. -- app.on('ready', async () => { -- await this.createWindow(); -- resolve(); -- if (this.argvCallback != null) { -- this.argvCallback(process.argv); -- } -- }); -- -- // Quit when all windows are closed. -- app.on('window-all-closed', () => { -- // On OS X it is common for applications and their menu bar -- // to stay active until the user quits explicitly with Cmd + Q -- if (process.platform !== 'darwin' || this.isQuitting || isMacAppStore()) { -- app.quit(); -- } -- }); -- -- app.on('activate', async () => { -- // On OS X it's common to re-create a window in the app when the -- // dock icon is clicked and there are no other windows open. -- if (this.win === null) { -- await this.createWindow(); -- } else { -- // Show the window when clicking on Dock icon -- this.win.show(); -- } -- }); -- -- } catch (e) { -- // Catch Error -- // throw e; -- reject(e); -- } -- }); -- } -- -- async createWindow(): Promise { -- this.windowStates[Keys.mainWindowSize] = await this.getWindowState(Keys.mainWindowSize, this.defaultWidth, -- this.defaultHeight); -- this.enableAlwaysOnTop = await this.storageService.get(ElectronConstants.enableAlwaysOnTopKey); -- -- // Create the browser window. -- this.win = new BrowserWindow({ -- width: this.windowStates[Keys.mainWindowSize].width, -- height: this.windowStates[Keys.mainWindowSize].height, -- minWidth: 680, -- minHeight: 500, -- x: this.windowStates[Keys.mainWindowSize].x, -- y: this.windowStates[Keys.mainWindowSize].y, -- title: app.name, -- icon: process.platform === 'linux' ? path.join(__dirname, '/images/icon.png') : undefined, -- titleBarStyle: this.hideTitleBar && process.platform === 'darwin' ? 'hiddenInset' : undefined, -- show: false, -- backgroundColor: '#fff', -- alwaysOnTop: this.enableAlwaysOnTop, -- webPreferences: { -- nodeIntegration: true, -- backgroundThrottling: false, -- contextIsolation: false, -- }, -- }); -- -- if (this.windowStates[Keys.mainWindowSize].isMaximized) { -- this.win.maximize(); -- } -- -- // Show it later since it might need to be maximized. -- this.win.show(); -- -- // and load the index.html of the app. -- this.win.loadURL(url.format( -- { -- protocol: 'file:', -- pathname: path.join(__dirname, '/index.html'), -- slashes: true, -- }), -- { -- userAgent: cleanUserAgent(this.win.webContents.userAgent), -- } -- ); -- -- // Open the DevTools. -- if (isDev()) { -- this.win.webContents.openDevTools(); -+ this.win.focus(); -+ } -+ if (process.platform === "win32" || process.platform === "linux") { -+ if (this.argvCallback != null) { -+ this.argvCallback(argv); -+ } -+ } -+ }); -+ } - } - -- // Emitted when the window is closed. -- this.win.on('closed', async () => { -- await this.updateWindowState(Keys.mainWindowSize, this.win); -- -- // Dereference the window object, usually you would store window -- // in an array if your app supports multi windows, this is the time -- // when you should delete the corresponding element. -- this.win = null; -- }); -- -- this.win.on('close', async () => { -- await this.updateWindowState(Keys.mainWindowSize, this.win); -- }); -- -- this.win.on('maximize', async () => { -- await this.updateWindowState(Keys.mainWindowSize, this.win); -+ // This method will be called when Electron is shutting -+ // down the application. -+ app.on("before-quit", () => { -+ this.isQuitting = true; - }); - -- this.win.on('unmaximize', async () => { -- await this.updateWindowState(Keys.mainWindowSize, this.win); -+ // This method will be called when Electron has finished -+ // initialization and is ready to create browser windows. -+ // Some APIs can only be used after this event occurs. -+ app.on("ready", async () => { -+ await this.createWindow(); -+ resolve(); -+ if (this.argvCallback != null) { -+ this.argvCallback(process.argv); -+ } - }); - -- this.win.on('resize', () => { -- this.windowStateChangeHandler(Keys.mainWindowSize, this.win); -+ // Quit when all windows are closed. -+ app.on("window-all-closed", () => { -+ // On OS X it is common for applications and their menu bar -+ // to stay active until the user quits explicitly with Cmd + Q -+ if (process.platform !== "darwin" || this.isQuitting || isMacAppStore()) { -+ app.quit(); -+ } - }); - -- this.win.on('move', () => { -- this.windowStateChangeHandler(Keys.mainWindowSize, this.win); -- }); -- this.win.on('focus', () => { -- this.win.webContents.send('messagingService', { -- command: 'windowIsFocused', -- windowIsFocused: true, -- }); -+ app.on("activate", async () => { -+ // On OS X it's common to re-create a window in the app when the -+ // dock icon is clicked and there are no other windows open. -+ if (this.win === null) { -+ await this.createWindow(); -+ } else { -+ // Show the window when clicking on Dock icon -+ this.win.show(); -+ } - }); -- -- if (this.createWindowCallback) { -- this.createWindowCallback(this.win); -- } -+ } catch (e) { -+ // Catch Error -+ // throw e; -+ reject(e); -+ } -+ }); -+ } -+ -+ async createWindow(): Promise { -+ this.windowStates[mainWindowSizeKey] = await this.getWindowState( -+ this.defaultWidth, -+ this.defaultHeight -+ ); -+ this.enableAlwaysOnTop = await this.stateService.getEnableAlwaysOnTop(); -+ -+ // Create the browser window. -+ this.win = new BrowserWindow({ -+ width: this.windowStates[mainWindowSizeKey].width, -+ height: this.windowStates[mainWindowSizeKey].height, -+ minWidth: 680, -+ minHeight: 500, -+ x: this.windowStates[mainWindowSizeKey].x, -+ y: this.windowStates[mainWindowSizeKey].y, -+ title: app.name, -+ icon: process.platform === "linux" ? path.join(__dirname, "/images/icon.png") : undefined, -+ titleBarStyle: this.hideTitleBar && process.platform === "darwin" ? "hiddenInset" : undefined, -+ show: false, -+ backgroundColor: "#fff", -+ alwaysOnTop: this.enableAlwaysOnTop, -+ webPreferences: { -+ spellcheck: false, -+ nodeIntegration: true, -+ backgroundThrottling: false, -+ contextIsolation: false, -+ }, -+ }); -+ -+ if (this.windowStates[mainWindowSizeKey].isMaximized) { -+ this.win.maximize(); - } - -- async toggleAlwaysOnTop() { -- this.enableAlwaysOnTop = !this.win.isAlwaysOnTop(); -- this.win.setAlwaysOnTop(this.enableAlwaysOnTop); -- await this.storageService.save(ElectronConstants.enableAlwaysOnTopKey, this.enableAlwaysOnTop); -+ // Show it later since it might need to be maximized. -+ this.win.show(); -+ -+ // and load the index.html of the app. -+ this.win.loadURL( -+ url.format({ -+ protocol: "file:", -+ pathname: path.join(__dirname, "/index.html"), -+ slashes: true, -+ }), -+ { -+ userAgent: cleanUserAgent(this.win.webContents.userAgent), -+ } -+ ); -+ -+ // Open the DevTools. -+ if (isDev()) { -+ this.win.webContents.openDevTools(); - } - -- private windowStateChangeHandler(configKey: string, win: BrowserWindow) { -- global.clearTimeout(this.windowStateChangeTimer); -- this.windowStateChangeTimer = global.setTimeout(async () => { -- await this.updateWindowState(configKey, win); -- }, WindowEventHandlingDelay); -+ // Emitted when the window is closed. -+ this.win.on("closed", async () => { -+ await this.updateWindowState(mainWindowSizeKey, this.win); -+ -+ // Dereference the window object, usually you would store window -+ // in an array if your app supports multi windows, this is the time -+ // when you should delete the corresponding element. -+ this.win = null; -+ }); -+ -+ this.win.on("close", async () => { -+ await this.updateWindowState(mainWindowSizeKey, this.win); -+ }); -+ -+ this.win.on("maximize", async () => { -+ await this.updateWindowState(mainWindowSizeKey, this.win); -+ }); -+ -+ this.win.on("unmaximize", async () => { -+ await this.updateWindowState(mainWindowSizeKey, this.win); -+ }); -+ -+ this.win.on("resize", () => { -+ this.windowStateChangeHandler(mainWindowSizeKey, this.win); -+ }); -+ -+ this.win.on("move", () => { -+ this.windowStateChangeHandler(mainWindowSizeKey, this.win); -+ }); -+ this.win.on("focus", () => { -+ this.win.webContents.send("messagingService", { -+ command: "windowIsFocused", -+ windowIsFocused: true, -+ }); -+ }); -+ -+ if (this.createWindowCallback) { -+ this.createWindowCallback(this.win); -+ } -+ } -+ -+ async toggleAlwaysOnTop() { -+ this.enableAlwaysOnTop = !this.win.isAlwaysOnTop(); -+ this.win.setAlwaysOnTop(this.enableAlwaysOnTop); -+ await this.stateService.setEnableAlwaysOnTop(this.enableAlwaysOnTop); -+ } -+ -+ private windowStateChangeHandler(configKey: string, win: BrowserWindow) { -+ global.clearTimeout(this.windowStateChangeTimer); -+ this.windowStateChangeTimer = global.setTimeout(async () => { -+ await this.updateWindowState(configKey, win); -+ }, WindowEventHandlingDelay); -+ } -+ -+ private async updateWindowState(configKey: string, win: BrowserWindow) { -+ if (win == null) { -+ return; - } - -- private async updateWindowState(configKey: string, win: BrowserWindow) { -- if (win == null) { -- return; -- } -- -- try { -- const bounds = win.getBounds(); -- -- if (this.windowStates[configKey] == null) { -- this.windowStates[configKey] = await this.storageService.get(configKey); -- if (this.windowStates[configKey] == null) { -- this.windowStates[configKey] = {}; -- } -- } -- -- this.windowStates[configKey].isMaximized = win.isMaximized(); -- this.windowStates[configKey].displayBounds = screen.getDisplayMatching(bounds).bounds; -- -- if (!win.isMaximized() && !win.isMinimized() && !win.isFullScreen()) { -- this.windowStates[configKey].x = bounds.x; -- this.windowStates[configKey].y = bounds.y; -- this.windowStates[configKey].width = bounds.width; -- this.windowStates[configKey].height = bounds.height; -- } -+ try { -+ const bounds = win.getBounds(); - -- await this.storageService.save(configKey, this.windowStates[configKey]); -- } catch (e) { -- this.logService.error(e); -+ if (this.windowStates[configKey] == null) { -+ this.windowStates[configKey] = await this.stateService.getWindow(); -+ if (this.windowStates[configKey] == null) { -+ this.windowStates[configKey] = {}; - } -- } -+ } - -- private async getWindowState(configKey: string, defaultWidth: number, defaultHeight: number) { -- let state = await this.storageService.get(configKey); -- -- const isValid = state != null && (this.stateHasBounds(state) || state.isMaximized); -- let displayBounds: Electron.Rectangle = null; -- if (!isValid) { -- state = { -- width: defaultWidth, -- height: defaultHeight, -- }; -- -- displayBounds = screen.getPrimaryDisplay().bounds; -- } else if (this.stateHasBounds(state) && state.displayBounds) { -- // Check if the display where the window was last open is still available -- displayBounds = screen.getDisplayMatching(state.displayBounds).bounds; -- -- if (displayBounds.width !== state.displayBounds.width || -- displayBounds.height !== state.displayBounds.height || -- displayBounds.x !== state.displayBounds.x || -- displayBounds.y !== state.displayBounds.y) { -- state.x = undefined; -- state.y = undefined; -- displayBounds = screen.getPrimaryDisplay().bounds; -- } -- } -+ this.windowStates[configKey].isMaximized = win.isMaximized(); -+ this.windowStates[configKey].displayBounds = screen.getDisplayMatching(bounds).bounds; - -- if (displayBounds != null) { -- if (state.width > displayBounds.width && state.height > displayBounds.height) { -- state.isMaximized = true; -- } -- -- if (state.width > displayBounds.width) { -- state.width = displayBounds.width - 10; -- } -- if (state.height > displayBounds.height) { -- state.height = displayBounds.height - 10; -- } -- } -+ if (!win.isMaximized() && !win.isMinimized() && !win.isFullScreen()) { -+ this.windowStates[configKey].x = bounds.x; -+ this.windowStates[configKey].y = bounds.y; -+ this.windowStates[configKey].width = bounds.width; -+ this.windowStates[configKey].height = bounds.height; -+ } - -- return state; -+ await this.stateService.setWindow(this.windowStates[configKey]); -+ } catch (e) { -+ this.logService.error(e); -+ } -+ } -+ -+ private async getWindowState(defaultWidth: number, defaultHeight: number) { -+ const state = await this.stateService.getWindow(); -+ -+ const isValid = state != null && (this.stateHasBounds(state) || state.isMaximized); -+ let displayBounds: Electron.Rectangle = null; -+ if (!isValid) { -+ state.width = defaultWidth; -+ state.height = defaultHeight; -+ -+ displayBounds = screen.getPrimaryDisplay().bounds; -+ } else if (this.stateHasBounds(state) && state.displayBounds) { -+ // Check if the display where the window was last open is still available -+ displayBounds = screen.getDisplayMatching(state.displayBounds).bounds; -+ -+ if ( -+ displayBounds.width !== state.displayBounds.width || -+ displayBounds.height !== state.displayBounds.height || -+ displayBounds.x !== state.displayBounds.x || -+ displayBounds.y !== state.displayBounds.y -+ ) { -+ state.x = undefined; -+ state.y = undefined; -+ displayBounds = screen.getPrimaryDisplay().bounds; -+ } - } - -- private stateHasBounds(state: any): boolean { -- return state != null && Number.isInteger(state.x) && Number.isInteger(state.y) && -- Number.isInteger(state.width) && state.width > 0 && Number.isInteger(state.height) && state.height > 0; -+ if (displayBounds != null) { -+ if (state.width > displayBounds.width && state.height > displayBounds.height) { -+ state.isMaximized = true; -+ } -+ -+ if (state.width > displayBounds.width) { -+ state.width = displayBounds.width - 10; -+ } -+ if (state.height > displayBounds.height) { -+ state.height = displayBounds.height - 10; -+ } - } -+ -+ return state; -+ } -+ -+ private stateHasBounds(state: any): boolean { -+ return ( -+ state != null && -+ Number.isInteger(state.x) && -+ Number.isInteger(state.y) && -+ Number.isInteger(state.width) && -+ state.width > 0 && -+ Number.isInteger(state.height) && -+ state.height > 0 -+ ); -+ } - } -diff --git a/jslib/electron/tsconfig.json b/jslib/electron/tsconfig.json -index 3d41e86c..07c6a21d 100644 ---- a/jslib/electron/tsconfig.json -+++ b/jslib/electron/tsconfig.json -@@ -1,30 +1,10 @@ - { -+ "extends": "../shared/tsconfig", - "compilerOptions": { -- "pretty": true, -- "moduleResolution": "node", -- "noImplicitAny": true, -- "target": "ES6", -- "module": "commonjs", -- "lib": ["es5", "es6", "es7", "dom"], -- "sourceMap": true, -- "declaration": true, -- "allowSyntheticDefaultImports": true, -- "experimentalDecorators": true, -- "emitDecoratorMetadata": true, -- "declarationDir": "dist/types", -- "outDir": "dist", - "paths": { -- "jslib-common/*": [ -- "../common/src/*" -- ] -+ "jslib-common/*": ["../common/src/*"] - } - }, -- "include": [ -- "src", -- "spec" -- ], -- "exclude": [ -- "node_modules", -- "dist" -- ] -+ "include": ["src", "spec"], -+ "exclude": ["node_modules", "dist"] - } -diff --git a/jslib/node/package-lock.json b/jslib/node/package-lock.json -index ef230d0f..ab1b64a3 100644 ---- a/jslib/node/package-lock.json -+++ b/jslib/node/package-lock.json -@@ -21,13 +21,14 @@ - "devDependencies": { - "@types/inquirer": "^7.3.1", - "@types/lowdb": "^1.0.10", -- "@types/node": "^14.17.1", -+ "@types/node": "^16.11.12", - "@types/node-fetch": "^2.5.10", - "rimraf": "^3.0.2", -- "typescript": "4.1.5" -+ "typescript": "4.3.5" - } - }, - "../common": { -+ "name": "@bitwarden/jslib-common", - "version": "0.0.0", - "license": "GPL-3.0", - "dependencies": { -@@ -38,19 +39,19 @@ - "lunr": "^2.3.9", - "node-forge": "^0.10.0", - "papaparse": "^5.3.0", -- "rxjs": "6.6.7", -+ "rxjs": "^7.4.0", - "tldjs": "^2.3.1", - "zxcvbn": "^4.4.2" - }, - "devDependencies": { - "@types/lunr": "^2.3.3", -- "@types/node": "^14.17.1", -+ "@types/node": "^16.11.12", - "@types/node-forge": "^0.9.7", - "@types/papaparse": "^5.2.5", - "@types/tldjs": "^2.3.0", - "@types/zxcvbn": "^4.4.1", - "rimraf": "^3.0.2", -- "typescript": "4.1.5" -+ "typescript": "4.3.5" - } - }, - "node_modules/@bitwarden/jslib-common": { -@@ -59,7 +60,6 @@ - }, - "node_modules/@types/inquirer": { - "version": "7.3.3", -- "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-HhxyLejTHMfohAuhRun4csWigAMjXTmRyiJTU1Y/I1xmggikFMkOUoMQRlFm+zQcPEGHSs3io/0FAmNZf8EymQ==", - "dev": true, - "dependencies": { -@@ -69,13 +69,11 @@ - }, - "node_modules/@types/lodash": { - "version": "4.14.175", -- "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.175.tgz", - "integrity": "sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw==", - "dev": true - }, - "node_modules/@types/lowdb": { - "version": "1.0.11", -- "resolved": "https://registry.npmjs.org/@types/lowdb/-/lowdb-1.0.11.tgz", - "integrity": "sha512-h99VMxvTuz+VsXUVCCJo4dsps4vbkXwvU71TpmxDoiBU24bJ0VBygIHgmMm+UPoQIFihmV6euRik4z8J7XDJWg==", - "dev": true, - "dependencies": { -@@ -83,14 +81,13 @@ - } - }, - "node_modules/@types/node": { -- "version": "14.17.19", -- "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.19.tgz", -- "integrity": "sha512-jjYI6NkyfXykucU6ELEoT64QyKOdvaA6enOqKtP4xUsGY0X0ZUZz29fUmrTRo+7v7c6TgDu82q3GHHaCEkqZwA==", -+ "version": "16.11.12", -+ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", -+ "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==", - "dev": true - }, - "node_modules/@types/node-fetch": { - "version": "2.5.12", -- "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", - "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", - "dev": true, - "dependencies": { -@@ -100,7 +97,6 @@ - }, - "node_modules/@types/node-fetch/node_modules/form-data": { - "version": "3.0.1", -- "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "dependencies": { -@@ -114,7 +110,6 @@ - }, - "node_modules/@types/through": { - "version": "0.0.30", -- "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", - "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==", - "dev": true, - "dependencies": { -@@ -123,7 +118,6 @@ - }, - "node_modules/agent-base": { - "version": "6.0.2", -- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dependencies": { - "debug": "4" -@@ -134,7 +128,6 @@ - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", -- "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dependencies": { - "type-fest": "^0.21.3" -@@ -148,7 +141,6 @@ - }, - "node_modules/ansi-regex": { - "version": "5.0.1", -- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" -@@ -156,7 +148,6 @@ - }, - "node_modules/ansi-styles": { - "version": "4.3.0", -- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" -@@ -170,18 +161,15 @@ - }, - "node_modules/asynckit": { - "version": "0.4.0", -- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "node_modules/balanced-match": { - "version": "1.0.2", -- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", -- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { -@@ -191,7 +179,6 @@ - }, - "node_modules/chalk": { - "version": "4.1.2", -- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", -@@ -206,12 +193,10 @@ - }, - "node_modules/chardet": { - "version": "0.7.0", -- "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" - }, - "node_modules/cli-cursor": { - "version": "3.1.0", -- "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dependencies": { - "restore-cursor": "^3.1.0" -@@ -222,7 +207,6 @@ - }, - "node_modules/cli-width": { - "version": "3.0.0", -- "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "engines": { - "node": ">= 10" -@@ -230,7 +214,6 @@ - }, - "node_modules/color-convert": { - "version": "2.0.1", -- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" -@@ -241,12 +224,10 @@ - }, - "node_modules/color-name": { - "version": "1.1.4", -- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/combined-stream": { - "version": "1.0.8", -- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" -@@ -264,13 +245,11 @@ - }, - "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/debug": { - "version": "4.3.2", -- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dependencies": { - "ms": "2.1.2" -@@ -286,7 +265,6 @@ - }, - "node_modules/delayed-stream": { - "version": "1.0.0", -- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "engines": { - "node": ">=0.4.0" -@@ -294,12 +272,10 @@ - }, - "node_modules/emoji-regex": { - "version": "8.0.0", -- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", -- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "engines": { - "node": ">=0.8.0" -@@ -307,7 +283,6 @@ - }, - "node_modules/external-editor": { - "version": "3.1.0", -- "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dependencies": { - "chardet": "^0.7.0", -@@ -320,7 +295,6 @@ - }, - "node_modules/figures": { - "version": "3.2.0", -- "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dependencies": { - "escape-string-regexp": "^1.0.5" -@@ -346,13 +320,11 @@ - }, - "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": { -@@ -372,12 +344,10 @@ - }, - "node_modules/graceful-fs": { - "version": "4.2.8", -- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" - }, - "node_modules/has-flag": { - "version": "4.0.0", -- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" -@@ -385,7 +355,6 @@ - }, - "node_modules/https-proxy-agent": { - "version": "5.0.0", -- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dependencies": { - "agent-base": "6", -@@ -397,7 +366,6 @@ - }, - "node_modules/iconv-lite": { - "version": "0.4.24", -- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" -@@ -408,7 +376,6 @@ - }, - "node_modules/inflight": { - "version": "1.0.6", -- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { -@@ -418,13 +385,11 @@ - }, - "node_modules/inherits": { - "version": "2.0.4", -- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/inquirer": { - "version": "8.0.0", -- "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.0.0.tgz", - "integrity": "sha512-ON8pEJPPCdyjxj+cxsYRe6XfCJepTxANdNnTebsTuQgXpRyZRRT9t4dJwjRubgmvn20CLSEnozRUayXyM9VTXA==", - "dependencies": { - "ansi-escapes": "^4.2.1", -@@ -447,7 +412,6 @@ - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", -- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" -@@ -455,17 +419,14 @@ - }, - "node_modules/is-promise": { - "version": "2.2.2", -- "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" - }, - "node_modules/lodash": { - "version": "4.17.21", -- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lowdb": { - "version": "1.0.0", -- "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-1.0.0.tgz", - "integrity": "sha512-2+x8esE/Wb9SQ1F9IHaYWfsC9FIecLOPrK4g17FGEayjUWH172H6nwicRovGvSE2CPZouc2MCIqCI7h9d+GftQ==", - "dependencies": { - "graceful-fs": "^4.1.3", -@@ -479,19 +440,17 @@ - } - }, - "node_modules/mime-db": { -- "version": "1.49.0", -- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", -- "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", -+ "version": "1.50.0", -+ "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { -- "version": "2.1.32", -- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", -- "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", -+ "version": "2.1.33", -+ "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", - "dependencies": { -- "mime-db": "1.49.0" -+ "mime-db": "1.50.0" - }, - "engines": { - "node": ">= 0.6" -@@ -499,7 +458,6 @@ - }, - "node_modules/mimic-fn": { - "version": "2.1.0", -- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "engines": { - "node": ">=6" -@@ -507,7 +465,6 @@ - }, - "node_modules/minimatch": { - "version": "3.0.4", -- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { -@@ -519,17 +476,14 @@ - }, - "node_modules/ms": { - "version": "2.1.2", -- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/mute-stream": { - "version": "0.0.8", -- "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" - }, - "node_modules/node-fetch": { - "version": "2.6.5", -- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", - "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", - "dependencies": { - "whatwg-url": "^5.0.0" -@@ -540,7 +494,6 @@ - }, - "node_modules/once": { - "version": "1.4.0", -- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { -@@ -549,7 +502,6 @@ - }, - "node_modules/onetime": { - "version": "5.1.2", -- "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "^2.1.0" -@@ -563,7 +515,6 @@ - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", -- "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "engines": { - "node": ">=0.10.0" -@@ -571,7 +522,6 @@ - }, - "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": { -@@ -580,7 +530,6 @@ - }, - "node_modules/pify": { - "version": "3.0.0", -- "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "engines": { - "node": ">=4" -@@ -588,7 +537,6 @@ - }, - "node_modules/restore-cursor": { - "version": "3.1.0", -- "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dependencies": { - "onetime": "^5.1.0", -@@ -600,7 +548,6 @@ - }, - "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": { -@@ -615,7 +562,6 @@ - }, - "node_modules/run-async": { - "version": "2.4.1", -- "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "engines": { - "node": ">=0.12.0" -@@ -623,7 +569,6 @@ - }, - "node_modules/rxjs": { - "version": "6.6.7", -- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dependencies": { - "tslib": "^1.9.0" -@@ -634,17 +579,14 @@ - }, - "node_modules/safer-buffer": { - "version": "2.1.2", -- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/signal-exit": { -- "version": "3.0.4", -- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.4.tgz", -- "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==" -+ "version": "3.0.5", -+ "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==" - }, - "node_modules/steno": { - "version": "0.4.4", -- "resolved": "https://registry.npmjs.org/steno/-/steno-0.4.4.tgz", - "integrity": "sha1-BxEFvfwobmYVwEA8J+nXtdy4Vcs=", - "dependencies": { - "graceful-fs": "^4.1.3" -@@ -652,7 +594,6 @@ - }, - "node_modules/string-width": { - "version": "4.2.3", -- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", -@@ -665,7 +606,6 @@ - }, - "node_modules/strip-ansi": { - "version": "6.0.1", -- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" -@@ -676,7 +616,6 @@ - }, - "node_modules/supports-color": { - "version": "7.2.0", -- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" -@@ -687,12 +626,10 @@ - }, - "node_modules/through": { - "version": "2.3.8", -- "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "node_modules/tmp": { - "version": "0.0.33", -- "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dependencies": { - "os-tmpdir": "~1.0.2" -@@ -703,17 +640,14 @@ - }, - "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": "1.14.1", -- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/type-fest": { - "version": "0.21.3", -- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "engines": { - "node": ">=10" -@@ -723,9 +657,9 @@ - } - }, - "node_modules/typescript": { -- "version": "4.1.5", -- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz", -- "integrity": "sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==", -+ "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", -@@ -737,12 +671,10 @@ - }, - "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", -@@ -751,7 +683,6 @@ - }, - "node_modules/wrappy": { - "version": "1.0.2", -- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - } -@@ -763,7 +694,7 @@ - "@microsoft/signalr": "5.0.10", - "@microsoft/signalr-protocol-msgpack": "5.0.10", - "@types/lunr": "^2.3.3", -- "@types/node": "^14.17.1", -+ "@types/node": "^16.11.12", - "@types/node-forge": "^0.9.7", - "@types/papaparse": "^5.2.5", - "@types/tldjs": "^2.3.0", -@@ -774,15 +705,14 @@ - "node-forge": "^0.10.0", - "papaparse": "^5.3.0", - "rimraf": "^3.0.2", -- "rxjs": "6.6.7", -+ "rxjs": "^7.4.0", - "tldjs": "^2.3.1", -- "typescript": "4.1.5", -+ "typescript": "4.3.5", - "zxcvbn": "^4.4.2" - } - }, - "@types/inquirer": { - "version": "7.3.3", -- "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-HhxyLejTHMfohAuhRun4csWigAMjXTmRyiJTU1Y/I1xmggikFMkOUoMQRlFm+zQcPEGHSs3io/0FAmNZf8EymQ==", - "dev": true, - "requires": { -@@ -792,13 +722,11 @@ - }, - "@types/lodash": { - "version": "4.14.175", -- "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.175.tgz", - "integrity": "sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw==", - "dev": true - }, - "@types/lowdb": { - "version": "1.0.11", -- "resolved": "https://registry.npmjs.org/@types/lowdb/-/lowdb-1.0.11.tgz", - "integrity": "sha512-h99VMxvTuz+VsXUVCCJo4dsps4vbkXwvU71TpmxDoiBU24bJ0VBygIHgmMm+UPoQIFihmV6euRik4z8J7XDJWg==", - "dev": true, - "requires": { -@@ -806,14 +734,13 @@ - } - }, - "@types/node": { -- "version": "14.17.19", -- "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.19.tgz", -- "integrity": "sha512-jjYI6NkyfXykucU6ELEoT64QyKOdvaA6enOqKtP4xUsGY0X0ZUZz29fUmrTRo+7v7c6TgDu82q3GHHaCEkqZwA==", -+ "version": "16.11.12", -+ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", -+ "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==", - "dev": true - }, - "@types/node-fetch": { - "version": "2.5.12", -- "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", - "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", - "dev": true, - "requires": { -@@ -823,7 +750,6 @@ - "dependencies": { - "form-data": { - "version": "3.0.1", -- "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "requires": { -@@ -836,7 +762,6 @@ - }, - "@types/through": { - "version": "0.0.30", -- "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", - "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==", - "dev": true, - "requires": { -@@ -845,7 +770,6 @@ - }, - "agent-base": { - "version": "6.0.2", -- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "requires": { - "debug": "4" -@@ -853,7 +777,6 @@ - }, - "ansi-escapes": { - "version": "4.3.2", -- "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "requires": { - "type-fest": "^0.21.3" -@@ -861,12 +784,10 @@ - }, - "ansi-regex": { - "version": "5.0.1", -- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "ansi-styles": { - "version": "4.3.0", -- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" -@@ -874,18 +795,15 @@ - }, - "asynckit": { - "version": "0.4.0", -- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "balanced-match": { - "version": "1.0.2", -- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", -- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { -@@ -895,7 +813,6 @@ - }, - "chalk": { - "version": "4.1.2", -- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", -@@ -904,12 +821,10 @@ - }, - "chardet": { - "version": "0.7.0", -- "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" - }, - "cli-cursor": { - "version": "3.1.0", -- "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "requires": { - "restore-cursor": "^3.1.0" -@@ -917,12 +832,10 @@ - }, - "cli-width": { - "version": "3.0.0", -- "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" - }, - "color-convert": { - "version": "2.0.1", -- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" -@@ -930,29 +843,26 @@ - }, - "color-name": { - "version": "1.1.4", -- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "combined-stream": { - "version": "1.0.8", -- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { -- "version": "7.2.0" -+ "version": "7.2.0", -+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" - }, - "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 - }, - "debug": { - "version": "4.3.2", -- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "requires": { - "ms": "2.1.2" -@@ -960,22 +870,18 @@ - }, - "delayed-stream": { - "version": "1.0.0", -- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "emoji-regex": { - "version": "8.0.0", -- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "escape-string-regexp": { - "version": "1.0.5", -- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "external-editor": { - "version": "3.1.0", -- "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "requires": { - "chardet": "^0.7.0", -@@ -985,7 +891,6 @@ - }, - "figures": { - "version": "3.2.0", -- "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "requires": { - "escape-string-regexp": "^1.0.5" -@@ -993,6 +898,7 @@ - }, - "form-data": { - "version": "4.0.0", -+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", -@@ -1001,13 +907,11 @@ - }, - "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": { -@@ -1021,17 +925,14 @@ - }, - "graceful-fs": { - "version": "4.2.8", -- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" - }, - "has-flag": { - "version": "4.0.0", -- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "https-proxy-agent": { - "version": "5.0.0", -- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "requires": { - "agent-base": "6", -@@ -1040,7 +941,6 @@ - }, - "iconv-lite": { - "version": "0.4.24", -- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" -@@ -1048,7 +948,6 @@ - }, - "inflight": { - "version": "1.0.6", -- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { -@@ -1058,13 +957,11 @@ - }, - "inherits": { - "version": "2.0.4", -- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "inquirer": { - "version": "8.0.0", -- "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.0.0.tgz", - "integrity": "sha512-ON8pEJPPCdyjxj+cxsYRe6XfCJepTxANdNnTebsTuQgXpRyZRRT9t4dJwjRubgmvn20CLSEnozRUayXyM9VTXA==", - "requires": { - "ansi-escapes": "^4.2.1", -@@ -1084,22 +981,18 @@ - }, - "is-fullwidth-code-point": { - "version": "3.0.0", -- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "is-promise": { - "version": "2.2.2", -- "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" - }, - "lodash": { - "version": "4.17.21", -- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lowdb": { - "version": "1.0.0", -- "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-1.0.0.tgz", - "integrity": "sha512-2+x8esE/Wb9SQ1F9IHaYWfsC9FIecLOPrK4g17FGEayjUWH172H6nwicRovGvSE2CPZouc2MCIqCI7h9d+GftQ==", - "requires": { - "graceful-fs": "^4.1.3", -@@ -1110,26 +1003,22 @@ - } - }, - "mime-db": { -- "version": "1.49.0", -- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", -- "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==" -+ "version": "1.50.0", -+ "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==" - }, - "mime-types": { -- "version": "2.1.32", -- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", -- "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", -+ "version": "2.1.33", -+ "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", - "requires": { -- "mime-db": "1.49.0" -+ "mime-db": "1.50.0" - } - }, - "mimic-fn": { - "version": "2.1.0", -- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" - }, - "minimatch": { - "version": "3.0.4", -- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { -@@ -1138,17 +1027,14 @@ - }, - "ms": { - "version": "2.1.2", -- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "mute-stream": { - "version": "0.0.8", -- "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" - }, - "node-fetch": { - "version": "2.6.5", -- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", - "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", - "requires": { - "whatwg-url": "^5.0.0" -@@ -1156,7 +1042,6 @@ - }, - "once": { - "version": "1.4.0", -- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { -@@ -1165,7 +1050,6 @@ - }, - "onetime": { - "version": "5.1.2", -- "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "requires": { - "mimic-fn": "^2.1.0" -@@ -1173,23 +1057,19 @@ - }, - "os-tmpdir": { - "version": "1.0.2", -- "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "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 - }, - "pify": { - "version": "3.0.0", -- "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - }, - "restore-cursor": { - "version": "3.1.0", -- "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "requires": { - "onetime": "^5.1.0", -@@ -1198,7 +1078,6 @@ - }, - "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": { -@@ -1207,12 +1086,10 @@ - }, - "run-async": { - "version": "2.4.1", -- "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" - }, - "rxjs": { - "version": "6.6.7", -- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "requires": { - "tslib": "^1.9.0" -@@ -1220,17 +1097,14 @@ - }, - "safer-buffer": { - "version": "2.1.2", -- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "signal-exit": { -- "version": "3.0.4", -- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.4.tgz", -- "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==" -+ "version": "3.0.5", -+ "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==" - }, - "steno": { - "version": "0.4.4", -- "resolved": "https://registry.npmjs.org/steno/-/steno-0.4.4.tgz", - "integrity": "sha1-BxEFvfwobmYVwEA8J+nXtdy4Vcs=", - "requires": { - "graceful-fs": "^4.1.3" -@@ -1238,7 +1112,6 @@ - }, - "string-width": { - "version": "4.2.3", -- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", -@@ -1248,7 +1121,6 @@ - }, - "strip-ansi": { - "version": "6.0.1", -- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" -@@ -1256,7 +1128,6 @@ - }, - "supports-color": { - "version": "7.2.0", -- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" -@@ -1264,12 +1135,10 @@ - }, - "through": { - "version": "2.3.8", -- "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "tmp": { - "version": "0.0.33", -- "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "requires": { - "os-tmpdir": "~1.0.2" -@@ -1277,33 +1146,28 @@ - }, - "tr46": { - "version": "0.0.3", -- "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" - }, - "tslib": { - "version": "1.14.1", -- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "type-fest": { - "version": "0.21.3", -- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==" - }, - "typescript": { -- "version": "4.1.5", -- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz", -- "integrity": "sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==", -+ "version": "4.3.5", -+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", -+ "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", - "dev": true - }, - "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", -@@ -1312,7 +1176,6 @@ - }, - "wrappy": { - "version": "1.0.2", -- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - } -diff --git a/jslib/node/package.json b/jslib/node/package.json -index 607cfce1..af6c0e3a 100644 ---- a/jslib/node/package.json -+++ b/jslib/node/package.json -@@ -22,10 +22,10 @@ - "devDependencies": { - "@types/inquirer": "^7.3.1", - "@types/lowdb": "^1.0.10", -- "@types/node": "^14.17.1", -+ "@types/node": "^16.11.12", - "@types/node-fetch": "^2.5.10", - "rimraf": "^3.0.2", -- "typescript": "4.1.5" -+ "typescript": "4.3.5" - }, - "dependencies": { - "@bitwarden/jslib-common": "file:../common", -diff --git a/jslib/node/src/cli/baseProgram.ts b/jslib/node/src/cli/baseProgram.ts -index 7772ba80..b2ddd22f 100644 ---- a/jslib/node/src/cli/baseProgram.ts -+++ b/jslib/node/src/cli/baseProgram.ts -@@ -1,108 +1,113 @@ --import * as chalk from 'chalk'; -+import * as chalk from "chalk"; - --import { Response } from './models/response'; --import { ListResponse } from './models/response/listResponse'; --import { MessageResponse } from './models/response/messageResponse'; --import { StringResponse } from './models/response/stringResponse'; -+import { StateService } from "jslib-common/abstractions/state.service"; - --import { UserService } from 'jslib-common/abstractions/user.service'; -+import { Response } from "./models/response"; -+import { ListResponse } from "./models/response/listResponse"; -+import { MessageResponse } from "./models/response/messageResponse"; -+import { StringResponse } from "./models/response/stringResponse"; - - export abstract class BaseProgram { -- constructor( -- private userService: UserService, -- private writeLn: (s: string, finalLine: boolean, error: boolean) => void) { } -+ constructor( -+ protected stateService: StateService, -+ private writeLn: (s: string, finalLine: boolean, error: boolean) => void -+ ) {} - -- protected processResponse(response: Response, exitImmediately = false, dataProcessor: () => string = null) { -- if (!response.success) { -- if (process.env.BW_QUIET !== 'true') { -- if (process.env.BW_RESPONSE === 'true') { -- this.writeLn(this.getJson(response), true, false); -- } else { -- this.writeLn(chalk.redBright(response.message), true, true); -- } -- } -- const exitCode = process.env.BW_CLEANEXIT ? 0 : 1; -- if (exitImmediately) { -- process.exit(exitCode); -- } else { -- process.exitCode = exitCode; -- } -- return; -- } -- -- if (process.env.BW_RESPONSE === 'true') { -- this.writeLn(this.getJson(response), true, false); -- } else if (response.data != null) { -- let out: string = dataProcessor != null ? dataProcessor() : null; -- if (out == null) { -- if (response.data.object === 'string') { -- const data = (response.data as StringResponse).data; -- if (data != null) { -- out = data; -- } -- } else if (response.data.object === 'list') { -- out = this.getJson((response.data as ListResponse).data); -- } else if (response.data.object === 'message') { -- out = this.getMessage(response); -- } else { -- out = this.getJson(response.data); -- } -- } -- -- if (out != null && process.env.BW_QUIET !== 'true') { -- this.writeLn(out, true, false); -- } -- } -- if (exitImmediately) { -- process.exit(0); -+ protected processResponse( -+ response: Response, -+ exitImmediately = false, -+ dataProcessor: () => string = null -+ ) { -+ if (!response.success) { -+ if (process.env.BW_QUIET !== "true") { -+ if (process.env.BW_RESPONSE === "true") { -+ this.writeLn(this.getJson(response), true, false); - } else { -- process.exitCode = 0; -+ this.writeLn(chalk.redBright(response.message), true, true); - } -+ } -+ const exitCode = process.env.BW_CLEANEXIT ? 0 : 1; -+ if (exitImmediately) { -+ process.exit(exitCode); -+ } else { -+ process.exitCode = exitCode; -+ } -+ return; - } - -- protected getJson(obj: any): string { -- if (process.env.BW_PRETTY === 'true') { -- return JSON.stringify(obj, null, ' '); -+ if (process.env.BW_RESPONSE === "true") { -+ this.writeLn(this.getJson(response), true, false); -+ } else if (response.data != null) { -+ let out: string = dataProcessor != null ? dataProcessor() : null; -+ if (out == null) { -+ if (response.data.object === "string") { -+ const data = (response.data as StringResponse).data; -+ if (data != null) { -+ out = data; -+ } -+ } else if (response.data.object === "list") { -+ out = this.getJson((response.data as ListResponse).data); -+ } else if (response.data.object === "message") { -+ out = this.getMessage(response); - } else { -- return JSON.stringify(obj); -+ out = this.getJson(response.data); - } -+ } -+ -+ if (out != null && process.env.BW_QUIET !== "true") { -+ this.writeLn(out, true, false); -+ } -+ } -+ if (exitImmediately) { -+ process.exit(0); -+ } else { -+ process.exitCode = 0; - } -+ } - -- protected getMessage(response: Response): string { -- const message = (response.data as MessageResponse); -- if (process.env.BW_RAW === 'true') { -- return message.raw; -- } -+ protected getJson(obj: any): string { -+ if (process.env.BW_PRETTY === "true") { -+ return JSON.stringify(obj, null, " "); -+ } else { -+ return JSON.stringify(obj); -+ } -+ } - -- let out: string = ''; -- if (message.title != null) { -- if (message.noColor) { -- out = message.title; -- } else { -- out = chalk.greenBright(message.title); -- } -- } -- if (message.message != null) { -- if (message.title != null) { -- out += '\n'; -- } -- out += message.message; -- } -- return out.trim() === '' ? null : out; -+ protected getMessage(response: Response): string { -+ const message = response.data as MessageResponse; -+ if (process.env.BW_RAW === "true") { -+ return message.raw; - } - -- protected async exitIfAuthed() { -- const authed = await this.userService.isAuthenticated(); -- if (authed) { -- const email = await this.userService.getEmail(); -- this.processResponse(Response.error('You are already logged in as ' + email + '.'), true); -- } -+ let out: string = ""; -+ if (message.title != null) { -+ if (message.noColor) { -+ out = message.title; -+ } else { -+ out = chalk.greenBright(message.title); -+ } -+ } -+ if (message.message != null) { -+ if (message.title != null) { -+ out += "\n"; -+ } -+ out += message.message; - } -+ return out.trim() === "" ? null : out; -+ } - -- protected async exitIfNotAuthed() { -- const authed = await this.userService.isAuthenticated(); -- if (!authed) { -- this.processResponse(Response.error('You are not logged in.'), true); -- } -+ protected async exitIfAuthed() { -+ const authed = await this.stateService.getIsAuthenticated(); -+ if (authed) { -+ const email = await this.stateService.getEmail(); -+ this.processResponse(Response.error("You are already logged in as " + email + "."), true); -+ } -+ } -+ -+ protected async exitIfNotAuthed() { -+ const authed = await this.stateService.getIsAuthenticated(); -+ if (!authed) { -+ this.processResponse(Response.error("You are not logged in."), true); - } -+ } - } -diff --git a/jslib/node/src/cli/commands/login.command.ts b/jslib/node/src/cli/commands/login.command.ts -index 7cdb607e..8dfac8ec 100644 ---- a/jslib/node/src/cli/commands/login.command.ts -+++ b/jslib/node/src/cli/commands/login.command.ts -@@ -1,572 +1,610 @@ --import * as program from 'commander'; --import * as http from 'http'; --import * as inquirer from 'inquirer'; -+import * as program from "commander"; -+import * as http from "http"; -+import * as inquirer from "inquirer"; - --import { TwoFactorProviderType } from 'jslib-common/enums/twoFactorProviderType'; -+import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType"; - --import { AuthResult } from 'jslib-common/models/domain/authResult'; --import { TwoFactorEmailRequest } from 'jslib-common/models/request/twoFactorEmailRequest'; --import { ErrorResponse } from 'jslib-common/models/response/errorResponse'; -+import { AuthResult } from "jslib-common/models/domain/authResult"; -+import { TwoFactorEmailRequest } from "jslib-common/models/request/twoFactorEmailRequest"; -+import { ErrorResponse } from "jslib-common/models/response/errorResponse"; - --import { ApiService } from 'jslib-common/abstractions/api.service'; --import { AuthService } from 'jslib-common/abstractions/auth.service'; --import { CryptoService } from 'jslib-common/abstractions/crypto.service'; --import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; --import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; --import { PasswordGenerationService } from 'jslib-common/abstractions/passwordGeneration.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { PolicyService } from 'jslib-common/abstractions/policy.service'; --import { SyncService } from 'jslib-common/abstractions/sync.service'; --import { UserService } from 'jslib-common/abstractions/user.service'; -+import { ApiService } from "jslib-common/abstractions/api.service"; -+import { AuthService } from "jslib-common/abstractions/auth.service"; -+import { CryptoService } from "jslib-common/abstractions/crypto.service"; -+import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service"; -+import { EnvironmentService } from "jslib-common/abstractions/environment.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { PolicyService } from "jslib-common/abstractions/policy.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; - --import { Response } from '../models/response'; -+import { Response } from "../models/response"; - --import { KeyConnectorUserKeyRequest } from 'jslib-common/models/request/keyConnectorUserKeyRequest'; --import { UpdateTempPasswordRequest } from 'jslib-common/models/request/updateTempPasswordRequest'; -+import { UpdateTempPasswordRequest } from "jslib-common/models/request/updateTempPasswordRequest"; - --import { MessageResponse } from '../models/response/messageResponse'; -+import { MessageResponse } from "../models/response/messageResponse"; - --import { NodeUtils } from 'jslib-common/misc/nodeUtils'; --import { Utils } from 'jslib-common/misc/utils'; -+import { NodeUtils } from "jslib-common/misc/nodeUtils"; -+import { Utils } from "jslib-common/misc/utils"; - - // tslint:disable-next-line --const open = require('open'); -+const open = require("open"); - - export class LoginCommand { -- protected validatedParams: () => Promise; -- protected success: () => Promise; -- protected logout: () => Promise; -- protected canInteract: boolean; -- protected clientId: string; -- protected clientSecret: string; -- protected email: string; -- -- private ssoRedirectUri: string = null; -- -- constructor(protected authService: AuthService, protected apiService: ApiService, -- protected i18nService: I18nService, protected environmentService: EnvironmentService, -- protected passwordGenerationService: PasswordGenerationService, -- protected cryptoFunctionService: CryptoFunctionService, protected platformUtilsService: PlatformUtilsService, -- protected userService: UserService, protected cryptoService: CryptoService, -- protected policyService: PolicyService, clientId: string, private syncService: SyncService, -- protected keyConnectorService: KeyConnectorService) { -- this.clientId = clientId; -+ protected validatedParams: () => Promise; -+ protected success: () => Promise; -+ protected logout: () => Promise; -+ protected canInteract: boolean; -+ protected clientId: string; -+ protected clientSecret: string; -+ protected email: string; -+ -+ private ssoRedirectUri: string = null; -+ -+ constructor( -+ protected authService: AuthService, -+ protected apiService: ApiService, -+ protected i18nService: I18nService, -+ protected environmentService: EnvironmentService, -+ protected passwordGenerationService: PasswordGenerationService, -+ protected cryptoFunctionService: CryptoFunctionService, -+ protected platformUtilsService: PlatformUtilsService, -+ protected stateService: StateService, -+ protected cryptoService: CryptoService, -+ protected policyService: PolicyService, -+ clientId: string -+ ) { -+ this.clientId = clientId; -+ } -+ -+ async run(email: string, password: string, options: program.OptionValues) { -+ this.canInteract = process.env.BW_NOINTERACTION !== "true"; -+ -+ let ssoCodeVerifier: string = null; -+ let ssoCode: string = null; -+ let orgIdentifier: string = null; -+ -+ let clientId: string = null; -+ let clientSecret: string = null; -+ -+ if (options.apikey != null) { -+ const apiIdentifiers = await this.apiIdentifiers(); -+ clientId = apiIdentifiers.clientId; -+ clientSecret = apiIdentifiers.clientSecret; -+ } else if (options.sso != null && this.canInteract) { -+ const passwordOptions: any = { -+ type: "password", -+ length: 64, -+ uppercase: true, -+ lowercase: true, -+ numbers: true, -+ special: false, -+ }; -+ const state = await this.passwordGenerationService.generatePassword(passwordOptions); -+ ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); -+ const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256"); -+ const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); -+ try { -+ const ssoParams = await this.openSsoPrompt(codeChallenge, state); -+ ssoCode = ssoParams.ssoCode; -+ orgIdentifier = ssoParams.orgIdentifier; -+ } catch { -+ return Response.badRequest("Something went wrong. Try again."); -+ } -+ } else { -+ if ((email == null || email === "") && this.canInteract) { -+ const answer: inquirer.Answers = await inquirer.createPromptModule({ -+ output: process.stderr, -+ })({ -+ type: "input", -+ name: "email", -+ message: "Email address:", -+ }); -+ email = answer.email; -+ } -+ if (email == null || email.trim() === "") { -+ return Response.badRequest("Email address is required."); -+ } -+ if (email.indexOf("@") === -1) { -+ return Response.badRequest("Email address is invalid."); -+ } -+ this.email = email; -+ -+ if (password == null || password === "") { -+ if (options.passwordfile) { -+ password = await NodeUtils.readFirstLine(options.passwordfile); -+ } else if (options.passwordenv && process.env[options.passwordenv]) { -+ password = process.env[options.passwordenv]; -+ } else if (this.canInteract) { -+ const answer: inquirer.Answers = await inquirer.createPromptModule({ -+ output: process.stderr, -+ })({ -+ type: "password", -+ name: "password", -+ message: "Master password:", -+ }); -+ password = answer.password; -+ } -+ } -+ -+ if (password == null || password === "") { -+ return Response.badRequest("Master password is required."); -+ } - } - -- async run(email: string, password: string, options: program.OptionValues) { -- this.canInteract = process.env.BW_NOINTERACTION !== 'true'; -- -- let ssoCodeVerifier: string = null; -- let ssoCode: string = null; -- let orgIdentifier: string = null; -- -- let clientId: string = null; -- let clientSecret: string = null; -- -- if (options.apikey != null) { -- const apiIdentifiers = await this.apiIdentifiers(); -- clientId = apiIdentifiers.clientId; -- clientSecret = apiIdentifiers.clientSecret; -- } else if (options.sso != null && this.canInteract) { -- const passwordOptions: any = { -- type: 'password', -- length: 64, -- uppercase: true, -- lowercase: true, -- numbers: true, -- special: false, -- }; -- const state = await this.passwordGenerationService.generatePassword(passwordOptions); -- ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); -- const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, 'sha256'); -- const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); -- try { -- const ssoParams = await this.openSsoPrompt(codeChallenge, state); -- ssoCode = ssoParams.ssoCode; -- orgIdentifier = ssoParams.orgIdentifier; -- } catch { -- return Response.badRequest('Something went wrong. Try again.'); -- } -- } else { -- if ((email == null || email === '') && this.canInteract) { -- const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ -- type: 'input', -- name: 'email', -- message: 'Email address:', -- }); -- email = answer.email; -- } -- if (email == null || email.trim() === '') { -- return Response.badRequest('Email address is required.'); -- } -- if (email.indexOf('@') === -1) { -- return Response.badRequest('Email address is invalid.'); -- } -- this.email = email; -- -- if (password == null || password === '') { -- if (options.passwordfile) { -- password = await NodeUtils.readFirstLine(options.passwordfile); -- } else if (options.passwordenv && process.env[options.passwordenv]) { -- password = process.env[options.passwordenv]; -- } else if (this.canInteract) { -- const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ -- type: 'password', -- name: 'password', -- message: 'Master password:', -- }); -- password = answer.password; -- } -- } -+ let twoFactorToken: string = options.code; -+ let twoFactorMethod: TwoFactorProviderType = null; -+ try { -+ if (options.method != null) { -+ twoFactorMethod = parseInt(options.method, null); -+ } -+ } catch (e) { -+ return Response.error("Invalid two-step login method."); -+ } - -- if (password == null || password === '') { -- return Response.badRequest('Master password is required.'); -- } -+ try { -+ if (this.validatedParams != null) { -+ await this.validatedParams(); -+ } -+ -+ let response: AuthResult = null; -+ if (twoFactorToken != null && twoFactorMethod != null) { -+ if (clientId != null && clientSecret != null) { -+ response = await this.authService.logInApiKeyComplete( -+ clientId, -+ clientSecret, -+ twoFactorMethod, -+ twoFactorToken, -+ false -+ ); -+ } else if (ssoCode != null && ssoCodeVerifier != null) { -+ response = await this.authService.logInSsoComplete( -+ ssoCode, -+ ssoCodeVerifier, -+ this.ssoRedirectUri, -+ twoFactorMethod, -+ twoFactorToken, -+ false -+ ); -+ } else { -+ response = await this.authService.logInComplete( -+ email, -+ password, -+ twoFactorMethod, -+ twoFactorToken, -+ false, -+ this.clientSecret -+ ); - } -- -- let twoFactorToken: string = options.code; -- let twoFactorMethod: TwoFactorProviderType = null; -- try { -- if (options.method != null) { -- twoFactorMethod = parseInt(options.method, null); -- } -- } catch (e) { -- return Response.error('Invalid two-step login method.'); -+ } else { -+ if (clientId != null && clientSecret != null) { -+ response = await this.authService.logInApiKey(clientId, clientSecret); -+ } else if (ssoCode != null && ssoCodeVerifier != null) { -+ response = await this.authService.logInSso( -+ ssoCode, -+ ssoCodeVerifier, -+ this.ssoRedirectUri, -+ orgIdentifier -+ ); -+ } else { -+ response = await this.authService.logIn(email, password); - } -- -- try { -- if (this.validatedParams != null) { -- await this.validatedParams(); -+ if (response.captchaSiteKey) { -+ const badCaptcha = Response.badRequest( -+ "Your authentication request appears to be coming from a bot\n" + -+ "Please use your API key to validate this request and ensure BW_CLIENTSECRET is correct, if set.\n" + -+ "(https://bitwarden.com/help/article/cli-auth-challenges)" -+ ); -+ -+ try { -+ const captchaClientSecret = await this.apiClientSecret(true); -+ if (Utils.isNullOrWhitespace(captchaClientSecret)) { -+ return badCaptcha; - } - -- let response: AuthResult = null; -- if (twoFactorToken != null && twoFactorMethod != null) { -- if (clientId != null && clientSecret != null) { -- response = await this.authService.logInApiKeyComplete(clientId, clientSecret, twoFactorMethod, -- twoFactorToken, false); -- } else if (ssoCode != null && ssoCodeVerifier != null) { -- response = await this.authService.logInSsoComplete(ssoCode, ssoCodeVerifier, this.ssoRedirectUri, -- twoFactorMethod, twoFactorToken, false); -- } else { -- response = await this.authService.logInComplete(email, password, twoFactorMethod, -- twoFactorToken, false, this.clientSecret); -- } -+ const secondResponse = await this.authService.logInComplete( -+ email, -+ password, -+ twoFactorMethod, -+ twoFactorToken, -+ false, -+ captchaClientSecret -+ ); -+ response = secondResponse; -+ } catch (e) { -+ if ( -+ (e instanceof ErrorResponse || e.constructor.name === "ErrorResponse") && -+ (e as ErrorResponse).message.includes("Captcha is invalid") -+ ) { -+ return badCaptcha; - } else { -- if (clientId != null && clientSecret != null) { -- response = await this.authService.logInApiKey(clientId, clientSecret); -- } else if (ssoCode != null && ssoCodeVerifier != null) { -- response = await this.authService.logInSso(ssoCode, ssoCodeVerifier, this.ssoRedirectUri, -- orgIdentifier); -- } else { -- response = await this.authService.logIn(email, password); -- } -- if (response.captchaSiteKey) { -- const badCaptcha = Response.badRequest('Your authentication request appears to be coming from a bot\n' + -- 'Please use your API key to validate this request and ensure BW_CLIENTSECRET is correct, if set.\n' + -- '(https://bitwarden.com/help/article/cli-auth-challenges)'); -- -- try { -- const captchaClientSecret = await this.apiClientSecret(true); -- if (Utils.isNullOrWhitespace(captchaClientSecret)) { -- return badCaptcha; -- } -- -- const secondResponse = await this.authService.logInComplete(email, password, twoFactorMethod, -- twoFactorToken, false, captchaClientSecret); -- response = secondResponse; -- } catch (e) { -- if ((e instanceof ErrorResponse || e.constructor.name === 'ErrorResponse') && -- (e as ErrorResponse).message.includes('Captcha is invalid')) { -- return badCaptcha; -- } else { -- throw e; -- } -- } -- } -- if (response.twoFactor) { -- let selectedProvider: any = null; -- const twoFactorProviders = this.authService.getSupportedTwoFactorProviders(null); -- if (twoFactorProviders.length === 0) { -- return Response.badRequest('No providers available for this client.'); -- } -- -- if (twoFactorMethod != null) { -- try { -- selectedProvider = twoFactorProviders.filter(p => p.type === twoFactorMethod)[0]; -- } catch (e) { -- return Response.error('Invalid two-step login method.'); -- } -- } -- -- if (selectedProvider == null) { -- if (twoFactorProviders.length === 1) { -- selectedProvider = twoFactorProviders[0]; -- } else if (this.canInteract) { -- const twoFactorOptions = twoFactorProviders.map(p => p.name); -- twoFactorOptions.push(new inquirer.Separator()); -- twoFactorOptions.push('Cancel'); -- const answer: inquirer.Answers = -- await inquirer.createPromptModule({ output: process.stderr })({ -- type: 'list', -- name: 'method', -- message: 'Two-step login method:', -- choices: twoFactorOptions, -- }); -- const i = twoFactorOptions.indexOf(answer.method); -- if (i === (twoFactorOptions.length - 1)) { -- return Response.error('Login failed.'); -- } -- selectedProvider = twoFactorProviders[i]; -- } -- if (selectedProvider == null) { -- return Response.error('Login failed. No provider selected.'); -- } -- } -- -- if (twoFactorToken == null && response.twoFactorProviders.size > 1 && -- selectedProvider.type === TwoFactorProviderType.Email) { -- const emailReq = new TwoFactorEmailRequest(); -- emailReq.email = this.authService.email; -- emailReq.masterPasswordHash = this.authService.masterPasswordHash; -- await this.apiService.postTwoFactorEmail(emailReq); -- } -- -- if (twoFactorToken == null) { -- if (this.canInteract) { -- const answer: inquirer.Answers = -- await inquirer.createPromptModule({ output: process.stderr })({ -- type: 'input', -- name: 'token', -- message: 'Two-step login code:', -- }); -- twoFactorToken = answer.token; -- } -- if (twoFactorToken == null || twoFactorToken === '') { -- return Response.badRequest('Code is required.'); -- } -- } -- -- response = await this.authService.logInTwoFactor(selectedProvider.type, -- twoFactorToken, false); -- } -+ throw e; - } -- -- if (response.twoFactor) { -- return Response.error('Login failed.'); -+ } -+ } -+ if (response.twoFactor) { -+ let selectedProvider: any = null; -+ const twoFactorProviders = this.authService.getSupportedTwoFactorProviders(null); -+ if (twoFactorProviders.length === 0) { -+ return Response.badRequest("No providers available for this client."); -+ } -+ -+ if (twoFactorMethod != null) { -+ try { -+ selectedProvider = twoFactorProviders.filter((p) => p.type === twoFactorMethod)[0]; -+ } catch (e) { -+ return Response.error("Invalid two-step login method."); - } -- -- if (response.resetMasterPassword) { -- return Response.error('In order to log in with SSO from the CLI, you must first log in' + -- ' through the web vault to set your master password.'); -+ } -+ -+ if (selectedProvider == null) { -+ if (twoFactorProviders.length === 1) { -+ selectedProvider = twoFactorProviders[0]; -+ } else if (this.canInteract) { -+ const twoFactorOptions = twoFactorProviders.map((p) => p.name); -+ twoFactorOptions.push(new inquirer.Separator()); -+ twoFactorOptions.push("Cancel"); -+ const answer: inquirer.Answers = await inquirer.createPromptModule({ -+ output: process.stderr, -+ })({ -+ type: "list", -+ name: "method", -+ message: "Two-step login method:", -+ choices: twoFactorOptions, -+ }); -+ const i = twoFactorOptions.indexOf(answer.method); -+ if (i === twoFactorOptions.length - 1) { -+ return Response.error("Login failed."); -+ } -+ selectedProvider = twoFactorProviders[i]; - } -- -- // Full sync required for the reset password and key connector checks -- await this.syncService.fullSync(true); -- -- // Handle converting to Key Connector if required -- if (await this.keyConnectorService.userNeedsMigration()) { -- return await this.migrateToKeyConnector(); -+ if (selectedProvider == null) { -+ return Response.error("Login failed. No provider selected."); - } -- -- // Handle Updating Temp Password if NOT using an API Key for authentication -- if (response.forcePasswordReset && (clientId == null && clientSecret == null)) { -- return await this.updateTempPassword(); -+ } -+ -+ if ( -+ twoFactorToken == null && -+ response.twoFactorProviders.size > 1 && -+ selectedProvider.type === TwoFactorProviderType.Email -+ ) { -+ const emailReq = new TwoFactorEmailRequest(); -+ emailReq.email = this.authService.email; -+ emailReq.masterPasswordHash = this.authService.masterPasswordHash; -+ await this.apiService.postTwoFactorEmail(emailReq); -+ } -+ -+ if (twoFactorToken == null) { -+ if (this.canInteract) { -+ const answer: inquirer.Answers = await inquirer.createPromptModule({ -+ output: process.stderr, -+ })({ -+ type: "input", -+ name: "token", -+ message: "Two-step login code:", -+ }); -+ twoFactorToken = answer.token; -+ } -+ if (twoFactorToken == null || twoFactorToken === "") { -+ return Response.badRequest("Code is required."); - } -+ } - -- return await this.handleSuccessResponse(); -- } catch (e) { -- return Response.error(e); -+ response = await this.authService.logInTwoFactor( -+ selectedProvider.type, -+ twoFactorToken, -+ false -+ ); - } -+ } -+ -+ if (response.twoFactor) { -+ return Response.error("Login failed."); -+ } -+ -+ if (response.resetMasterPassword) { -+ return Response.error( -+ "In order to log in with SSO from the CLI, you must first log in" + -+ " through the web vault to set your master password." -+ ); -+ } -+ -+ // Handle Updating Temp Password if NOT using an API Key for authentication -+ if (response.forcePasswordReset && clientId == null && clientSecret == null) { -+ return await this.updateTempPassword(); -+ } -+ -+ return await this.handleSuccessResponse(); -+ } catch (e) { -+ return Response.error(e); - } -- -- private async handleSuccessResponse(): Promise { -- if (this.success != null) { -- const res = await this.success(); -- return Response.success(res); -- } else { -- const res = new MessageResponse('You are logged in!', null); -- return Response.success(res); -- } -+ } -+ -+ private async handleSuccessResponse(): Promise { -+ if (this.success != null) { -+ const res = await this.success(); -+ return Response.success(res); -+ } else { -+ const res = new MessageResponse("You are logged in!", null); -+ return Response.success(res); - } -- -- private async updateTempPassword(error?: string): Promise { -- // If no interaction available, alert user to use web vault -- if (!this.canInteract) { -- await this.logout(); -- this.authService.logOut(() => { /* Do nothing */ }); -- return Response.error(new MessageResponse('An organization administrator recently changed your master password. In order to access the vault, you must update your master password now via the web vault. You have been logged out.', null)); -- } -- -- if (this.email == null || this.email === 'undefined') { -- this.email = await this.userService.getEmail(); -- } -- -- // Get New Master Password -- const baseMessage = 'An organization administrator recently changed your master password. In order to access the vault, you must update your master password now.\n' + 'Master password: '; -- const firstMessage = error != null ? error + baseMessage : baseMessage; -- const mp: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ -- type: 'password', -- name: 'password', -- message: firstMessage, -- }); -- const masterPassword = mp.password; -- -- // Master Password Validation -- if (masterPassword == null || masterPassword === '') { -- return this.updateTempPassword('Master password is required.\n'); -- } -- -- if (masterPassword.length < 8) { -- return this.updateTempPassword('Master password must be at least 8 characters long.\n'); -- } -- -- // Strength & Policy Validation -- const strengthResult = this.passwordGenerationService.passwordStrength(masterPassword, -- this.getPasswordStrengthUserInput()); -- -- // Get New Master Password Re-type -- const reTypeMessage = 'Re-type New Master password (Strength: ' + strengthResult.score + ')'; -- const retype: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ -- type: 'password', -- name: 'password', -- message: reTypeMessage, -- }); -- const masterPasswordRetype = retype.password; -- -- // Re-type Validation -- if (masterPassword !== masterPasswordRetype) { -- return this.updateTempPassword('Master password confirmation does not match.\n'); -- } -- -- // Get Hint (optional) -- const hint: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ -- type: 'input', -- name: 'input', -- message: 'Master Password Hint (optional):', -- }); -- const masterPasswordHint = hint.input; -- -- // Retrieve details for key generation -- const enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(); -- const kdf = await this.userService.getKdf(); -- const kdfIterations = await this.userService.getKdfIterations(); -- -- if (enforcedPolicyOptions != null && -- !this.policyService.evaluateMasterPassword( -- strengthResult.score, -- masterPassword, -- enforcedPolicyOptions)) { -- return this.updateTempPassword('Your new master password does not meet the policy requirements.\n'); -- } -- -- try { -- // Create new key and hash new password -- const newKey = await this.cryptoService.makeKey(masterPassword, this.email.trim().toLowerCase(), -- kdf, kdfIterations); -- const newPasswordHash = await this.cryptoService.hashPassword(masterPassword, newKey); -- -- // Grab user's current enc key -- const userEncKey = await this.cryptoService.getEncKey(); -- -- // Create new encKey for the User -- const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey); -- -- // Create request -- const request = new UpdateTempPasswordRequest(); -- request.key = newEncKey[1].encryptedString; -- request.newMasterPasswordHash = newPasswordHash; -- request.masterPasswordHint = masterPasswordHint; -- -- // Update user's password -- await this.apiService.putUpdateTempPassword(request); -- return this.handleSuccessResponse(); -- } catch (e) { -- await this.logout(); -- this.authService.logOut(() => { /* Do nothing */ }); -- return Response.error(e); -- } -+ } -+ -+ private async updateTempPassword(error?: string): Promise { -+ // If no interaction available, alert user to use web vault -+ if (!this.canInteract) { -+ await this.logout(); -+ this.authService.logOut(() => { -+ /* Do nothing */ -+ }); -+ return Response.error( -+ new MessageResponse( -+ "An organization administrator recently changed your master password. In order to access the vault, you must update your master password now via the web vault. You have been logged out.", -+ null -+ ) -+ ); - } - -- private getPasswordStrengthUserInput() { -- let userInput: string[] = []; -- const atPosition = this.email.indexOf('@'); -- if (atPosition > -1) { -- userInput = userInput.concat(this.email.substr(0, atPosition).trim().toLowerCase().split(/[^A-Za-z0-9]/)); -- } -- return userInput; -+ if (this.email == null || this.email === "undefined") { -+ this.email = await this.stateService.getEmail(); - } - -- private async migrateToKeyConnector() { -- // If no interaction available, alert user to use web vault -- if (!this.canInteract) { -- await this.logout(); -- this.authService.logOut(() => { /* Do nothing */ }); -- return Response.error(new MessageResponse('An organization you are a member of is using Key Connector. ' + -- 'In order to access the vault, you must opt-in to Key Connector now via the web vault. You have been logged out.', null)); -- } -- -- const organization = await this.keyConnectorService.getManagingOrganization(); -- -- const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ -- type: 'list', -- name: 'convert', -- message: organization.name + ' is using a self-hosted key server. A master password is no longer required to log in for members of this organization. ', -- choices: [ -- { -- name: 'Remove master password and log in', -- value: 'remove', -- }, -- { -- name: 'Leave organization and log in', -- value: 'leave', -- }, -- { -- name: 'Exit', -- value: 'exit', -- }, -- ], -- }); -- -- if (answer.convert === 'remove') { -- await this.keyConnectorService.migrateUser(); -- -- // Update environment URL - required for api key login -- const urls = this.environmentService.getUrls(); -- urls.keyConnector = organization.keyConnectorUrl; -- await this.environmentService.setUrls(urls, true); -- -- return await this.handleSuccessResponse(); -- } else if (answer.convert === 'leave') { -- await this.apiService.postLeaveOrganization(organization.id); -- await this.syncService.fullSync(true); -- return await this.handleSuccessResponse(); -- } else { -- await this.logout(); -- this.authService.logOut(() => { /* Do nothing */ }); -- return Response.error('You have been logged out.'); -- } -+ // Get New Master Password -+ const baseMessage = -+ "An organization administrator recently changed your master password. In order to access the vault, you must update your master password now.\n" + -+ "Master password: "; -+ const firstMessage = error != null ? error + baseMessage : baseMessage; -+ const mp: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ -+ type: "password", -+ name: "password", -+ message: firstMessage, -+ }); -+ const masterPassword = mp.password; -+ -+ // Master Password Validation -+ if (masterPassword == null || masterPassword === "") { -+ return this.updateTempPassword("Master password is required.\n"); - } - -- private async apiClientId(): Promise { -- let clientId: string = null; -- -- const storedClientId: string = process.env.BW_CLIENTID; -- if (storedClientId == null) { -- if (this.canInteract) { -- const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ -- type: 'input', -- name: 'clientId', -- message: 'client_id:', -- }); -- clientId = answer.clientId; -- } else { -- clientId = null; -- } -- } else { -- clientId = storedClientId; -- } -- -- return clientId; -+ if (masterPassword.length < 8) { -+ return this.updateTempPassword("Master password must be at least 8 characters long.\n"); - } - -- private async apiClientSecret(isAdditionalAuthentication: boolean = false): Promise { -- const additionalAuthenticationMessage = 'Additional authentication required.\nAPI key '; -- let clientSecret: string = null; -- -- const storedClientSecret: string = this.clientSecret || process.env.BW_CLIENTSECRET; -- if (this.canInteract && storedClientSecret == null) { -- const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ -- type: 'input', -- name: 'clientSecret', -- message: (isAdditionalAuthentication ? additionalAuthenticationMessage : '') + 'client_secret:', -- -- }); -- clientSecret = answer.clientSecret; -- } else { -- clientSecret = storedClientSecret; -- } -- -- return clientSecret; -+ // Strength & Policy Validation -+ const strengthResult = this.passwordGenerationService.passwordStrength( -+ masterPassword, -+ this.getPasswordStrengthUserInput() -+ ); -+ -+ // Get New Master Password Re-type -+ const reTypeMessage = "Re-type New Master password (Strength: " + strengthResult.score + ")"; -+ const retype: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ -+ type: "password", -+ name: "password", -+ message: reTypeMessage, -+ }); -+ const masterPasswordRetype = retype.password; -+ -+ // Re-type Validation -+ if (masterPassword !== masterPasswordRetype) { -+ return this.updateTempPassword("Master password confirmation does not match.\n"); - } - -- private async apiIdentifiers(): Promise<{ clientId: string, clientSecret: string; }> { -- return { -- clientId: await this.apiClientId(), -- clientSecret: await this.apiClientSecret(), -- }; -+ // Get Hint (optional) -+ const hint: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ -+ type: "input", -+ name: "input", -+ message: "Master Password Hint (optional):", -+ }); -+ const masterPasswordHint = hint.input; -+ -+ // Retrieve details for key generation -+ const enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(); -+ const kdf = await this.stateService.getKdfType(); -+ const kdfIterations = await this.stateService.getKdfIterations(); -+ -+ if ( -+ enforcedPolicyOptions != null && -+ !this.policyService.evaluateMasterPassword( -+ strengthResult.score, -+ masterPassword, -+ enforcedPolicyOptions -+ ) -+ ) { -+ return this.updateTempPassword( -+ "Your new master password does not meet the policy requirements.\n" -+ ); - } - -- private async openSsoPrompt(codeChallenge: string, state: string): Promise<{ ssoCode: string, orgIdentifier: string }> { -- return new Promise((resolve, reject) => { -- const callbackServer = http.createServer((req, res) => { -- const urlString = 'http://localhost' + req.url; -- const url = new URL(urlString); -- const code = url.searchParams.get('code'); -- const receivedState = url.searchParams.get('state'); -- const orgIdentifier = this.getOrgIdentifierFromState(receivedState); -- res.setHeader('Content-Type', 'text/html'); -- if (code != null && receivedState != null && this.checkState(receivedState, state)) { -- res.writeHead(200); -- res.end('Success | Bitwarden CLI' + -- '

Successfully authenticated with the Bitwarden CLI

' + -- '

You may now close this tab and return to the terminal.

' + -- ''); -- callbackServer.close(() => resolve({ -- ssoCode: code, -- orgIdentifier: orgIdentifier, -- })); -- } else { -- res.writeHead(400); -- res.end('Failed | Bitwarden CLI' + -- '

Something went wrong logging into the Bitwarden CLI

' + -- '

You may now close this tab and return to the terminal.

' + -- ''); -- callbackServer.close(() => reject()); -- } -- }); -- let foundPort = false; -- const webUrl = this.environmentService.getWebVaultUrl(); -- for (let port = 8065; port <= 8070; port++) { -- try { -- this.ssoRedirectUri = 'http://localhost:' + port; -- callbackServer.listen(port, () => { -- this.platformUtilsService.launchUri(webUrl + '/#/sso?clientId=' + this.clientId + -- '&redirectUri=' + encodeURIComponent(this.ssoRedirectUri) + -- '&state=' + state + '&codeChallenge=' + codeChallenge); -- }); -- foundPort = true; -- break; -- } catch { -- // Ignore error since we run the same command up to 5 times. -- } -- } -- if (!foundPort) { -- reject(); -- } -+ try { -+ // Create new key and hash new password -+ const newKey = await this.cryptoService.makeKey( -+ masterPassword, -+ this.email.trim().toLowerCase(), -+ kdf, -+ kdfIterations -+ ); -+ const newPasswordHash = await this.cryptoService.hashPassword(masterPassword, newKey); -+ -+ // Grab user's current enc key -+ const userEncKey = await this.cryptoService.getEncKey(); -+ -+ // Create new encKey for the User -+ const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey); -+ -+ // Create request -+ const request = new UpdateTempPasswordRequest(); -+ request.key = newEncKey[1].encryptedString; -+ request.newMasterPasswordHash = newPasswordHash; -+ request.masterPasswordHint = masterPasswordHint; -+ -+ // Update user's password -+ await this.apiService.putUpdateTempPassword(request); -+ return this.handleSuccessResponse(); -+ } catch (e) { -+ await this.logout(); -+ this.authService.logOut(() => { -+ /* Do nothing */ -+ }); -+ return Response.error(e); -+ } -+ } -+ -+ private getPasswordStrengthUserInput() { -+ let userInput: string[] = []; -+ const atPosition = this.email.indexOf("@"); -+ if (atPosition > -1) { -+ userInput = userInput.concat( -+ this.email -+ .substr(0, atPosition) -+ .trim() -+ .toLowerCase() -+ .split(/[^A-Za-z0-9]/) -+ ); -+ } -+ return userInput; -+ } -+ -+ private async apiClientId(): Promise { -+ let clientId: string = null; -+ -+ const storedClientId: string = process.env.BW_CLIENTID; -+ if (storedClientId == null) { -+ if (this.canInteract) { -+ const answer: inquirer.Answers = await inquirer.createPromptModule({ -+ output: process.stderr, -+ })({ -+ type: "input", -+ name: "clientId", -+ message: "client_id:", - }); -+ clientId = answer.clientId; -+ } else { -+ clientId = null; -+ } -+ } else { -+ clientId = storedClientId; - } - -- private getOrgIdentifierFromState(state: string): string { -- if (state === null || state === undefined) { -- return null; -- } -- -- const stateSplit = state.split('_identifier='); -- return stateSplit.length > 1 ? stateSplit[1] : null; -+ return clientId; -+ } -+ -+ private async apiClientSecret(isAdditionalAuthentication: boolean = false): Promise { -+ const additionalAuthenticationMessage = "Additional authentication required.\nAPI key "; -+ let clientSecret: string = null; -+ -+ const storedClientSecret: string = this.clientSecret || process.env.BW_CLIENTSECRET; -+ if (this.canInteract && storedClientSecret == null) { -+ const answer: inquirer.Answers = await inquirer.createPromptModule({ -+ output: process.stderr, -+ })({ -+ type: "input", -+ name: "clientSecret", -+ message: -+ (isAdditionalAuthentication ? additionalAuthenticationMessage : "") + "client_secret:", -+ }); -+ clientSecret = answer.clientSecret; -+ } else { -+ clientSecret = storedClientSecret; - } - -- private checkState(state: string, checkState: string): boolean { -- if (state === null || state === undefined) { -- return false; -+ return clientSecret; -+ } -+ -+ private async apiIdentifiers(): Promise<{ clientId: string; clientSecret: string }> { -+ return { -+ clientId: await this.apiClientId(), -+ clientSecret: await this.apiClientSecret(), -+ }; -+ } -+ -+ private async openSsoPrompt( -+ codeChallenge: string, -+ state: string -+ ): Promise<{ ssoCode: string; orgIdentifier: string }> { -+ return new Promise((resolve, reject) => { -+ const callbackServer = http.createServer((req, res) => { -+ const urlString = "http://localhost" + req.url; -+ const url = new URL(urlString); -+ const code = url.searchParams.get("code"); -+ const receivedState = url.searchParams.get("state"); -+ const orgIdentifier = this.getOrgIdentifierFromState(receivedState); -+ res.setHeader("Content-Type", "text/html"); -+ if (code != null && receivedState != null && this.checkState(receivedState, state)) { -+ res.writeHead(200); -+ res.end( -+ "Success | Bitwarden CLI" + -+ "

Successfully authenticated with the Bitwarden CLI

" + -+ "

You may now close this tab and return to the terminal.

" + -+ "" -+ ); -+ callbackServer.close(() => -+ resolve({ -+ ssoCode: code, -+ orgIdentifier: orgIdentifier, -+ }) -+ ); -+ } else { -+ res.writeHead(400); -+ res.end( -+ "Failed | Bitwarden CLI" + -+ "

Something went wrong logging into the Bitwarden CLI

" + -+ "

You may now close this tab and return to the terminal.

" + -+ "" -+ ); -+ callbackServer.close(() => reject()); - } -- if (checkState === null || checkState === undefined) { -- return false; -+ }); -+ let foundPort = false; -+ const webUrl = this.environmentService.getWebVaultUrl(); -+ for (let port = 8065; port <= 8070; port++) { -+ try { -+ this.ssoRedirectUri = "http://localhost:" + port; -+ callbackServer.listen(port, () => { -+ this.platformUtilsService.launchUri( -+ webUrl + -+ "/#/sso?clientId=" + -+ this.clientId + -+ "&redirectUri=" + -+ encodeURIComponent(this.ssoRedirectUri) + -+ "&state=" + -+ state + -+ "&codeChallenge=" + -+ codeChallenge -+ ); -+ }); -+ foundPort = true; -+ break; -+ } catch { -+ // Ignore error since we run the same command up to 5 times. - } -+ } -+ if (!foundPort) { -+ reject(); -+ } -+ }); -+ } -+ -+ private getOrgIdentifierFromState(state: string): string { -+ if (state === null || state === undefined) { -+ return null; -+ } -+ -+ const stateSplit = state.split("_identifier="); -+ return stateSplit.length > 1 ? stateSplit[1] : null; -+ } - -- const stateSplit = state.split('_identifier='); -- const checkStateSplit = checkState.split('_identifier='); -- return stateSplit[0] === checkStateSplit[0]; -+ private checkState(state: string, checkState: string): boolean { -+ if (state === null || state === undefined) { -+ return false; - } -+ if (checkState === null || checkState === undefined) { -+ return false; -+ } -+ -+ const stateSplit = state.split("_identifier="); -+ const checkStateSplit = checkState.split("_identifier="); -+ return stateSplit[0] === checkStateSplit[0]; -+ } - } -diff --git a/jslib/node/src/cli/commands/logout.command.ts b/jslib/node/src/cli/commands/logout.command.ts -index 12b1533e..752d142d 100644 ---- a/jslib/node/src/cli/commands/logout.command.ts -+++ b/jslib/node/src/cli/commands/logout.command.ts -@@ -1,19 +1,24 @@ --import * as program from 'commander'; -+import * as program from "commander"; - --import { AuthService } from 'jslib-common/abstractions/auth.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; -+import { AuthService } from "jslib-common/abstractions/auth.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; - --import { Response } from '../models/response'; --import { MessageResponse } from '../models/response/messageResponse'; -+import { Response } from "../models/response"; -+import { MessageResponse } from "../models/response/messageResponse"; - - export class LogoutCommand { -- constructor(private authService: AuthService, private i18nService: I18nService, -- private logoutCallback: () => Promise) { } -+ constructor( -+ private authService: AuthService, -+ private i18nService: I18nService, -+ private logoutCallback: () => Promise -+ ) {} - -- async run() { -- await this.logoutCallback(); -- this.authService.logOut(() => { /* Do nothing */ }); -- const res = new MessageResponse('You have logged out.', null); -- return Response.success(res); -- } -+ async run() { -+ await this.logoutCallback(); -+ this.authService.logOut(() => { -+ /* Do nothing */ -+ }); -+ const res = new MessageResponse("You have logged out.", null); -+ return Response.success(res); -+ } - } -diff --git a/jslib/node/src/cli/commands/update.command.ts b/jslib/node/src/cli/commands/update.command.ts -index 94dc046e..147a4694 100644 ---- a/jslib/node/src/cli/commands/update.command.ts -+++ b/jslib/node/src/cli/commands/update.command.ts -@@ -1,86 +1,105 @@ --import * as program from 'commander'; --import * as fetch from 'node-fetch'; -+import * as program from "commander"; -+import * as fetch from "node-fetch"; - --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; - --import { Response } from '../models/response'; --import { MessageResponse } from '../models/response/messageResponse'; -+import { Response } from "../models/response"; -+import { MessageResponse } from "../models/response/messageResponse"; - - export class UpdateCommand { -- inPkg: boolean = false; -+ inPkg: boolean = false; - -- constructor(private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, -- private repoName: string, private executableName: string, private showExtendedMessage: boolean) { -- this.inPkg = !!(process as any).pkg; -- } -+ constructor( -+ private platformUtilsService: PlatformUtilsService, -+ private i18nService: I18nService, -+ private repoName: string, -+ private executableName: string, -+ private showExtendedMessage: boolean -+ ) { -+ this.inPkg = !!(process as any).pkg; -+ } - -- async run(): Promise { -- const currentVersion = await this.platformUtilsService.getApplicationVersion(); -+ async run(): Promise { -+ const currentVersion = await this.platformUtilsService.getApplicationVersion(); - -- const response = await fetch.default('https://api.github.com/repos/bitwarden/' + -- this.repoName + '/releases/latest'); -- if (response.status === 200) { -- const responseJson = await response.json(); -- const res = new MessageResponse(null, null); -+ const response = await fetch.default( -+ "https://api.github.com/repos/bitwarden/" + this.repoName + "/releases/latest" -+ ); -+ if (response.status === 200) { -+ const responseJson = await response.json(); -+ const res = new MessageResponse(null, null); - -- const tagName: string = responseJson.tag_name; -- if (tagName === ('v' + currentVersion)) { -- res.title = 'No update available.'; -- res.noColor = true; -- return Response.success(res); -- } -+ const tagName: string = responseJson.tag_name; -+ if (tagName === "v" + currentVersion) { -+ res.title = "No update available."; -+ res.noColor = true; -+ return Response.success(res); -+ } - -- let downloadUrl: string = null; -- if (responseJson.assets != null) { -- for (const a of responseJson.assets) { -- const download: string = a.browser_download_url; -- if (download == null) { -- continue; -- } -+ let downloadUrl: string = null; -+ if (responseJson.assets != null) { -+ for (const a of responseJson.assets) { -+ const download: string = a.browser_download_url; -+ if (download == null) { -+ continue; -+ } - -- if (download.indexOf('.zip') === -1) { -- continue; -- } -+ if (download.indexOf(".zip") === -1) { -+ continue; -+ } - -- if (process.platform === 'win32' && download.indexOf(this.executableName + '-windows') > -1) { -- downloadUrl = download; -- break; -- } else if (process.platform === 'darwin' && download.indexOf(this.executableName + '-macos') > -1) { -- downloadUrl = download; -- break; -- } else if (process.platform === 'linux' && download.indexOf(this.executableName + '-linux') > -1) { -- downloadUrl = download; -- break; -- } -- } -- } -+ if ( -+ process.platform === "win32" && -+ download.indexOf(this.executableName + "-windows") > -1 -+ ) { -+ downloadUrl = download; -+ break; -+ } else if ( -+ process.platform === "darwin" && -+ download.indexOf(this.executableName + "-macos") > -1 -+ ) { -+ downloadUrl = download; -+ break; -+ } else if ( -+ process.platform === "linux" && -+ download.indexOf(this.executableName + "-linux") > -1 -+ ) { -+ downloadUrl = download; -+ break; -+ } -+ } -+ } - -- res.title = 'A new version is available: ' + tagName; -- if (downloadUrl == null) { -- downloadUrl = 'https://github.com/bitwarden/' + this.repoName + '/releases'; -- } else { -- res.raw = downloadUrl; -- } -- res.message = ''; -- if (responseJson.body != null && responseJson.body !== '') { -- res.message = responseJson.body + '\n\n'; -- } -+ res.title = "A new version is available: " + tagName; -+ if (downloadUrl == null) { -+ downloadUrl = "https://github.com/bitwarden/" + this.repoName + "/releases"; -+ } else { -+ res.raw = downloadUrl; -+ } -+ res.message = ""; -+ if (responseJson.body != null && responseJson.body !== "") { -+ res.message = responseJson.body + "\n\n"; -+ } - -- res.message += 'You can download this update at ' + downloadUrl; -+ res.message += "You can download this update at " + downloadUrl; - -- if (this.showExtendedMessage) { -- if (this.inPkg) { -- res.message += '\n\nIf you installed this CLI through a package manager ' + -- 'you should probably update using its update command instead.'; -- } else { -- res.message += '\n\nIf you installed this CLI through NPM ' + -- 'you should update using `npm install -g @bitwarden/' + this.repoName + '`'; -- } -- } -- return Response.success(res); -+ if (this.showExtendedMessage) { -+ if (this.inPkg) { -+ res.message += -+ "\n\nIf you installed this CLI through a package manager " + -+ "you should probably update using its update command instead."; - } else { -- return Response.error('Error contacting update API: ' + response.status); -+ res.message += -+ "\n\nIf you installed this CLI through NPM " + -+ "you should update using `npm install -g @bitwarden/" + -+ this.repoName + -+ "`"; - } -+ } -+ return Response.success(res); -+ } else { -+ return Response.error("Error contacting update API: " + response.status); - } -+ } - } -diff --git a/jslib/node/src/cli/models/response.ts b/jslib/node/src/cli/models/response.ts -index d3a858ac..d768c51f 100644 ---- a/jslib/node/src/cli/models/response.ts -+++ b/jslib/node/src/cli/models/response.ts -@@ -1,45 +1,50 @@ --import { BaseResponse } from './response/baseResponse'; -+import { BaseResponse } from "./response/baseResponse"; - - export class Response { -- static error(error: any, data?: any): Response { -- const res = new Response(); -- res.success = false; -- if (typeof (error) === 'string') { -- res.message = error; -- } else { -- res.message = error.message != null ? error.message : -- error.toString() === '[object Object]' ? JSON.stringify(error) : error.toString(); -- } -- res.data = data; -- return res; -+ static error(error: any, data?: any): Response { -+ const res = new Response(); -+ res.success = false; -+ if (typeof error === "string") { -+ res.message = error; -+ } else { -+ res.message = -+ error.message != null -+ ? error.message -+ : error.toString() === "[object Object]" -+ ? JSON.stringify(error) -+ : error.toString(); - } -+ res.data = data; -+ return res; -+ } - -- static notFound(): Response { -- return Response.error('Not found.'); -- } -+ static notFound(): Response { -+ return Response.error("Not found."); -+ } - -- static badRequest(message: string): Response { -- return Response.error(message); -- } -+ static badRequest(message: string): Response { -+ return Response.error(message); -+ } - -- static multipleResults(ids: string[]): Response { -- let msg = 'More than one result was found. Try getting a specific object by `id` instead. ' + -- 'The following objects were found:'; -- ids.forEach(id => { -- msg += '\n' + id; -- }); -- return Response.error(msg, ids); -- } -+ static multipleResults(ids: string[]): Response { -+ let msg = -+ "More than one result was found. Try getting a specific object by `id` instead. " + -+ "The following objects were found:"; -+ ids.forEach((id) => { -+ msg += "\n" + id; -+ }); -+ return Response.error(msg, ids); -+ } - -- static success(data?: BaseResponse): Response { -- const res = new Response(); -- res.success = true; -- res.data = data; -- return res; -- } -+ static success(data?: BaseResponse): Response { -+ const res = new Response(); -+ res.success = true; -+ res.data = data; -+ return res; -+ } - -- success: boolean; -- message: string; -- errorCode: number; -- data: BaseResponse; -+ success: boolean; -+ message: string; -+ errorCode: number; -+ data: BaseResponse; - } -diff --git a/jslib/node/src/cli/models/response/baseResponse.ts b/jslib/node/src/cli/models/response/baseResponse.ts -index 9d8beca0..b0cc57da 100644 ---- a/jslib/node/src/cli/models/response/baseResponse.ts -+++ b/jslib/node/src/cli/models/response/baseResponse.ts -@@ -1,3 +1,3 @@ - export interface BaseResponse { -- object: string; -+ object: string; - } -diff --git a/jslib/node/src/cli/models/response/fileResponse.ts b/jslib/node/src/cli/models/response/fileResponse.ts -index 2e832dfc..c16b56e0 100644 ---- a/jslib/node/src/cli/models/response/fileResponse.ts -+++ b/jslib/node/src/cli/models/response/fileResponse.ts -@@ -1,13 +1,13 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class FileResponse implements BaseResponse { -- object: string; -- data: Buffer; -- fileName: string; -+ object: string; -+ data: Buffer; -+ fileName: string; - -- constructor(data: Buffer, fileName: string) { -- this.object = 'file'; -- this.data = data; -- this.fileName = fileName; -- } -+ constructor(data: Buffer, fileName: string) { -+ this.object = "file"; -+ this.data = data; -+ this.fileName = fileName; -+ } - } -diff --git a/jslib/node/src/cli/models/response/listResponse.ts b/jslib/node/src/cli/models/response/listResponse.ts -index 7995bd4f..db415bcd 100644 ---- a/jslib/node/src/cli/models/response/listResponse.ts -+++ b/jslib/node/src/cli/models/response/listResponse.ts -@@ -1,11 +1,11 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class ListResponse implements BaseResponse { -- object: string; -- data: BaseResponse[]; -+ object: string; -+ data: BaseResponse[]; - -- constructor(data: BaseResponse[]) { -- this.object = 'list'; -- this.data = data; -- } -+ constructor(data: BaseResponse[]) { -+ this.object = "list"; -+ this.data = data; -+ } - } -diff --git a/jslib/node/src/cli/models/response/messageResponse.ts b/jslib/node/src/cli/models/response/messageResponse.ts -index 448e3db7..8612841a 100644 ---- a/jslib/node/src/cli/models/response/messageResponse.ts -+++ b/jslib/node/src/cli/models/response/messageResponse.ts -@@ -1,15 +1,15 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class MessageResponse implements BaseResponse { -- object: string; -- title: string; -- message: string; -- raw: string; -- noColor = false; -+ object: string; -+ title: string; -+ message: string; -+ raw: string; -+ noColor = false; - -- constructor(title: string, message: string) { -- this.object = 'message'; -- this.title = title; -- this.message = message; -- } -+ constructor(title: string, message: string) { -+ this.object = "message"; -+ this.title = title; -+ this.message = message; -+ } - } -diff --git a/jslib/node/src/cli/models/response/stringResponse.ts b/jslib/node/src/cli/models/response/stringResponse.ts -index b9a0f044..f4becfe8 100644 ---- a/jslib/node/src/cli/models/response/stringResponse.ts -+++ b/jslib/node/src/cli/models/response/stringResponse.ts -@@ -1,11 +1,11 @@ --import { BaseResponse } from './baseResponse'; -+import { BaseResponse } from "./baseResponse"; - - export class StringResponse implements BaseResponse { -- object: string; -- data: string; -+ object: string; -+ data: string; - -- constructor(data: string) { -- this.object = 'string'; -- this.data = data; -- } -+ constructor(data: string) { -+ this.object = "string"; -+ this.data = data; -+ } - } -diff --git a/jslib/node/src/cli/services/cliPlatformUtils.service.ts b/jslib/node/src/cli/services/cliPlatformUtils.service.ts -index 78afaee9..7975d064 100644 ---- a/jslib/node/src/cli/services/cliPlatformUtils.service.ts -+++ b/jslib/node/src/cli/services/cliPlatformUtils.service.ts -@@ -1,157 +1,166 @@ --import * as child_process from 'child_process'; -+import * as child_process from "child_process"; - --import { DeviceType } from 'jslib-common/enums/deviceType'; --import { ThemeType } from 'jslib-common/enums/themeType'; -+import { DeviceType } from "jslib-common/enums/deviceType"; -+import { ThemeType } from "jslib-common/enums/themeType"; - --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; - - // tslint:disable-next-line --const open = require('open'); -+const open = require("open"); - - export class CliPlatformUtilsService implements PlatformUtilsService { -- identityClientId: string; -- -- private deviceCache: DeviceType = null; -- -- constructor(identityClientId: string, private packageJson: any) { -- this.identityClientId = identityClientId; -- } -- -- getDevice(): DeviceType { -- if (!this.deviceCache) { -- switch (process.platform) { -- case 'win32': -- this.deviceCache = DeviceType.WindowsDesktop; -- break; -- case 'darwin': -- this.deviceCache = DeviceType.MacOsDesktop; -- break; -- case 'linux': -- default: -- this.deviceCache = DeviceType.LinuxDesktop; -- break; -- } -- } -- -- return this.deviceCache; -- } -- -- getDeviceString(): string { -- const device = DeviceType[this.getDevice()].toLowerCase(); -- return device.replace('desktop', ''); -- } -- -- isFirefox() { -- return false; -- } -- -- isChrome() { -- return false; -- } -- -- isEdge() { -- return false; -- } -- -- isOpera() { -- return false; -- } -- -- isVivaldi() { -- return false; -- } -- -- isSafari() { -- return false; -- } -- -- isIE() { -- return false; -- } -- -- isMacAppStore() { -- return false; -- } -- -- isViewOpen() { -- return Promise.resolve(false); -- } -- -- launchUri(uri: string, options?: any): void { -- if (process.platform === 'linux') { -- child_process.spawnSync('xdg-open', [uri]); -- } else { -- open(uri); -- } -- } -- -- saveFile(win: Window, blobData: any, blobOptions: any, fileName: string): void { -- throw new Error('Not implemented.'); -- } -- -- getApplicationVersion(): Promise { -- return Promise.resolve(this.packageJson.version); -- } -- -- getApplicationVersionSync(): string { -- return this.packageJson.version; -- } -- -- supportsWebAuthn(win: Window) { -- return false; -- } -- -- supportsDuo(): boolean { -- return false; -- } -- -- showToast(type: 'error' | 'success' | 'warning' | 'info', title: string, text: string | string[], -- options?: any): void { -- throw new Error('Not implemented.'); -- } -- -- showDialog(text: string, title?: string, confirmText?: string, cancelText?: string, type?: string): -- Promise { -- throw new Error('Not implemented.'); -- } -- -- isDev(): boolean { -- return process.env.BWCLI_ENV === 'development'; -- } -- -- isSelfHost(): boolean { -- return false; -- } -- -- copyToClipboard(text: string, options?: any): void { -- throw new Error('Not implemented.'); -- } -- -- readFromClipboard(options?: any): Promise { -- throw new Error('Not implemented.'); -- } -- -- supportsBiometric(): Promise { -- return Promise.resolve(false); -- } -- -- authenticateBiometric(): Promise { -- return Promise.resolve(false); -- } -- -- getDefaultSystemTheme() { -- return Promise.resolve(ThemeType.Light as ThemeType.Light | ThemeType.Dark); -- } -- -- onDefaultSystemThemeChange() { -- /* noop */ -- } -- -- getEffectiveTheme() { -- return Promise.resolve(ThemeType.Light); -- } -- -- supportsSecureStorage(): boolean { -- return false; -- } -+ identityClientId: string; -+ -+ private deviceCache: DeviceType = null; -+ -+ constructor(identityClientId: string, private packageJson: any) { -+ this.identityClientId = identityClientId; -+ } -+ -+ getDevice(): DeviceType { -+ if (!this.deviceCache) { -+ switch (process.platform) { -+ case "win32": -+ this.deviceCache = DeviceType.WindowsDesktop; -+ break; -+ case "darwin": -+ this.deviceCache = DeviceType.MacOsDesktop; -+ break; -+ case "linux": -+ default: -+ this.deviceCache = DeviceType.LinuxDesktop; -+ break; -+ } -+ } -+ -+ return this.deviceCache; -+ } -+ -+ getDeviceString(): string { -+ const device = DeviceType[this.getDevice()].toLowerCase(); -+ return device.replace("desktop", ""); -+ } -+ -+ isFirefox() { -+ return false; -+ } -+ -+ isChrome() { -+ return false; -+ } -+ -+ isEdge() { -+ return false; -+ } -+ -+ isOpera() { -+ return false; -+ } -+ -+ isVivaldi() { -+ return false; -+ } -+ -+ isSafari() { -+ return false; -+ } -+ -+ isIE() { -+ return false; -+ } -+ -+ isMacAppStore() { -+ return false; -+ } -+ -+ isViewOpen() { -+ return Promise.resolve(false); -+ } -+ -+ launchUri(uri: string, options?: any): void { -+ if (process.platform === "linux") { -+ child_process.spawnSync("xdg-open", [uri]); -+ } else { -+ open(uri); -+ } -+ } -+ -+ saveFile(win: Window, blobData: any, blobOptions: any, fileName: string): void { -+ throw new Error("Not implemented."); -+ } -+ -+ getApplicationVersion(): Promise { -+ return Promise.resolve(this.packageJson.version); -+ } -+ -+ getApplicationVersionSync(): string { -+ return this.packageJson.version; -+ } -+ -+ supportsWebAuthn(win: Window) { -+ return false; -+ } -+ -+ supportsDuo(): boolean { -+ return false; -+ } -+ -+ showToast( -+ type: "error" | "success" | "warning" | "info", -+ title: string, -+ text: string | string[], -+ options?: any -+ ): void { -+ throw new Error("Not implemented."); -+ } -+ -+ showDialog( -+ text: string, -+ title?: string, -+ confirmText?: string, -+ cancelText?: string, -+ type?: string -+ ): Promise { -+ throw new Error("Not implemented."); -+ } -+ -+ isDev(): boolean { -+ return process.env.BWCLI_ENV === "development"; -+ } -+ -+ isSelfHost(): boolean { -+ return false; -+ } -+ -+ copyToClipboard(text: string, options?: any): void { -+ throw new Error("Not implemented."); -+ } -+ -+ readFromClipboard(options?: any): Promise { -+ throw new Error("Not implemented."); -+ } -+ -+ supportsBiometric(): Promise { -+ return Promise.resolve(false); -+ } -+ -+ authenticateBiometric(): Promise { -+ return Promise.resolve(false); -+ } -+ -+ getDefaultSystemTheme() { -+ return Promise.resolve(ThemeType.Light as ThemeType.Light | ThemeType.Dark); -+ } -+ -+ onDefaultSystemThemeChange() { -+ /* noop */ -+ } -+ -+ getEffectiveTheme() { -+ return Promise.resolve(ThemeType.Light); -+ } -+ -+ supportsSecureStorage(): boolean { -+ return false; -+ } - } -diff --git a/jslib/node/src/cli/services/consoleLog.service.ts b/jslib/node/src/cli/services/consoleLog.service.ts -index f603f326..f55e2d7d 100644 ---- a/jslib/node/src/cli/services/consoleLog.service.ts -+++ b/jslib/node/src/cli/services/consoleLog.service.ts -@@ -1,23 +1,23 @@ --import { LogLevelType } from 'jslib-common/enums/logLevelType'; -+import { LogLevelType } from "jslib-common/enums/logLevelType"; - --import { ConsoleLogService as BaseConsoleLogService } from 'jslib-common/services/consoleLog.service'; -+import { ConsoleLogService as BaseConsoleLogService } from "jslib-common/services/consoleLog.service"; - - export class ConsoleLogService extends BaseConsoleLogService { -- constructor(isDev: boolean, filter: (level: LogLevelType) => boolean = null) { -- super(isDev, filter); -- } -- -- write(level: LogLevelType, message: string) { -- if (this.filter != null && this.filter(level)) { -- return; -- } -+ constructor(isDev: boolean, filter: (level: LogLevelType) => boolean = null) { -+ super(isDev, filter); -+ } - -- if (process.env.BW_RESPONSE === 'true') { -- // tslint:disable-next-line -- console.error(message); -- return; -- } -+ write(level: LogLevelType, message: string) { -+ if (this.filter != null && this.filter(level)) { -+ return; -+ } - -- super.write(level, message); -+ if (process.env.BW_RESPONSE === "true") { -+ // tslint:disable-next-line -+ console.error(message); -+ return; - } -+ -+ super.write(level, message); -+ } - } -diff --git a/jslib/node/src/services/lowdbStorage.service.ts b/jslib/node/src/services/lowdbStorage.service.ts -index 99b327a1..05396115 100644 ---- a/jslib/node/src/services/lowdbStorage.service.ts -+++ b/jslib/node/src/services/lowdbStorage.service.ts -@@ -1,119 +1,148 @@ --import * as fs from 'fs'; --import * as lowdb from 'lowdb'; --import * as FileSync from 'lowdb/adapters/FileSync'; --import * as path from 'path'; -+import * as fs from "fs"; -+import * as lowdb from "lowdb"; -+import * as FileSync from "lowdb/adapters/FileSync"; -+import * as path from "path"; - --import { LogService } from 'jslib-common/abstractions/log.service'; --import { StorageService } from 'jslib-common/abstractions/storage.service'; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { StorageService } from "jslib-common/abstractions/storage.service"; - --import { NodeUtils } from 'jslib-common/misc/nodeUtils'; --import { Utils } from 'jslib-common/misc/utils'; -+import { NodeUtils } from "jslib-common/misc/nodeUtils"; -+import { sequentialize } from "jslib-common/misc/sequentialize"; -+import { Utils } from "jslib-common/misc/utils"; - - export class LowdbStorageService implements StorageService { -- protected dataFilePath: string; -- private db: lowdb.LowdbSync; -- private defaults: any; -+ protected dataFilePath: string; -+ private db: lowdb.LowdbSync; -+ private defaults: any; -+ private ready = false; - -- constructor(protected logService: LogService, defaults?: any, private dir?: string, private allowCache = false) { -- this.defaults = defaults; -- } -+ constructor( -+ protected logService: LogService, -+ defaults?: any, -+ private dir?: string, -+ private allowCache = false -+ ) { -+ this.defaults = defaults; -+ } - -- async init() { -- this.logService.info('Initializing lowdb storage service.'); -- let adapter: lowdb.AdapterSync; -- if (Utils.isNode && this.dir != null) { -- if (!fs.existsSync(this.dir)) { -- this.logService.warning(`Could not find dir, "${this.dir}"; creating it instead.`); -- NodeUtils.mkdirpSync(this.dir, '700'); -- this.logService.info(`Created dir "${this.dir}".`); -- } -- this.dataFilePath = path.join(this.dir, 'data.json'); -- if (!fs.existsSync(this.dataFilePath)) { -- this.logService.warning(`Could not find data file, "${this.dataFilePath}"; creating it instead.`); -- fs.writeFileSync(this.dataFilePath, '', { mode: 0o600 }); -- fs.chmodSync(this.dataFilePath, 0o600); -- this.logService.info(`Created data file "${this.dataFilePath}" with chmod 600.`); -- } else { -- this.logService.info(`db file "${this.dataFilePath} already exists"; using existing db`); -- } -- await this.lockDbFile(() => { -- adapter = new FileSync(this.dataFilePath); -- }); -- } -- try { -- this.logService.info('Attempting to create lowdb storage adapter.'); -- this.db = lowdb(adapter); -- this.logService.info('Successfully created lowdb storage adapter.'); -- } catch (e) { -- if (e instanceof SyntaxError) { -- this.logService.warning(`Error creating lowdb storage adapter, "${e.message}"; emptying data file.`); -- if (fs.existsSync(this.dataFilePath)) { -- const backupPath = this.dataFilePath + '.bak'; -- this.logService.warning(`Writing backup of data file to ${backupPath}`); -- await fs.copyFile(this.dataFilePath, backupPath, err => { -- this.logService.warning(`Error while creating data file backup, "${e.message}". No backup may have been created.`); -- }); -- } -- adapter.write({}); -- this.db = lowdb(adapter); -- } else { -- this.logService.error(`Error creating lowdb storage adapter, "${e.message}".`); -- throw e; -- } -- } -+ @sequentialize(() => "lowdbStorageInit") -+ async init() { -+ if (this.ready) { -+ return; -+ } - -- if (this.defaults != null) { -- this.lockDbFile(() => { -- this.logService.info('Writing defaults.'); -- this.readForNoCache(); -- this.db.defaults(this.defaults).write(); -- this.logService.info('Successfully wrote defaults to db.'); -- }); -+ this.logService.info("Initializing lowdb storage service."); -+ let adapter: lowdb.AdapterSync; -+ if (Utils.isNode && this.dir != null) { -+ if (!fs.existsSync(this.dir)) { -+ this.logService.warning(`Could not find dir, "${this.dir}"; creating it instead.`); -+ NodeUtils.mkdirpSync(this.dir, "700"); -+ this.logService.info(`Created dir "${this.dir}".`); -+ } -+ this.dataFilePath = path.join(this.dir, "data.json"); -+ if (!fs.existsSync(this.dataFilePath)) { -+ this.logService.warning( -+ `Could not find data file, "${this.dataFilePath}"; creating it instead.` -+ ); -+ fs.writeFileSync(this.dataFilePath, "", { mode: 0o600 }); -+ fs.chmodSync(this.dataFilePath, 0o600); -+ this.logService.info(`Created data file "${this.dataFilePath}" with chmod 600.`); -+ } else { -+ this.logService.info(`db file "${this.dataFilePath} already exists"; using existing db`); -+ } -+ await this.lockDbFile(() => { -+ adapter = new FileSync(this.dataFilePath); -+ }); -+ } -+ try { -+ this.logService.info("Attempting to create lowdb storage adapter."); -+ this.db = lowdb(adapter); -+ this.logService.info("Successfully created lowdb storage adapter."); -+ } catch (e) { -+ if (e instanceof SyntaxError) { -+ this.logService.warning( -+ `Error creating lowdb storage adapter, "${e.message}"; emptying data file.` -+ ); -+ if (fs.existsSync(this.dataFilePath)) { -+ const backupPath = this.dataFilePath + ".bak"; -+ this.logService.warning(`Writing backup of data file to ${backupPath}`); -+ await fs.copyFile(this.dataFilePath, backupPath, (err) => { -+ this.logService.warning( -+ `Error while creating data file backup, "${e.message}". No backup may have been created.` -+ ); -+ }); - } -+ adapter.write({}); -+ this.db = lowdb(adapter); -+ } else { -+ this.logService.error(`Error creating lowdb storage adapter, "${e.message}".`); -+ throw e; -+ } - } - -- get(key: string): Promise { -- return this.lockDbFile(() => { -- this.readForNoCache(); -- const val = this.db.get(key).value(); -- this.logService.debug(`Successfully read ${key} from db`); -- if (val == null) { -- return null; -- } -- return val as T; -- }); -+ if (this.defaults != null) { -+ this.lockDbFile(() => { -+ this.logService.info("Writing defaults."); -+ this.readForNoCache(); -+ this.db.defaults(this.defaults).write(); -+ this.logService.info("Successfully wrote defaults to db."); -+ }); - } - -- has(key: string): Promise { -- return this.get(key).then(v => v != null); -- } -+ this.ready = true; -+ } - -- save(key: string, obj: any): Promise { -- return this.lockDbFile(() => { -- this.readForNoCache(); -- this.db.set(key, obj).write(); -- this.logService.debug(`Successfully wrote ${key} to db`); -- return; -- }); -- } -+ async get(key: string): Promise { -+ await this.waitForReady(); -+ return this.lockDbFile(() => { -+ this.readForNoCache(); -+ const val = this.db.get(key).value(); -+ this.logService.debug(`Successfully read ${key} from db`); -+ if (val == null) { -+ return null; -+ } -+ return val as T; -+ }); -+ } - -- remove(key: string): Promise { -- return this.lockDbFile(() => { -- this.readForNoCache(); -- this.db.unset(key).write(); -- this.logService.debug(`Successfully removed ${key} from db`); -- return; -- }); -- } -+ has(key: string): Promise { -+ return this.get(key).then((v) => v != null); -+ } -+ -+ async save(key: string, obj: any): Promise { -+ await this.waitForReady(); -+ return this.lockDbFile(() => { -+ this.readForNoCache(); -+ this.db.set(key, obj).write(); -+ this.logService.debug(`Successfully wrote ${key} to db`); -+ return; -+ }); -+ } - -- protected async lockDbFile(action: () => T): Promise { -- // Lock methods implemented in clients -- return Promise.resolve(action()); -+ async remove(key: string): Promise { -+ await this.waitForReady(); -+ return this.lockDbFile(() => { -+ this.readForNoCache(); -+ this.db.unset(key).write(); -+ this.logService.debug(`Successfully removed ${key} from db`); -+ return; -+ }); -+ } -+ -+ protected async lockDbFile(action: () => T): Promise { -+ // Lock methods implemented in clients -+ return Promise.resolve(action()); -+ } -+ -+ private readForNoCache() { -+ if (!this.allowCache) { -+ this.db.read(); - } -+ } - -- private readForNoCache() { -- if (!this.allowCache) { -- this.db.read(); -- } -+ private async waitForReady() { -+ if (!this.ready) { -+ await this.init(); - } -+ } - } -diff --git a/jslib/node/src/services/nodeApi.service.ts b/jslib/node/src/services/nodeApi.service.ts -index 3cfc5bb8..08d85449 100644 ---- a/jslib/node/src/services/nodeApi.service.ts -+++ b/jslib/node/src/services/nodeApi.service.ts -@@ -1,12 +1,12 @@ --import * as FormData from 'form-data'; --import { HttpsProxyAgent } from 'https-proxy-agent'; --import * as fe from 'node-fetch'; -+import * as FormData from "form-data"; -+import { HttpsProxyAgent } from "https-proxy-agent"; -+import * as fe from "node-fetch"; - --import { ApiService } from 'jslib-common/services/api.service'; -+import { ApiService } from "jslib-common/services/api.service"; - --import { EnvironmentService } from 'jslib-common/abstractions/environment.service'; --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; --import { TokenService } from 'jslib-common/abstractions/token.service'; -+import { EnvironmentService } from "jslib-common/abstractions/environment.service"; -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+import { TokenService } from "jslib-common/abstractions/token.service"; - - (global as any).fetch = fe.default; - (global as any).Request = fe.Request; -@@ -15,18 +15,23 @@ import { TokenService } from 'jslib-common/abstractions/token.service'; - (global as any).FormData = FormData; - - export class NodeApiService extends ApiService { -- constructor(tokenService: TokenService, platformUtilsService: PlatformUtilsService, -- environmentService: EnvironmentService, logoutCallback: (expired: boolean) => Promise, -- customUserAgent: string = null, apiKeyRefresh: (clientId: string, clientSecret: string) => Promise) { -- super(tokenService, platformUtilsService, environmentService, logoutCallback, customUserAgent); -- this.apiKeyRefresh = apiKeyRefresh; -- } -+ constructor( -+ tokenService: TokenService, -+ platformUtilsService: PlatformUtilsService, -+ environmentService: EnvironmentService, -+ logoutCallback: (expired: boolean) => Promise, -+ customUserAgent: string = null, -+ apiKeyRefresh: (clientId: string, clientSecret: string) => Promise -+ ) { -+ super(tokenService, platformUtilsService, environmentService, logoutCallback, customUserAgent); -+ this.apiKeyRefresh = apiKeyRefresh; -+ } - -- nativeFetch(request: Request): Promise { -- const proxy = process.env.http_proxy || process.env.https_proxy; -- if (proxy) { -- (request as any).agent = new HttpsProxyAgent(proxy); -- } -- return fetch(request); -+ nativeFetch(request: Request): Promise { -+ const proxy = process.env.http_proxy || process.env.https_proxy; -+ if (proxy) { -+ (request as any).agent = new HttpsProxyAgent(proxy); - } -+ return fetch(request); -+ } - } -diff --git a/jslib/node/src/services/nodeCryptoFunction.service.ts b/jslib/node/src/services/nodeCryptoFunction.service.ts -index 5fd6b2c5..dae6fa68 100644 ---- a/jslib/node/src/services/nodeCryptoFunction.service.ts -+++ b/jslib/node/src/services/nodeCryptoFunction.service.ts -@@ -1,263 +1,302 @@ --import * as crypto from 'crypto'; --import * as forge from 'node-forge'; -+import * as crypto from "crypto"; -+import * as forge from "node-forge"; - --import { CryptoFunctionService } from 'jslib-common/abstractions/cryptoFunction.service'; -+import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service"; - --import { DecryptParameters } from 'jslib-common/models/domain/decryptParameters'; --import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey'; -+import { DecryptParameters } from "jslib-common/models/domain/decryptParameters"; -+import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey"; - --import { Utils } from 'jslib-common/misc/utils'; -+import { Utils } from "jslib-common/misc/utils"; - - export class NodeCryptoFunctionService implements CryptoFunctionService { -- pbkdf2(password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512', -- iterations: number): Promise { -- const len = algorithm === 'sha256' ? 32 : 64; -- const nodePassword = this.toNodeValue(password); -- const nodeSalt = this.toNodeValue(salt); -- return new Promise((resolve, reject) => { -- crypto.pbkdf2(nodePassword, nodeSalt, iterations, len, algorithm, (error, key) => { -- if (error != null) { -- reject(error); -- } else { -- resolve(this.toArrayBuffer(key)); -- } -- }); -- }); -- } -- -- // ref: https://tools.ietf.org/html/rfc5869 -- async hkdf(ikm: ArrayBuffer, salt: string | ArrayBuffer, info: string | ArrayBuffer, -- outputByteSize: number, algorithm: 'sha256' | 'sha512'): Promise { -- const saltBuf = this.toArrayBuffer(salt); -- const prk = await this.hmac(ikm, saltBuf, algorithm); -- return this.hkdfExpand(prk, info, outputByteSize, algorithm); -- } -- -- // ref: https://tools.ietf.org/html/rfc5869 -- async hkdfExpand(prk: ArrayBuffer, info: string | ArrayBuffer, outputByteSize: number, -- algorithm: 'sha256' | 'sha512'): Promise { -- const hashLen = algorithm === 'sha256' ? 32 : 64; -- if (outputByteSize > 255 * hashLen) { -- throw new Error('outputByteSize is too large.'); -- } -- const prkArr = new Uint8Array(prk); -- if (prkArr.length < hashLen) { -- throw new Error('prk is too small.'); -- } -- const infoBuf = this.toArrayBuffer(info); -- const infoArr = new Uint8Array(infoBuf); -- let runningOkmLength = 0; -- let previousT = new Uint8Array(0); -- const n = Math.ceil(outputByteSize / hashLen); -- const okm = new Uint8Array(n * hashLen); -- for (let i = 0; i < n; i++) { -- const t = new Uint8Array(previousT.length + infoArr.length + 1); -- t.set(previousT); -- t.set(infoArr, previousT.length); -- t.set([i + 1], t.length - 1); -- previousT = new Uint8Array(await this.hmac(t.buffer, prk, algorithm)); -- okm.set(previousT, runningOkmLength); -- runningOkmLength += previousT.length; -- if (runningOkmLength >= outputByteSize) { -- break; -- } -- } -- return okm.slice(0, outputByteSize).buffer; -- } -- -- hash(value: string | ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512' | 'md5'): Promise { -- const nodeValue = this.toNodeValue(value); -- const hash = crypto.createHash(algorithm); -- hash.update(nodeValue); -- return Promise.resolve(this.toArrayBuffer(hash.digest())); -- } -- -- hmac(value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512'): Promise { -- const nodeValue = this.toNodeBuffer(value); -- const nodeKey = this.toNodeBuffer(key); -- const hmac = crypto.createHmac(algorithm, nodeKey); -- hmac.update(nodeValue); -- return Promise.resolve(this.toArrayBuffer(hmac.digest())); -- } -- -- async compare(a: ArrayBuffer, b: ArrayBuffer): Promise { -- const key = await this.randomBytes(32); -- const mac1 = await this.hmac(a, key, 'sha256'); -- const mac2 = await this.hmac(b, key, 'sha256'); -- if (mac1.byteLength !== mac2.byteLength) { -- return false; -- } -- -- const arr1 = new Uint8Array(mac1); -- const arr2 = new Uint8Array(mac2); -- for (let i = 0; i < arr2.length; i++) { -- if (arr1[i] !== arr2[i]) { -- return false; -- } -+ pbkdf2( -+ password: string | ArrayBuffer, -+ salt: string | ArrayBuffer, -+ algorithm: "sha256" | "sha512", -+ iterations: number -+ ): Promise { -+ const len = algorithm === "sha256" ? 32 : 64; -+ const nodePassword = this.toNodeValue(password); -+ const nodeSalt = this.toNodeValue(salt); -+ return new Promise((resolve, reject) => { -+ crypto.pbkdf2(nodePassword, nodeSalt, iterations, len, algorithm, (error, key) => { -+ if (error != null) { -+ reject(error); -+ } else { -+ resolve(this.toArrayBuffer(key)); - } -- -- return true; -- } -- -- hmacFast(value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512'): Promise { -- return this.hmac(value, key, algorithm); -- } -- -- compareFast(a: ArrayBuffer, b: ArrayBuffer): Promise { -- return this.compare(a, b); -- } -- -- aesEncrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise { -- const nodeData = this.toNodeBuffer(data); -- const nodeIv = this.toNodeBuffer(iv); -- const nodeKey = this.toNodeBuffer(key); -- const cipher = crypto.createCipheriv('aes-256-cbc', nodeKey, nodeIv); -- const encBuf = Buffer.concat([cipher.update(nodeData), cipher.final()]); -- return Promise.resolve(this.toArrayBuffer(encBuf)); -+ }); -+ }); -+ } -+ -+ // ref: https://tools.ietf.org/html/rfc5869 -+ async hkdf( -+ ikm: ArrayBuffer, -+ salt: string | ArrayBuffer, -+ info: string | ArrayBuffer, -+ outputByteSize: number, -+ algorithm: "sha256" | "sha512" -+ ): Promise { -+ const saltBuf = this.toArrayBuffer(salt); -+ const prk = await this.hmac(ikm, saltBuf, algorithm); -+ return this.hkdfExpand(prk, info, outputByteSize, algorithm); -+ } -+ -+ // ref: https://tools.ietf.org/html/rfc5869 -+ async hkdfExpand( -+ prk: ArrayBuffer, -+ info: string | ArrayBuffer, -+ outputByteSize: number, -+ algorithm: "sha256" | "sha512" -+ ): Promise { -+ const hashLen = algorithm === "sha256" ? 32 : 64; -+ if (outputByteSize > 255 * hashLen) { -+ throw new Error("outputByteSize is too large."); - } -- -- aesDecryptFastParameters(data: string, iv: string, mac: string, key: SymmetricCryptoKey): -- DecryptParameters { -- const p = new DecryptParameters(); -- p.encKey = key.encKey; -- p.data = Utils.fromB64ToArray(data).buffer; -- p.iv = Utils.fromB64ToArray(iv).buffer; -- -- const macData = new Uint8Array(p.iv.byteLength + p.data.byteLength); -- macData.set(new Uint8Array(p.iv), 0); -- macData.set(new Uint8Array(p.data), p.iv.byteLength); -- p.macData = macData.buffer; -- -- if (key.macKey != null) { -- p.macKey = key.macKey; -- } -- if (mac != null) { -- p.mac = Utils.fromB64ToArray(mac).buffer; -- } -- -- return p; -+ const prkArr = new Uint8Array(prk); -+ if (prkArr.length < hashLen) { -+ throw new Error("prk is too small."); - } -- -- async aesDecryptFast(parameters: DecryptParameters): Promise { -- const decBuf = await this.aesDecrypt(parameters.data, parameters.iv, parameters.encKey); -- return Utils.fromBufferToUtf8(decBuf); -+ const infoBuf = this.toArrayBuffer(info); -+ const infoArr = new Uint8Array(infoBuf); -+ let runningOkmLength = 0; -+ let previousT = new Uint8Array(0); -+ const n = Math.ceil(outputByteSize / hashLen); -+ const okm = new Uint8Array(n * hashLen); -+ for (let i = 0; i < n; i++) { -+ const t = new Uint8Array(previousT.length + infoArr.length + 1); -+ t.set(previousT); -+ t.set(infoArr, previousT.length); -+ t.set([i + 1], t.length - 1); -+ previousT = new Uint8Array(await this.hmac(t.buffer, prk, algorithm)); -+ okm.set(previousT, runningOkmLength); -+ runningOkmLength += previousT.length; -+ if (runningOkmLength >= outputByteSize) { -+ break; -+ } - } -- -- aesDecrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise { -- const nodeData = this.toNodeBuffer(data); -- const nodeIv = this.toNodeBuffer(iv); -- const nodeKey = this.toNodeBuffer(key); -- const decipher = crypto.createDecipheriv('aes-256-cbc', nodeKey, nodeIv); -- const decBuf = Buffer.concat([decipher.update(nodeData), decipher.final()]); -- return Promise.resolve(this.toArrayBuffer(decBuf)); -+ return okm.slice(0, outputByteSize).buffer; -+ } -+ -+ hash( -+ value: string | ArrayBuffer, -+ algorithm: "sha1" | "sha256" | "sha512" | "md5" -+ ): Promise { -+ const nodeValue = this.toNodeValue(value); -+ const hash = crypto.createHash(algorithm); -+ hash.update(nodeValue); -+ return Promise.resolve(this.toArrayBuffer(hash.digest())); -+ } -+ -+ hmac( -+ value: ArrayBuffer, -+ key: ArrayBuffer, -+ algorithm: "sha1" | "sha256" | "sha512" -+ ): Promise { -+ const nodeValue = this.toNodeBuffer(value); -+ const nodeKey = this.toNodeBuffer(key); -+ const hmac = crypto.createHmac(algorithm, nodeKey); -+ hmac.update(nodeValue); -+ return Promise.resolve(this.toArrayBuffer(hmac.digest())); -+ } -+ -+ async compare(a: ArrayBuffer, b: ArrayBuffer): Promise { -+ const key = await this.randomBytes(32); -+ const mac1 = await this.hmac(a, key, "sha256"); -+ const mac2 = await this.hmac(b, key, "sha256"); -+ if (mac1.byteLength !== mac2.byteLength) { -+ return false; - } - -- rsaEncrypt(data: ArrayBuffer, publicKey: ArrayBuffer, algorithm: 'sha1' | 'sha256'): Promise { -- if (algorithm === 'sha256') { -- throw new Error('Node crypto does not support RSA-OAEP SHA-256'); -- } -- -- const pem = this.toPemPublicKey(publicKey); -- const decipher = crypto.publicEncrypt(pem, this.toNodeBuffer(data)); -- return Promise.resolve(this.toArrayBuffer(decipher)); -+ const arr1 = new Uint8Array(mac1); -+ const arr2 = new Uint8Array(mac2); -+ for (let i = 0; i < arr2.length; i++) { -+ if (arr1[i] !== arr2[i]) { -+ return false; -+ } - } - -- rsaDecrypt(data: ArrayBuffer, privateKey: ArrayBuffer, algorithm: 'sha1' | 'sha256'): Promise { -- if (algorithm === 'sha256') { -- throw new Error('Node crypto does not support RSA-OAEP SHA-256'); -- } -- -- const pem = this.toPemPrivateKey(privateKey); -- const decipher = crypto.privateDecrypt(pem, this.toNodeBuffer(data)); -- return Promise.resolve(this.toArrayBuffer(decipher)); -+ return true; -+ } -+ -+ hmacFast( -+ value: ArrayBuffer, -+ key: ArrayBuffer, -+ algorithm: "sha1" | "sha256" | "sha512" -+ ): Promise { -+ return this.hmac(value, key, algorithm); -+ } -+ -+ compareFast(a: ArrayBuffer, b: ArrayBuffer): Promise { -+ return this.compare(a, b); -+ } -+ -+ aesEncrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise { -+ const nodeData = this.toNodeBuffer(data); -+ const nodeIv = this.toNodeBuffer(iv); -+ const nodeKey = this.toNodeBuffer(key); -+ const cipher = crypto.createCipheriv("aes-256-cbc", nodeKey, nodeIv); -+ const encBuf = Buffer.concat([cipher.update(nodeData), cipher.final()]); -+ return Promise.resolve(this.toArrayBuffer(encBuf)); -+ } -+ -+ aesDecryptFastParameters( -+ data: string, -+ iv: string, -+ mac: string, -+ key: SymmetricCryptoKey -+ ): DecryptParameters { -+ const p = new DecryptParameters(); -+ p.encKey = key.encKey; -+ p.data = Utils.fromB64ToArray(data).buffer; -+ p.iv = Utils.fromB64ToArray(iv).buffer; -+ -+ const macData = new Uint8Array(p.iv.byteLength + p.data.byteLength); -+ macData.set(new Uint8Array(p.iv), 0); -+ macData.set(new Uint8Array(p.data), p.iv.byteLength); -+ p.macData = macData.buffer; -+ -+ if (key.macKey != null) { -+ p.macKey = key.macKey; - } -- -- rsaExtractPublicKey(privateKey: ArrayBuffer): Promise { -- const privateKeyByteString = Utils.fromBufferToByteString(privateKey); -- const privateKeyAsn1 = forge.asn1.fromDer(privateKeyByteString); -- const forgePrivateKey = (forge as any).pki.privateKeyFromAsn1(privateKeyAsn1); -- const forgePublicKey = (forge.pki as any).setRsaPublicKey(forgePrivateKey.n, forgePrivateKey.e); -- const publicKeyAsn1 = (forge.pki as any).publicKeyToAsn1(forgePublicKey); -- const publicKeyByteString = forge.asn1.toDer(publicKeyAsn1).data; -- const publicKeyArray = Utils.fromByteStringToArray(publicKeyByteString); -- return Promise.resolve(publicKeyArray.buffer); -+ if (mac != null) { -+ p.mac = Utils.fromB64ToArray(mac).buffer; - } - -- async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[ArrayBuffer, ArrayBuffer]> { -- return new Promise<[ArrayBuffer, ArrayBuffer]>((resolve, reject) => { -- forge.pki.rsa.generateKeyPair({ -- bits: length, -- workers: -1, -- e: 0x10001, // 65537 -- }, (error, keyPair) => { -- if (error != null) { -- reject(error); -- return; -- } -- -- const publicKeyAsn1 = (forge.pki as any).publicKeyToAsn1(keyPair.publicKey); -- const publicKeyByteString = forge.asn1.toDer(publicKeyAsn1).getBytes(); -- const publicKey = Utils.fromByteStringToArray(publicKeyByteString); -- -- const privateKeyAsn1 = (forge.pki as any).privateKeyToAsn1(keyPair.privateKey); -- const privateKeyPkcs8 = (forge.pki as any).wrapRsaPrivateKey(privateKeyAsn1); -- const privateKeyByteString = forge.asn1.toDer(privateKeyPkcs8).getBytes(); -- const privateKey = Utils.fromByteStringToArray(privateKeyByteString); -- -- resolve([publicKey.buffer, privateKey.buffer]); -- }); -- }); -+ return p; -+ } -+ -+ async aesDecryptFast(parameters: DecryptParameters): Promise { -+ const decBuf = await this.aesDecrypt(parameters.data, parameters.iv, parameters.encKey); -+ return Utils.fromBufferToUtf8(decBuf); -+ } -+ -+ aesDecrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise { -+ const nodeData = this.toNodeBuffer(data); -+ const nodeIv = this.toNodeBuffer(iv); -+ const nodeKey = this.toNodeBuffer(key); -+ const decipher = crypto.createDecipheriv("aes-256-cbc", nodeKey, nodeIv); -+ const decBuf = Buffer.concat([decipher.update(nodeData), decipher.final()]); -+ return Promise.resolve(this.toArrayBuffer(decBuf)); -+ } -+ -+ rsaEncrypt( -+ data: ArrayBuffer, -+ publicKey: ArrayBuffer, -+ algorithm: "sha1" | "sha256" -+ ): Promise { -+ if (algorithm === "sha256") { -+ throw new Error("Node crypto does not support RSA-OAEP SHA-256"); - } - -- randomBytes(length: number): Promise { -- return new Promise((resolve, reject) => { -- crypto.randomBytes(length, (error, bytes) => { -- if (error != null) { -- reject(error); -- } else { -- resolve(this.toArrayBuffer(bytes)); -- } -- }); -- }); -+ const pem = this.toPemPublicKey(publicKey); -+ const decipher = crypto.publicEncrypt(pem, this.toNodeBuffer(data)); -+ return Promise.resolve(this.toArrayBuffer(decipher)); -+ } -+ -+ rsaDecrypt( -+ data: ArrayBuffer, -+ privateKey: ArrayBuffer, -+ algorithm: "sha1" | "sha256" -+ ): Promise { -+ if (algorithm === "sha256") { -+ throw new Error("Node crypto does not support RSA-OAEP SHA-256"); - } - -- private toNodeValue(value: string | ArrayBuffer): string | Buffer { -- let nodeValue: string | Buffer; -- if (typeof (value) === 'string') { -- nodeValue = value; -- } else { -- nodeValue = this.toNodeBuffer(value); -+ const pem = this.toPemPrivateKey(privateKey); -+ const decipher = crypto.privateDecrypt(pem, this.toNodeBuffer(data)); -+ return Promise.resolve(this.toArrayBuffer(decipher)); -+ } -+ -+ rsaExtractPublicKey(privateKey: ArrayBuffer): Promise { -+ const privateKeyByteString = Utils.fromBufferToByteString(privateKey); -+ const privateKeyAsn1 = forge.asn1.fromDer(privateKeyByteString); -+ const forgePrivateKey = (forge as any).pki.privateKeyFromAsn1(privateKeyAsn1); -+ const forgePublicKey = (forge.pki as any).setRsaPublicKey(forgePrivateKey.n, forgePrivateKey.e); -+ const publicKeyAsn1 = (forge.pki as any).publicKeyToAsn1(forgePublicKey); -+ const publicKeyByteString = forge.asn1.toDer(publicKeyAsn1).data; -+ const publicKeyArray = Utils.fromByteStringToArray(publicKeyByteString); -+ return Promise.resolve(publicKeyArray.buffer); -+ } -+ -+ async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[ArrayBuffer, ArrayBuffer]> { -+ return new Promise<[ArrayBuffer, ArrayBuffer]>((resolve, reject) => { -+ forge.pki.rsa.generateKeyPair( -+ { -+ bits: length, -+ workers: -1, -+ e: 0x10001, // 65537 -+ }, -+ (error, keyPair) => { -+ if (error != null) { -+ reject(error); -+ return; -+ } -+ -+ const publicKeyAsn1 = (forge.pki as any).publicKeyToAsn1(keyPair.publicKey); -+ const publicKeyByteString = forge.asn1.toDer(publicKeyAsn1).getBytes(); -+ const publicKey = Utils.fromByteStringToArray(publicKeyByteString); -+ -+ const privateKeyAsn1 = (forge.pki as any).privateKeyToAsn1(keyPair.privateKey); -+ const privateKeyPkcs8 = (forge.pki as any).wrapRsaPrivateKey(privateKeyAsn1); -+ const privateKeyByteString = forge.asn1.toDer(privateKeyPkcs8).getBytes(); -+ const privateKey = Utils.fromByteStringToArray(privateKeyByteString); -+ -+ resolve([publicKey.buffer, privateKey.buffer]); - } -- return nodeValue; -- } -- -- private toNodeBuffer(value: ArrayBuffer): Buffer { -- return Buffer.from(new Uint8Array(value) as any); -- } -- -- private toArrayBuffer(value: Buffer | string | ArrayBuffer): ArrayBuffer { -- let buf: ArrayBuffer; -- if (typeof (value) === 'string') { -- buf = Utils.fromUtf8ToArray(value).buffer; -+ ); -+ }); -+ } -+ -+ randomBytes(length: number): Promise { -+ return new Promise((resolve, reject) => { -+ crypto.randomBytes(length, (error, bytes) => { -+ if (error != null) { -+ reject(error); - } else { -- buf = new Uint8Array(value).buffer; -+ resolve(this.toArrayBuffer(bytes)); - } -- return buf; -+ }); -+ }); -+ } -+ -+ private toNodeValue(value: string | ArrayBuffer): string | Buffer { -+ let nodeValue: string | Buffer; -+ if (typeof value === "string") { -+ nodeValue = value; -+ } else { -+ nodeValue = this.toNodeBuffer(value); - } -- -- private toPemPrivateKey(key: ArrayBuffer): string { -- const byteString = Utils.fromBufferToByteString(key); -- const asn1 = forge.asn1.fromDer(byteString); -- const privateKey = (forge as any).pki.privateKeyFromAsn1(asn1); -- const rsaPrivateKey = (forge.pki as any).privateKeyToAsn1(privateKey); -- const privateKeyInfo = (forge.pki as any).wrapRsaPrivateKey(rsaPrivateKey); -- return (forge.pki as any).privateKeyInfoToPem(privateKeyInfo); -- } -- -- private toPemPublicKey(key: ArrayBuffer): string { -- const byteString = Utils.fromBufferToByteString(key); -- const asn1 = forge.asn1.fromDer(byteString); -- const publicKey = (forge as any).pki.publicKeyFromAsn1(asn1); -- return (forge.pki as any).publicKeyToPem(publicKey); -+ return nodeValue; -+ } -+ -+ private toNodeBuffer(value: ArrayBuffer): Buffer { -+ return Buffer.from(new Uint8Array(value) as any); -+ } -+ -+ private toArrayBuffer(value: Buffer | string | ArrayBuffer): ArrayBuffer { -+ let buf: ArrayBuffer; -+ if (typeof value === "string") { -+ buf = Utils.fromUtf8ToArray(value).buffer; -+ } else { -+ buf = new Uint8Array(value).buffer; - } -+ return buf; -+ } -+ -+ private toPemPrivateKey(key: ArrayBuffer): string { -+ const byteString = Utils.fromBufferToByteString(key); -+ const asn1 = forge.asn1.fromDer(byteString); -+ const privateKey = (forge as any).pki.privateKeyFromAsn1(asn1); -+ const rsaPrivateKey = (forge.pki as any).privateKeyToAsn1(privateKey); -+ const privateKeyInfo = (forge.pki as any).wrapRsaPrivateKey(rsaPrivateKey); -+ return (forge.pki as any).privateKeyInfoToPem(privateKeyInfo); -+ } -+ -+ private toPemPublicKey(key: ArrayBuffer): string { -+ const byteString = Utils.fromBufferToByteString(key); -+ const asn1 = forge.asn1.fromDer(byteString); -+ const publicKey = (forge as any).pki.publicKeyFromAsn1(asn1); -+ return (forge.pki as any).publicKeyToPem(publicKey); -+ } - } -diff --git a/jslib/node/tsconfig.json b/jslib/node/tsconfig.json -index 69844dd1..07c6a21d 100644 ---- a/jslib/node/tsconfig.json -+++ b/jslib/node/tsconfig.json -@@ -1,31 +1,10 @@ - { -+ "extends": "../shared/tsconfig", - "compilerOptions": { -- "pretty": true, -- "moduleResolution": "node", -- "noImplicitAny": true, -- "target": "ES6", -- "module": "commonjs", -- "lib": ["es5", "es6", "es7", "dom"], -- "sourceMap": true, -- "declaration": true, -- "allowSyntheticDefaultImports": true, -- "experimentalDecorators": true, -- "emitDecoratorMetadata": true, -- "declarationDir": "dist/types", -- "outDir": "dist", -- "types": [], - "paths": { -- "jslib-common/*": [ -- "../common/src/*" -- ] -+ "jslib-common/*": ["../common/src/*"] - } - }, -- "include": [ -- "src", -- "spec" -- ], -- "exclude": [ -- "node_modules", -- "dist" -- ] -+ "include": ["src", "spec"], -+ "exclude": ["node_modules", "dist"] - } -diff --git a/jslib/package-lock.json b/jslib/package-lock.json -index b3e72c7f..2ca4e006 100644 ---- a/jslib/package-lock.json -+++ b/jslib/package-lock.json -@@ -17,10 +17,14 @@ - "devDependencies": { - "@fluffy-spoon/substitute": "^1.202.0", - "@types/jasmine": "^3.7.6", -- "@types/node": "^14.17.1", -+ "@types/node": "^16.11.12", -+ "commander": "7.2.0", - "concurrently": "^6.1.0", -+ "form-data": "4.0.0", -+ "husky": "^7.0.4", - "jasmine": "^3.7.0", - "jasmine-core": "^3.7.1", -+ "jasmine-ts": "^0.4.0", - "jasmine-ts-console-reporter": "^3.1.1", - "jsdom": "^16.5.3", - "karma": "^6.3.2", -@@ -32,19 +36,24 @@ - "karma-jasmine-html-reporter": "^1.5.4", - "karma-safari-launcher": "^1.0.0", - "karma-webpack": "^4.0.2", -+ "lint-staged": "^12.1.2", - "nodemon": "^2.0.7", -+ "prettier": "2.5.1", - "rimraf": "^3.0.2", -+ "rxjs": "^7.4.0", - "ts-loader": "^8.1.0", -+ "ts-node": "^10.4.0", -+ "tsconfig-paths": "^3.12.0", - "tslint": "^6.1.3", - "ttypescript": "^1.5.12", - "typemoq": "^2.1.0", -- "typescript": "4.1.5", -+ "typescript": "4.3.5", - "typescript-transform-paths": "^2.2.3", - "webpack": "^4.46.0" - }, - "engines": { -- "node": "~14", -- "npm": "~7" -+ "node": "~16", -+ "npm": "~8" - } - }, - "angular": { -@@ -52,25 +61,25 @@ - "version": "0.0.0", - "license": "GPL-3.0", - "dependencies": { -- "@angular/animations": "^11.2.11", -- "@angular/cdk": "^11.2.10", -- "@angular/common": "^11.2.11", -- "@angular/compiler": "^11.2.11", -- "@angular/core": "^11.2.11", -- "@angular/forms": "^11.2.11", -- "@angular/platform-browser": "^11.2.11", -- "@angular/platform-browser-dynamic": "^11.2.11", -- "@angular/router": "^11.2.11", -+ "@angular/animations": "^12.2.13", -+ "@angular/cdk": "^12.2.13", -+ "@angular/common": "^12.2.13", -+ "@angular/compiler": "^12.2.13", -+ "@angular/core": "^12.2.13", -+ "@angular/forms": "^12.2.13", -+ "@angular/platform-browser": "^12.2.13", -+ "@angular/platform-browser-dynamic": "^12.2.13", -+ "@angular/router": "^12.2.13", - "@bitwarden/jslib-common": "file:../common", - "duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git", -- "rxjs": "6.6.7", -+ "rxjs": "^7.4.0", - "tldjs": "^2.3.1", - "zone.js": "0.11.4" - }, - "devDependencies": { - "@types/duo_web_sdk": "^2.7.1", - "rimraf": "^3.0.2", -- "typescript": "4.1.5" -+ "typescript": "4.3.5" - } - }, - "common": { -@@ -85,19 +94,19 @@ - "lunr": "^2.3.9", - "node-forge": "^0.10.0", - "papaparse": "^5.3.0", -- "rxjs": "6.6.7", -+ "rxjs": "^7.4.0", - "tldjs": "^2.3.1", - "zxcvbn": "^4.4.2" - }, - "devDependencies": { - "@types/lunr": "^2.3.3", -- "@types/node": "^14.17.1", -+ "@types/node": "^16.11.12", - "@types/node-forge": "^0.9.7", - "@types/papaparse": "^5.2.5", - "@types/tldjs": "^2.3.0", - "@types/zxcvbn": "^4.4.1", - "rimraf": "^3.0.2", -- "typescript": "4.1.5" -+ "typescript": "4.3.5" - } - }, - "electron": { -@@ -107,17 +116,17 @@ - "dependencies": { - "@bitwarden/jslib-common": "file:../common", - "@nodert-win10-rs4/windows.security.credentials.ui": "^0.4.4", -- "electron": "14.2.0", -+ "electron": "16.0.7", - "electron-log": "4.4.1", - "electron-store": "8.0.1", -- "electron-updater": "4.3.9", -+ "electron-updater": "4.6.1", - "forcefocus": "^1.1.0", - "keytar": "7.7.0" - }, - "devDependencies": { -- "@types/node": "^14.17.1", -+ "@types/node": "^16.11.12", - "rimraf": "^3.0.2", -- "typescript": "4.1.5" -+ "typescript": "4.3.5" - } - }, - "node": { -@@ -137,21 +146,24 @@ - "devDependencies": { - "@types/inquirer": "^7.3.1", - "@types/lowdb": "^1.0.10", -- "@types/node": "^14.17.1", -+ "@types/node": "^16.11.12", - "@types/node-fetch": "^2.5.10", - "rimraf": "^3.0.2", -- "typescript": "4.1.5" -+ "typescript": "4.3.5" - } - }, - "node_modules/@angular/animations": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-11.2.14.tgz", -- "integrity": "sha512-Heq/nNrCmb3jbkusu+BQszOecfFI/31Oxxj+CDQkqqYpBcswk6bOJLoEE472o+vmgxaXbgeflU9qbIiCQhpMFA==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-12.2.14.tgz", -+ "integrity": "sha512-1BR5u32auVePvXNNP96DB2008V+Ku0OGqeZQl2h4XA9xzES/Zk5WllIJZXqRmWMRBVARfXsfb0RdMty9gcaVjA==", - "dependencies": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" -+ }, -+ "engines": { -+ "node": "^12.14.1 || >=14.0.0" - }, - "peerDependencies": { -- "@angular/core": "11.2.14" -+ "@angular/core": "12.2.14" - } - }, - "node_modules/@angular/animations/node_modules/tslib": { -@@ -160,18 +172,19 @@ - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "node_modules/@angular/cdk": { -- "version": "11.2.13", -- "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-11.2.13.tgz", -- "integrity": "sha512-FkE4iCwoLbQxLDUOjV1I7M/6hmpyb7erAjEdWgch7nGRNxF1hqX5Bqf1lvLFKPNCbx5NRI5K7YVAdIUQUR8vug==", -+ "version": "12.2.13", -+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.2.13.tgz", -+ "integrity": "sha512-zSKRhECyFqhingIeyRInIyTvYErt4gWo+x5DQr0b7YLUbU8DZSwWnG4w76Ke2s4U8T7ry1jpJBHoX/e8YBpGMg==", - "dependencies": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" - }, - "optionalDependencies": { - "parse5": "^5.0.0" - }, - "peerDependencies": { -- "@angular/common": "^11.0.0 || ^12.0.0-0", -- "@angular/core": "^11.0.0 || ^12.0.0-0" -+ "@angular/common": "^12.0.0 || ^13.0.0-0", -+ "@angular/core": "^12.0.0 || ^13.0.0-0", -+ "rxjs": "^6.5.3 || ^7.0.0" - } - }, - "node_modules/@angular/cdk/node_modules/parse5": { -@@ -186,15 +199,18 @@ - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "node_modules/@angular/common": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/common/-/common-11.2.14.tgz", -- "integrity": "sha512-ZSLV/3j7eCTyLf/8g4yBFLWySjiLz3vLJAGWscYoUpnJWMnug1VRu6zoF/COxCbtORgE+Wz6K0uhfS6MziBGVw==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/common/-/common-12.2.14.tgz", -+ "integrity": "sha512-ffYUYdwZETmFJw0AcWY30WsaWBhJxj/zSmFXWjgEGEGZH56zwbbNwfMZOYZ1jz4haAVxGu+TdXsOl2yMGzN7jQ==", - "dependencies": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" -+ }, -+ "engines": { -+ "node": "^12.14.1 || >=14.0.0" - }, - "peerDependencies": { -- "@angular/core": "11.2.14", -- "rxjs": "^6.5.3" -+ "@angular/core": "12.2.14", -+ "rxjs": "^6.5.3 || ^7.0.0" - } - }, - "node_modules/@angular/common/node_modules/tslib": { -@@ -203,11 +219,14 @@ - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "node_modules/@angular/compiler": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-11.2.14.tgz", -- "integrity": "sha512-XBOK3HgA+/y6Cz7kOX4zcJYmgJ264XnfcbXUMU2cD7Ac+mbNhLPKohWrEiSWalfcjnpf5gRfufQrQP7lpAGu0A==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-12.2.14.tgz", -+ "integrity": "sha512-dwmZi+n66IUzRFlGWu9mjXq170ZEsaDvlNLZzaPgs6vZTa4Kt7PWvIF/Y7TMvnVv/uqNG6kOhfmOkf6rfz1Gjg==", - "dependencies": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" -+ }, -+ "engines": { -+ "node": "^12.14.1 || >=14.0.0" - } - }, - "node_modules/@angular/compiler/node_modules/tslib": { -@@ -216,15 +235,18 @@ - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "node_modules/@angular/core": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/core/-/core-11.2.14.tgz", -- "integrity": "sha512-vpR4XqBGitk1Faph37CSpemwIYTmJ3pdIVNoHKP6jLonpWu+0azkchf0f7oD8/2ivj2F81opcIw0tcsy/D/5Vg==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/core/-/core-12.2.14.tgz", -+ "integrity": "sha512-dlVk7yqUHL2R/eCmM8LsWuxhEBfzg0y1zHt0UqCuFwlCoiw+IG4HFy4OlZEUw9NUEZJSv0aDv3sWqxLkvK5vvg==", - "dependencies": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" -+ }, -+ "engines": { -+ "node": "^12.14.1 || >=14.0.0" - }, - "peerDependencies": { -- "rxjs": "^6.5.3", -- "zone.js": "^0.10.2 || ^0.11.3" -+ "rxjs": "^6.5.3 || ^7.0.0", -+ "zone.js": "~0.11.4" - } - }, - "node_modules/@angular/core/node_modules/tslib": { -@@ -233,17 +255,20 @@ - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "node_modules/@angular/forms": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-11.2.14.tgz", -- "integrity": "sha512-4LWqY6KEIk1AZQFnk+4PJSOCamlD4tumuVN06gO4D0dZo9Cx+GcvW6pM6N0CPubRvPs3sScCnu20WT11HNWC1w==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-12.2.14.tgz", -+ "integrity": "sha512-/9/gSJUBXVRVdRnzgJnALAQZYJATuGDMkFC9ms9DEMG4PMAhe9x4if1lJjN6noz5RAom3qNuVBNWaYAPUxlcBQ==", - "dependencies": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" -+ }, -+ "engines": { -+ "node": "^12.14.1 || >=14.0.0" - }, - "peerDependencies": { -- "@angular/common": "11.2.14", -- "@angular/core": "11.2.14", -- "@angular/platform-browser": "11.2.14", -- "rxjs": "^6.5.3" -+ "@angular/common": "12.2.14", -+ "@angular/core": "12.2.14", -+ "@angular/platform-browser": "12.2.14", -+ "rxjs": "^6.5.3 || ^7.0.0" - } - }, - "node_modules/@angular/forms/node_modules/tslib": { -@@ -252,16 +277,19 @@ - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "node_modules/@angular/platform-browser": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-11.2.14.tgz", -- "integrity": "sha512-fb7b7ss/gRoP8wLAN17W62leMgjynuyjEPU2eUoAAazsG9f2cgM+z3rK29GYncDVyYQxZUZYnjSqvL6GSXx86A==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-12.2.14.tgz", -+ "integrity": "sha512-fWcE2rnJ3ZCISa1oPfsIDV7FBZBoLFEdDuMXAiDYqDPKvF/E5U5nHrS+K4SlLAi094bMobtTOReNWl/Ienniyw==", - "dependencies": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" -+ }, -+ "engines": { -+ "node": "^12.14.1 || >=14.0.0" - }, - "peerDependencies": { -- "@angular/animations": "11.2.14", -- "@angular/common": "11.2.14", -- "@angular/core": "11.2.14" -+ "@angular/animations": "12.2.14", -+ "@angular/common": "12.2.14", -+ "@angular/core": "12.2.14" - }, - "peerDependenciesMeta": { - "@angular/animations": { -@@ -270,17 +298,20 @@ - } - }, - "node_modules/@angular/platform-browser-dynamic": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-11.2.14.tgz", -- "integrity": "sha512-TWTPdFs6iBBcp+/YMsgCRQwdHpWGq8KjeJDJ2tfatGgBD3Gqt2YaHOMST1zPW6RkrmupytTejuVqXzeaKWFxuw==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-12.2.14.tgz", -+ "integrity": "sha512-0NPF7mS91Tct8rBmOLZPmnLSuS4kbLHXo6eTgrg80OC0vlzBiQwGDVW4X3KncCoX9CpevaGJCdSMc+uPNsFOUQ==", - "dependencies": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" -+ }, -+ "engines": { -+ "node": "^12.14.1 || >=14.0.0" - }, - "peerDependencies": { -- "@angular/common": "11.2.14", -- "@angular/compiler": "11.2.14", -- "@angular/core": "11.2.14", -- "@angular/platform-browser": "11.2.14" -+ "@angular/common": "12.2.14", -+ "@angular/compiler": "12.2.14", -+ "@angular/core": "12.2.14", -+ "@angular/platform-browser": "12.2.14" - } - }, - "node_modules/@angular/platform-browser-dynamic/node_modules/tslib": { -@@ -294,17 +325,20 @@ - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "node_modules/@angular/router": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/router/-/router-11.2.14.tgz", -- "integrity": "sha512-3aYBmj+zrEL9yf/ntIQxHIYaWShZOBKP3U07X2mX+TPMpGlvHDnR7L6bWhQVZwewzMMz7YVR16ldg50IFuAlfA==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/router/-/router-12.2.14.tgz", -+ "integrity": "sha512-yP5grSnqBvc4vNhtYdcxDgDYIebUKs5f0xyFkUJM5030UnQ0CV45tBsSxHMkQbPZucIfOuxpRy8xy5+4GizuwQ==", - "dependencies": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" -+ }, -+ "engines": { -+ "node": "^12.14.1 || >=14.0.0" - }, - "peerDependencies": { -- "@angular/common": "11.2.14", -- "@angular/core": "11.2.14", -- "@angular/platform-browser": "11.2.14", -- "rxjs": "^6.5.3" -+ "@angular/common": "12.2.14", -+ "@angular/core": "12.2.14", -+ "@angular/platform-browser": "12.2.14", -+ "rxjs": "^6.5.3 || ^7.0.0" - } - }, - "node_modules/@angular/router/node_modules/tslib": { -@@ -313,12 +347,12 @@ - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" - }, - "node_modules/@babel/code-frame": { -- "version": "7.14.5", -- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", -- "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", -+ "version": "7.16.0", -+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", -+ "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", - "dev": true, - "dependencies": { -- "@babel/highlight": "^7.14.5" -+ "@babel/highlight": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" -@@ -334,12 +368,12 @@ - } - }, - "node_modules/@babel/highlight": { -- "version": "7.14.5", -- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", -- "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", -+ "version": "7.16.0", -+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", -+ "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", - "dev": true, - "dependencies": { -- "@babel/helper-validator-identifier": "^7.14.5", -+ "@babel/helper-validator-identifier": "^7.15.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, -@@ -430,17 +464,15 @@ - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", - "dev": true, -- "peer": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/@cspotcode/source-map-support": { -- "version": "0.6.1", -- "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.6.1.tgz", -- "integrity": "sha512-DX3Z+T5dt1ockmPdobJS/FAsQPW4V4SrWEhD2iYQT2Cb2tQsiMnYxrcUH9By/Z3B+v0S5LMBkQtV/XOBbpLEOg==", -+ "version": "0.7.0", -+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", -+ "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", - "dev": true, -- "peer": true, - "dependencies": { - "@cspotcode/source-map-consumer": "0.8.0" - }, -@@ -449,9 +481,9 @@ - } - }, - "node_modules/@electron/get": { -- "version": "1.13.0", -- "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.13.0.tgz", -- "integrity": "sha512-+SjZhRuRo+STTO1Fdhzqnv9D2ZhjxXP6egsJ9kiO8dtP68cDx7dFCwWi64dlMQV7sWcfW1OYCW4wviEBzmRsfQ==", -+ "version": "1.13.1", -+ "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.13.1.tgz", -+ "integrity": "sha512-U5vkXDZ9DwXtkPqlB45tfYnnYBN8PePp1z/XDCupnSpdrxT8/ThCv9WCwPLf9oqiSGZTkH6dx2jDUPuoXpjkcA==", - "dependencies": { - "debug": "^4.1.1", - "env-paths": "^2.2.0", -@@ -465,7 +497,7 @@ - "node": ">=8.6" - }, - "optionalDependencies": { -- "global-agent": "^2.0.2", -+ "global-agent": "^3.0.0", - "global-tunnel-ng": "^2.7.1" - } - }, -@@ -560,34 +592,30 @@ - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", -- "dev": true, -- "peer": true -+ "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", -- "dev": true, -- "peer": true -+ "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", -- "dev": true, -- "peer": true -+ "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", -- "dev": true, -- "peer": true -+ "dev": true - }, - "node_modules/@types/component-emitter": { -- "version": "1.2.10", -- "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", -- "integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg==", -+ "version": "1.2.11", -+ "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", -+ "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==", - "dev": true - }, - "node_modules/@types/cookie": { -@@ -618,16 +646,40 @@ - "rxjs": "^6.4.0" - } - }, -+ "node_modules/@types/inquirer/node_modules/rxjs": { -+ "version": "6.6.7", -+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", -+ "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", -+ "dev": true, -+ "dependencies": { -+ "tslib": "^1.9.0" -+ }, -+ "engines": { -+ "npm": ">=2.0.0" -+ } -+ }, -+ "node_modules/@types/inquirer/node_modules/tslib": { -+ "version": "1.14.1", -+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", -+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", -+ "dev": true -+ }, - "node_modules/@types/jasmine": { -- "version": "3.9.1", -- "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.9.1.tgz", -- "integrity": "sha512-PVpjh8S8lqKFKurWSKdFATlfBHGPzgy0PoDdzQ+rr78jTQ0aacyh9YndzZcAUPxhk4kRujItFFGQdUJ7flHumw==", -+ "version": "3.10.2", -+ "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.10.2.tgz", -+ "integrity": "sha512-qs4xjVm4V/XjM6owGm/x6TNmhGl5iKX8dkTdsgdgl9oFnqgzxLepnS7rN9Tdo7kDmnFD/VEqKrW57cGD2odbEg==", -+ "dev": true -+ }, -+ "node_modules/@types/json5": { -+ "version": "0.0.29", -+ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", -+ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", - "dev": true - }, - "node_modules/@types/lodash": { -- "version": "4.14.175", -- "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.175.tgz", -- "integrity": "sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw==", -+ "version": "4.14.178", -+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", -+ "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==", - "dev": true - }, - "node_modules/@types/lowdb": { -@@ -646,9 +698,10 @@ - "dev": true - }, - "node_modules/@types/node": { -- "version": "14.17.20", -- "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.20.tgz", -- "integrity": "sha512-gI5Sl30tmhXsqkNvopFydP7ASc4c2cLfGNQrVKN3X90ADFWFsPEsotm/8JHSUJQKTHbwowAHtcJPeyVhtKv0TQ==" -+ "version": "16.11.13", -+ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.13.tgz", -+ "integrity": "sha512-eUXZzHLHoZqj1frtUetNkUetYoJ6X55UmrVnFD4DMhVeAmwLjniZhtBmsRiemQh4uq4G3vUra/Ws/hs9vEvL3Q==", -+ "dev": true - }, - "node_modules/@types/node-fetch": { - "version": "2.5.12", -@@ -660,6 +713,20 @@ - "form-data": "^3.0.0" - } - }, -+ "node_modules/@types/node-fetch/node_modules/form-data": { -+ "version": "3.0.1", -+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", -+ "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", -+ "dev": true, -+ "dependencies": { -+ "asynckit": "^0.4.0", -+ "combined-stream": "^1.0.8", -+ "mime-types": "^2.1.12" -+ }, -+ "engines": { -+ "node": ">= 6" -+ } -+ }, - "node_modules/@types/node-forge": { - "version": "0.9.10", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-0.9.10.tgz", -@@ -670,18 +737,18 @@ - } - }, - "node_modules/@types/papaparse": { -- "version": "5.2.6", -- "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.2.6.tgz", -- "integrity": "sha512-xGKSd0UTn58N1h0+zf8mW863Rv8BvXcGibEgKFtBIXZlcDXAmX/T4RdDO2mwmrmOypUDt5vRgo2v32a78JdqUA==", -+ "version": "5.3.1", -+ "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.1.tgz", -+ "integrity": "sha512-1lbngk9wty2kCyQB42LjqSa12SEop3t9wcEC7/xYr3ujTSTmv7HWKjKYXly0GkMfQ42PRb2lFPFEibDOiMXS0g==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/semver": { -- "version": "7.3.8", -- "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.8.tgz", -- "integrity": "sha512-D/2EJvAlCEtYFEYmmlGwbGXuK886HzyCc3nZX/tkFTQdEU8jZDAgiv08P162yB17y4ZXZoq7yFAnW4GDBb9Now==" -+ "version": "7.3.9", -+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", -+ "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==" - }, - "node_modules/@types/through": { - "version": "0.0.30", -@@ -928,9 +995,9 @@ - } - }, - "node_modules/acorn": { -- "version": "8.5.0", -- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", -- "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", -+ "version": "8.6.0", -+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", -+ "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" -@@ -981,6 +1048,19 @@ - "node": ">= 6.0.0" - } - }, -+ "node_modules/aggregate-error": { -+ "version": "3.1.0", -+ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", -+ "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", -+ "dev": true, -+ "dependencies": { -+ "clean-stack": "^2.0.0", -+ "indent-string": "^4.0.0" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", -@@ -1023,9 +1103,9 @@ - } - }, - "node_modules/ajv-formats/node_modules/ajv": { -- "version": "8.6.3", -- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", -- "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", -+ "version": "8.8.2", -+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", -+ "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", -@@ -1060,10 +1140,39 @@ - "string-width": "^4.1.0" - } - }, -+ "node_modules/ansi-align/node_modules/emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "dev": true -+ }, -+ "node_modules/ansi-align/node_modules/is-fullwidth-code-point": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", -+ "dev": true, -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/ansi-align/node_modules/string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "dev": true, -+ "dependencies": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, - "node_modules/ansi-colors": { -- "version": "3.2.4", -- "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", -- "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", -+ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" -@@ -1136,8 +1245,7 @@ - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", -- "dev": true, -- "peer": true -+ "dev": true - }, - "node_modules/argparse": { - "version": "1.0.10", -@@ -1236,6 +1344,15 @@ - "node": ">=0.10.0" - } - }, -+ "node_modules/astral-regex": { -+ "version": "2.0.0", -+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", -+ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", -+ "dev": true, -+ "engines": { -+ "node": ">=8" -+ } -+ }, - "node_modules/async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", -@@ -1348,9 +1465,9 @@ - } - }, - "node_modules/base64-arraybuffer": { -- "version": "0.1.4", -- "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", -- "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=", -+ "version": "1.0.1", -+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.1.tgz", -+ "integrity": "sha512-vFIUq7FdLtjZMhATwDul5RZWv2jpXQ09Pd6jcVEOvIsqCWTRFD/ONHNfyOS8dA/Ippi5dsIgpyKWKZaAKZltbA==", - "dev": true, - "engines": { - "node": ">= 0.6.0" -@@ -1440,21 +1557,21 @@ - "dev": true - }, - "node_modules/body-parser": { -- "version": "1.19.0", -- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", -- "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", -+ "version": "1.19.1", -+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", -+ "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", - "dev": true, - "dependencies": { -- "bytes": "3.1.0", -+ "bytes": "3.1.1", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", -- "http-errors": "1.7.2", -+ "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", -- "qs": "6.7.0", -- "raw-body": "2.4.0", -- "type-is": "~1.6.17" -+ "qs": "6.9.6", -+ "raw-body": "2.4.2", -+ "type-is": "~1.6.18" - }, - "engines": { - "node": ">= 0.8" -@@ -1503,6 +1620,35 @@ - "url": "https://github.com/sponsors/sindresorhus" - } - }, -+ "node_modules/boxen/node_modules/emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "dev": true -+ }, -+ "node_modules/boxen/node_modules/is-fullwidth-code-point": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", -+ "dev": true, -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/boxen/node_modules/string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "dev": true, -+ "dependencies": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, - "node_modules/boxen/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", -@@ -1692,9 +1838,9 @@ - "dev": true - }, - "node_modules/builder-util-runtime": { -- "version": "8.7.5", -- "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.7.5.tgz", -- "integrity": "sha512-fgUFHKtMNjdvH6PDRFntdIGUPgwZ69sXsAqEulCtoiqgWes5agrMq/Ud274zjJRTbckYh2PHh8/1CpFc6dpsbQ==", -+ "version": "8.9.1", -+ "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.9.1.tgz", -+ "integrity": "sha512-c8a8J3wK6BIVLW7ls+7TRK9igspTbzWmUqxFbgK0m40Ggm6efUbxtWVCGIjc+dtchyr5qAMAUL6iEGRdS/6vwg==", - "dependencies": { - "debug": "^4.3.2", - "sax": "^1.2.4" -@@ -1719,9 +1865,9 @@ - "dev": true - }, - "node_modules/bytes": { -- "version": "3.1.0", -- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", -- "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", -+ "version": "3.1.1", -+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", -+ "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==", - "dev": true, - "engines": { - "node": ">= 0.8" -@@ -1822,9 +1968,9 @@ - } - }, - "node_modules/camelcase": { -- "version": "6.2.0", -- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", -- "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", -+ "version": "6.2.1", -+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz", -+ "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==", - "dev": true, - "engines": { - "node": ">=10" -@@ -1937,6 +2083,15 @@ - "node": ">=0.10.0" - } - }, -+ "node_modules/clean-stack": { -+ "version": "2.2.0", -+ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", -+ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", -+ "dev": true, -+ "engines": { -+ "node": ">=6" -+ } -+ }, - "node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", -@@ -1960,6 +2115,22 @@ - "node": ">=8" - } - }, -+ "node_modules/cli-truncate": { -+ "version": "3.1.0", -+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", -+ "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", -+ "dev": true, -+ "dependencies": { -+ "slice-ansi": "^5.0.0", -+ "string-width": "^5.0.0" -+ }, -+ "engines": { -+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/sindresorhus" -+ } -+ }, - "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", -@@ -1979,6 +2150,35 @@ - "wrap-ansi": "^7.0.0" - } - }, -+ "node_modules/cliui/node_modules/emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "dev": true -+ }, -+ "node_modules/cliui/node_modules/is-fullwidth-code-point": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", -+ "dev": true, -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/cliui/node_modules/string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "dev": true, -+ "dependencies": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", -@@ -2038,6 +2238,12 @@ - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, -+ "node_modules/colorette": { -+ "version": "2.0.16", -+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", -+ "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", -+ "dev": true -+ }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", -@@ -2099,9 +2305,9 @@ - } - }, - "node_modules/concurrently": { -- "version": "6.2.2", -- "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-6.2.2.tgz", -- "integrity": "sha512-7a45BjVakAl3pprLOeqaOoZfIWZRmdC68NkjyzPbKu0/pE6CRmqS3l8RG7Q2cX9LnRHkB0oPM6af8PS7NEQvYQ==", -+ "version": "6.4.0", -+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-6.4.0.tgz", -+ "integrity": "sha512-HZ3D0RTQMH3oS4gvtYj1P+NBc6PzE2McEra6yEFcQKrUQ9HvtTGU4Dbne083F034p+LRb7kWU0tPRNvSGs1UCQ==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", -@@ -2120,10 +2326,28 @@ - "node": ">=10.0.0" - } - }, -+ "node_modules/concurrently/node_modules/rxjs": { -+ "version": "6.6.7", -+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", -+ "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", -+ "dev": true, -+ "dependencies": { -+ "tslib": "^1.9.0" -+ }, -+ "engines": { -+ "npm": ">=2.0.0" -+ } -+ }, -+ "node_modules/concurrently/node_modules/tslib": { -+ "version": "1.14.1", -+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", -+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", -+ "dev": true -+ }, - "node_modules/conf": { -- "version": "10.0.3", -- "resolved": "https://registry.npmjs.org/conf/-/conf-10.0.3.tgz", -- "integrity": "sha512-4gtQ/Q36qVxBzMe6B7gWOAfni1VdhuHkIzxydHkclnwGmgN+eW4bb6jj73vigCfr7d3WlmqawvhZrpCUCTPYxQ==", -+ "version": "10.1.1", -+ "resolved": "https://registry.npmjs.org/conf/-/conf-10.1.1.tgz", -+ "integrity": "sha512-z2civwq/k8TMYtcn3SVP0Peso4otIWnHtcTuHhQ0zDZDdP4NTxqEc8owfkz4zBsdMYdn/LFcE+ZhbCeqkhtq3Q==", - "dependencies": { - "ajv": "^8.6.3", - "ajv-formats": "^2.1.1", -@@ -2144,9 +2368,9 @@ - } - }, - "node_modules/conf/node_modules/ajv": { -- "version": "8.6.3", -- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", -- "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", -+ "version": "8.8.2", -+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", -+ "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", -@@ -2340,17 +2564,6 @@ - "node": ">=0.10.0" - } - }, -- "node_modules/core-js": { -- "version": "3.18.1", -- "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.18.1.tgz", -- "integrity": "sha512-vJlUi/7YdlCZeL6fXvWNaLUPh/id12WXj3MbkMw5uOyF0PfWPBNOCNbs53YqgrvtujLNlt9JQpruyIKkUZ+PKA==", -- "hasInstallScript": true, -- "optional": true, -- "funding": { -- "type": "opencollective", -- "url": "https://opencollective.com/core-js" -- } -- }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", -@@ -2416,8 +2629,36 @@ - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", -+ "dev": true -+ }, -+ "node_modules/cross-spawn": { -+ "version": "7.0.3", -+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", -+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", -+ "dev": true, -+ "dependencies": { -+ "path-key": "^3.1.0", -+ "shebang-command": "^2.0.0", -+ "which": "^2.0.1" -+ }, -+ "engines": { -+ "node": ">= 8" -+ } -+ }, -+ "node_modules/cross-spawn/node_modules/which": { -+ "version": "2.0.2", -+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", -+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, -- "peer": true -+ "dependencies": { -+ "isexe": "^2.0.0" -+ }, -+ "bin": { -+ "node-which": "bin/node-which" -+ }, -+ "engines": { -+ "node": ">= 8" -+ } - }, - "node_modules/crypto-browserify": { - "version": "3.12.0", -@@ -2501,9 +2742,9 @@ - } - }, - "node_modules/date-fns": { -- "version": "2.24.0", -- "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.24.0.tgz", -- "integrity": "sha512-6ujwvwgPID6zbI0o7UbURi2vlLDR9uP26+tW6Lg+Ji3w7dd0i3DOcjcClLjLPranT60SSEFBwdSyYwn/ZkPIuw==", -+ "version": "2.27.0", -+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.27.0.tgz", -+ "integrity": "sha512-sj+J0Mo2p2X1e306MHq282WS4/A8Pz/95GIFcsPNMPMZVI3EUrAdSv90al1k+p74WGLCruMXk23bfEDZa71X9Q==", - "dev": true, - "engines": { - "node": ">=0.11" -@@ -2545,9 +2786,9 @@ - } - }, - "node_modules/debug": { -- "version": "4.3.2", -- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", -- "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", -+ "version": "4.3.3", -+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", -+ "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dependencies": { - "ms": "2.1.2" - }, -@@ -2794,12 +3035,12 @@ - "dev": true - }, - "node_modules/electron": { -- "version": "14.2.0", -- "resolved": "https://registry.npmjs.org/electron/-/electron-14.2.0.tgz", -- "integrity": "sha512-6CmAv1P0xcwK3FQOSA27fHI36/wctSFVgj46VODn56srXXQWeolkK1VzeAFNE613iAuuH9jJdHvE3gz+c7XkNA==", -+ "version": "16.0.7", -+ "resolved": "https://registry.npmjs.org/electron/-/electron-16.0.7.tgz", -+ "integrity": "sha512-/IMwpBf2svhA1X/7Q58RV+Nn0fvUJsHniG4TizaO7q4iKFYSQ6hBvsLz+cylcZ8hRMKmVy5G1XaMNJID2ah23w==", - "hasInstallScript": true, - "dependencies": { -- "@electron/get": "^1.0.1", -+ "@electron/get": "^1.13.0", - "@types/node": "^14.6.2", - "extract-zip": "^1.0.3" - }, -@@ -2839,15 +3080,15 @@ - } - }, - "node_modules/electron-updater": { -- "version": "4.3.9", -- "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.3.9.tgz", -- "integrity": "sha512-LCNfedSwZfS4Hza+pDyPR05LqHtGorCStaBgVpRnfKxOlZcvpYEX0AbMeH5XUtbtGRoH2V8osbbf2qKPNb7AsA==", -+ "version": "4.6.1", -+ "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.6.1.tgz", -+ "integrity": "sha512-YsU1mHqXLrXXmBMsxhxy24PrbaB8rnpZDPmFa2gOkTYk/Ch13+R0fjsRSpPYvqtskVVY0ux8fu+HnUkVkqc7og==", - "dependencies": { -- "@types/semver": "^7.3.5", -- "builder-util-runtime": "8.7.5", -+ "@types/semver": "^7.3.6", -+ "builder-util-runtime": "8.9.1", - "fs-extra": "^10.0.0", - "js-yaml": "^4.1.0", -- "lazy-val": "^1.0.4", -+ "lazy-val": "^1.0.5", - "lodash.escaperegexp": "^4.1.2", - "lodash.isequal": "^4.5.0", - "semver": "^7.3.5" -@@ -2931,6 +3172,11 @@ - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, -+ "node_modules/electron/node_modules/@types/node": { -+ "version": "14.18.0", -+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.0.tgz", -+ "integrity": "sha512-0GeIl2kmVMXEnx8tg1SlG6Gg8vkqirrW752KqolYo1PHevhhZN3bhJ67qHj+bQaINhX0Ra3TlWwRvMCd9iEfNQ==" -+ }, - "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", -@@ -2953,9 +3199,10 @@ - "dev": true - }, - "node_modules/emoji-regex": { -- "version": "8.0.0", -- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" -+ "version": "9.2.2", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", -+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", -+ "dev": true - }, - "node_modules/emojis-list": { - "version": "3.0.0", -@@ -2984,42 +3231,45 @@ - } - }, - "node_modules/engine.io": { -- "version": "4.1.1", -- "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.1.1.tgz", -- "integrity": "sha512-t2E9wLlssQjGw0nluF6aYyfX8LwYU8Jj0xct+pAhfWfv/YrBn6TSNtEYsgxHIfaMqfrLx07czcMg9bMN6di+3w==", -+ "version": "6.1.0", -+ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.0.tgz", -+ "integrity": "sha512-ErhZOVu2xweCjEfYcTdkCnEYUiZgkAcBBAhW4jbIvNG8SLU3orAqoJCiytZjYF7eTpVmmCrLDjLIEaPlUAs1uw==", - "dev": true, - "dependencies": { -+ "@types/cookie": "^0.4.1", -+ "@types/cors": "^2.8.12", -+ "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", -- "engine.io-parser": "~4.0.0", -- "ws": "~7.4.2" -+ "engine.io-parser": "~5.0.0", -+ "ws": "~8.2.3" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/engine.io-parser": { -- "version": "4.0.3", -- "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.3.tgz", -- "integrity": "sha512-xEAAY0msNnESNPc00e19y5heTPX4y/TJ36gr8t1voOaNmTojP9b3oK3BbJLFufW2XFPQaaijpFewm2g2Um3uqA==", -+ "version": "5.0.2", -+ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.2.tgz", -+ "integrity": "sha512-wuiO7qO/OEkPJSFueuATIXtrxF7/6GTbAO9QLv7nnbjwZ5tYhLm9zxvLwxstRs0dcT0KUlWTjtIOs1T86jt12g==", - "dev": true, - "dependencies": { -- "base64-arraybuffer": "0.1.4" -+ "base64-arraybuffer": "~1.0.1" - }, - "engines": { -- "node": ">=8.0.0" -+ "node": ">=10.0.0" - } - }, - "node_modules/engine.io/node_modules/ws": { -- "version": "7.4.6", -- "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", -- "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", -+ "version": "8.2.3", -+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", -+ "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true, - "engines": { -- "node": ">=8.3.0" -+ "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", -@@ -3048,6 +3298,18 @@ - "node": ">=6.9.0" - } - }, -+ "node_modules/enquirer": { -+ "version": "2.3.6", -+ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", -+ "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", -+ "dev": true, -+ "dependencies": { -+ "ansi-colors": "^4.1.1" -+ }, -+ "engines": { -+ "node": ">=8.6" -+ } -+ }, - "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", -@@ -3206,9 +3468,9 @@ - } - }, - "node_modules/estraverse": { -- "version": "5.2.0", -- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", -- "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", -+ "version": "5.3.0", -+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", -+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" -@@ -3267,6 +3529,29 @@ - "safe-buffer": "^5.1.1" - } - }, -+ "node_modules/execa": { -+ "version": "5.1.1", -+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", -+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", -+ "dev": true, -+ "dependencies": { -+ "cross-spawn": "^7.0.3", -+ "get-stream": "^6.0.0", -+ "human-signals": "^2.1.0", -+ "is-stream": "^2.0.0", -+ "merge-stream": "^2.0.0", -+ "npm-run-path": "^4.0.1", -+ "onetime": "^5.1.2", -+ "signal-exit": "^3.0.3", -+ "strip-final-newline": "^2.0.0" -+ }, -+ "engines": { -+ "node": ">=10" -+ }, -+ "funding": { -+ "url": "https://github.com/sindresorhus/execa?sponsor=1" -+ } -+ }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", -@@ -3339,6 +3624,17 @@ - "node": ">=4" - } - }, -+ "node_modules/external-editor/node_modules/tmp": { -+ "version": "0.0.33", -+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", -+ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", -+ "dependencies": { -+ "os-tmpdir": "~1.0.2" -+ }, -+ "engines": { -+ "node": ">=0.6.0" -+ } -+ }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", -@@ -3615,9 +3911,9 @@ - } - }, - "node_modules/follow-redirects": { -- "version": "1.14.4", -- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", -- "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==", -+ "version": "1.14.6", -+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.6.tgz", -+ "integrity": "sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A==", - "dev": true, - "funding": [ - { -@@ -3657,10 +3953,9 @@ - } - }, - "node_modules/form-data": { -- "version": "3.0.1", -- "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", -- "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", -- "dev": true, -+ "version": "4.0.0", -+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", -+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", -@@ -3816,14 +4111,15 @@ - } - }, - "node_modules/get-stream": { -- "version": "4.1.0", -- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", -- "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", -- "dependencies": { -- "pump": "^3.0.0" -- }, -+ "version": "6.0.1", -+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", -+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", -+ "dev": true, - "engines": { -- "node": ">=6" -+ "node": ">=10" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-value": { -@@ -3873,13 +4169,12 @@ - } - }, - "node_modules/global-agent": { -- "version": "2.2.0", -- "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.2.0.tgz", -- "integrity": "sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg==", -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", -+ "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", - "optional": true, - "dependencies": { - "boolean": "^3.0.1", -- "core-js": "^3.6.5", - "es6-error": "^4.1.1", - "matcher": "^3.0.0", - "roarr": "^2.15.3", -@@ -3989,6 +4284,17 @@ - "node": ">=8.6" - } - }, -+ "node_modules/got/node_modules/get-stream": { -+ "version": "4.1.0", -+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", -+ "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", -+ "dependencies": { -+ "pump": "^3.0.0" -+ }, -+ "engines": { -+ "node": ">=6" -+ } -+ }, - "node_modules/graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", -@@ -4178,27 +4484,21 @@ - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" - }, - "node_modules/http-errors": { -- "version": "1.7.2", -- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", -- "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", -+ "version": "1.8.1", -+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", -+ "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", -- "inherits": "2.0.3", -- "setprototypeof": "1.1.1", -+ "inherits": "2.0.4", -+ "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", -- "toidentifier": "1.0.0" -+ "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.6" - } - }, -- "node_modules/http-errors/node_modules/inherits": { -- "version": "2.0.3", -- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", -- "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", -- "dev": true -- }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", -@@ -4245,6 +4545,30 @@ - "node": ">= 6" - } - }, -+ "node_modules/human-signals": { -+ "version": "2.1.0", -+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", -+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", -+ "dev": true, -+ "engines": { -+ "node": ">=10.17.0" -+ } -+ }, -+ "node_modules/husky": { -+ "version": "7.0.4", -+ "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz", -+ "integrity": "sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==", -+ "dev": true, -+ "bin": { -+ "husky": "lib/bin.js" -+ }, -+ "engines": { -+ "node": ">=12" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/typicode" -+ } -+ }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", -@@ -4305,6 +4629,15 @@ - "node": ">=0.8.19" - } - }, -+ "node_modules/indent-string": { -+ "version": "4.0.0", -+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", -+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", -+ "dev": true, -+ "engines": { -+ "node": ">=8" -+ } -+ }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", -@@ -4358,6 +4691,48 @@ - "node": ">=8.0.0" - } - }, -+ "node_modules/inquirer/node_modules/emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" -+ }, -+ "node_modules/inquirer/node_modules/is-fullwidth-code-point": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/inquirer/node_modules/rxjs": { -+ "version": "6.6.7", -+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", -+ "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", -+ "dependencies": { -+ "tslib": "^1.9.0" -+ }, -+ "engines": { -+ "npm": ">=2.0.0" -+ } -+ }, -+ "node_modules/inquirer/node_modules/string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "dependencies": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/inquirer/node_modules/tslib": { -+ "version": "1.14.1", -+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", -+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" -+ }, - "node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", -@@ -4413,9 +4788,9 @@ - } - }, - "node_modules/is-core-module": { -- "version": "2.7.0", -- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.7.0.tgz", -- "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", -+ "version": "2.8.0", -+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", -+ "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", - "dev": true, - "dependencies": { - "has": "^1.0.3" -@@ -4505,11 +4880,15 @@ - } - }, - "node_modules/is-fullwidth-code-point": { -- "version": "3.0.0", -- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", -+ "version": "4.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", -+ "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", -+ "dev": true, - "engines": { -- "node": ">=8" -+ "node": ">=12" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-glob": { -@@ -4601,6 +4980,18 @@ - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" - }, -+ "node_modules/is-stream": { -+ "version": "2.0.1", -+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", -+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", -+ "dev": true, -+ "engines": { -+ "node": ">=8" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/sindresorhus" -+ } -+ }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", -@@ -4667,24 +5058,41 @@ - } - }, - "node_modules/jasmine": { -- "version": "3.9.0", -- "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.9.0.tgz", -- "integrity": "sha512-JgtzteG7xnqZZ51fg7N2/wiQmXon09szkALcRMTgCMX4u/m17gVJFjObnvw5FXkZOWuweHPaPRVB6DI2uN0wVA==", -+ "version": "3.10.0", -+ "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.10.0.tgz", -+ "integrity": "sha512-2Y42VsC+3CQCTzTwJezOvji4qLORmKIE0kwowWC+934Krn6ZXNQYljiwK5st9V3PVx96BSiDYXSB60VVah3IlQ==", - "dev": true, - "dependencies": { - "glob": "^7.1.6", -- "jasmine-core": "~3.9.0" -+ "jasmine-core": "~3.10.0" - }, - "bin": { - "jasmine": "bin/jasmine.js" - } - }, - "node_modules/jasmine-core": { -- "version": "3.9.0", -- "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.9.0.tgz", -- "integrity": "sha512-Tv3kVbPCGVrjsnHBZ38NsPU3sDOtNa0XmbG2baiyJqdb5/SPpDO6GVwJYtUryl6KB4q1Ssckwg612ES9Z0dreQ==", -+ "version": "3.10.1", -+ "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.10.1.tgz", -+ "integrity": "sha512-ooZWSDVAdh79Rrj4/nnfklL3NQVra0BcuhcuWoAwwi+znLDoUeH87AFfeX8s+YeYi6xlv5nveRyaA1v7CintfA==", - "dev": true - }, -+ "node_modules/jasmine-ts": { -+ "version": "0.4.0", -+ "resolved": "https://registry.npmjs.org/jasmine-ts/-/jasmine-ts-0.4.0.tgz", -+ "integrity": "sha512-bIAWJKUwxfuZfGI1ctEbny7+dsyFzsN0eLzOokxh0qIUCofai2WUEKoe3MMllkGEBXJzbEVYr2IyhJBv4j7SBA==", -+ "dev": true, -+ "dependencies": { -+ "yargs": "^17.0.1" -+ }, -+ "bin": { -+ "jasmine-ts": "lib/index.js" -+ }, -+ "peerDependencies": { -+ "jasmine": ">=3.4", -+ "ts-node": ">=3.2.0 <=11", -+ "typescript": ">=3.5.2" -+ } -+ }, - "node_modules/jasmine-ts-console-reporter": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/jasmine-ts-console-reporter/-/jasmine-ts-console-reporter-3.1.1.tgz", -@@ -4697,6 +5105,71 @@ - "source-map-resolve": "^0.5.0" - } - }, -+ "node_modules/jasmine-ts/node_modules/emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "dev": true -+ }, -+ "node_modules/jasmine-ts/node_modules/is-fullwidth-code-point": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", -+ "dev": true, -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/jasmine-ts/node_modules/string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "dev": true, -+ "dependencies": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/jasmine-ts/node_modules/y18n": { -+ "version": "5.0.8", -+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", -+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", -+ "dev": true, -+ "engines": { -+ "node": ">=10" -+ } -+ }, -+ "node_modules/jasmine-ts/node_modules/yargs": { -+ "version": "17.3.1", -+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", -+ "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", -+ "dev": true, -+ "dependencies": { -+ "cliui": "^7.0.2", -+ "escalade": "^3.1.1", -+ "get-caller-file": "^2.0.5", -+ "require-directory": "^2.1.1", -+ "string-width": "^4.2.3", -+ "y18n": "^5.0.5", -+ "yargs-parser": "^21.0.0" -+ }, -+ "engines": { -+ "node": ">=12" -+ } -+ }, -+ "node_modules/jasmine-ts/node_modules/yargs-parser": { -+ "version": "21.0.0", -+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz", -+ "integrity": "sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA==", -+ "dev": true, -+ "engines": { -+ "node": ">=12" -+ } -+ }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", -@@ -4762,6 +5235,20 @@ - } - } - }, -+ "node_modules/jsdom/node_modules/form-data": { -+ "version": "3.0.1", -+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", -+ "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", -+ "dev": true, -+ "dependencies": { -+ "asynckit": "^0.4.0", -+ "combined-stream": "^1.0.8", -+ "mime-types": "^2.1.12" -+ }, -+ "engines": { -+ "node": ">= 6" -+ } -+ }, - "node_modules/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", -@@ -4811,9 +5298,9 @@ - } - }, - "node_modules/karma": { -- "version": "6.3.4", -- "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.4.tgz", -- "integrity": "sha512-hbhRogUYIulfkBTZT7xoPrCYhRBnBoqbbL4fszWD0ReFGUxU+LYBr3dwKdAluaDQ/ynT9/7C+Lf7pPNW4gSx4Q==", -+ "version": "6.3.9", -+ "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.9.tgz", -+ "integrity": "sha512-E/MqdLM9uVIhfuyVnrhlGBu4miafBdXEAEqCmwdEMh3n17C7UWC/8Kvm3AYKr91gc7scutekZ0xv6rxRaUCtnw==", - "dev": true, - "dependencies": { - "body-parser": "^1.19.0", -@@ -4834,10 +5321,10 @@ - "qjobs": "^1.2.0", - "range-parser": "^1.2.1", - "rimraf": "^3.0.2", -- "socket.io": "^3.1.0", -+ "socket.io": "^4.2.0", - "source-map": "^0.6.1", - "tmp": "^0.2.1", -- "ua-parser-js": "^0.7.28", -+ "ua-parser-js": "^0.7.30", - "yargs": "^16.1.1" - }, - "bin": { -@@ -4881,9 +5368,9 @@ - } - }, - "node_modules/karma-firefox-launcher": { -- "version": "2.1.1", -- "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.1.tgz", -- "integrity": "sha512-VzDMgPseXak9DtfyE1O5bB2BwsMy1zzO1kUxVW1rP0yhC4tDNJ0p3JoFdzvrK4QqVzdqUMa9Rx9YzkdFp8hz3Q==", -+ "version": "2.1.2", -+ "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.2.tgz", -+ "integrity": "sha512-VV9xDQU1QIboTrjtGVD4NCfzIH7n01ZXqy/qpBhnOeGVOkG5JYPEm8kuSd7psHE6WouZaQ9Ool92g8LFweSNMA==", - "dev": true, - "dependencies": { - "is-wsl": "^2.2.0", -@@ -4978,18 +5465,6 @@ - "node": ">=0.10.0" - } - }, -- "node_modules/karma/node_modules/tmp": { -- "version": "0.2.1", -- "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", -- "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", -- "dev": true, -- "dependencies": { -- "rimraf": "^3.0.0" -- }, -- "engines": { -- "node": ">=8.17.0" -- } -- }, - "node_modules/keytar": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.7.0.tgz", -@@ -5073,6 +5548,153 @@ - "node": ">= 0.8.0" - } - }, -+ "node_modules/lilconfig": { -+ "version": "2.0.4", -+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", -+ "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", -+ "dev": true, -+ "engines": { -+ "node": ">=10" -+ } -+ }, -+ "node_modules/lint-staged": { -+ "version": "12.1.2", -+ "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.1.2.tgz", -+ "integrity": "sha512-bSMcQVqMW98HLLLR2c2tZ+vnDCnx4fd+0QJBQgN/4XkdspGRPc8DGp7UuOEBe1ApCfJ+wXXumYnJmU+wDo7j9A==", -+ "dev": true, -+ "dependencies": { -+ "cli-truncate": "^3.1.0", -+ "colorette": "^2.0.16", -+ "commander": "^8.3.0", -+ "debug": "^4.3.2", -+ "enquirer": "^2.3.6", -+ "execa": "^5.1.1", -+ "lilconfig": "2.0.4", -+ "listr2": "^3.13.3", -+ "micromatch": "^4.0.4", -+ "normalize-path": "^3.0.0", -+ "object-inspect": "^1.11.0", -+ "string-argv": "^0.3.1", -+ "supports-color": "^9.0.2", -+ "yaml": "^1.10.2" -+ }, -+ "bin": { -+ "lint-staged": "bin/lint-staged.js" -+ }, -+ "engines": { -+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" -+ }, -+ "funding": { -+ "url": "https://opencollective.com/lint-staged" -+ } -+ }, -+ "node_modules/lint-staged/node_modules/commander": { -+ "version": "8.3.0", -+ "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", -+ "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", -+ "dev": true, -+ "engines": { -+ "node": ">= 12" -+ } -+ }, -+ "node_modules/lint-staged/node_modules/supports-color": { -+ "version": "9.2.1", -+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.1.tgz", -+ "integrity": "sha512-Obv7ycoCTG51N7y175StI9BlAXrmgZrFhZOb0/PyjHBher/NmsdBgbbQ1Inhq+gIhz6+7Gb+jWF2Vqi7Mf1xnQ==", -+ "dev": true, -+ "engines": { -+ "node": ">=12" -+ }, -+ "funding": { -+ "url": "https://github.com/chalk/supports-color?sponsor=1" -+ } -+ }, -+ "node_modules/listr2": { -+ "version": "3.13.5", -+ "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.13.5.tgz", -+ "integrity": "sha512-3n8heFQDSk+NcwBn3CgxEibZGaRzx+pC64n3YjpMD1qguV4nWus3Al+Oo3KooqFKTQEJ1v7MmnbnyyNspgx3NA==", -+ "dev": true, -+ "dependencies": { -+ "cli-truncate": "^2.1.0", -+ "colorette": "^2.0.16", -+ "log-update": "^4.0.0", -+ "p-map": "^4.0.0", -+ "rfdc": "^1.3.0", -+ "rxjs": "^7.4.0", -+ "through": "^2.3.8", -+ "wrap-ansi": "^7.0.0" -+ }, -+ "engines": { -+ "node": ">=10.0.0" -+ }, -+ "peerDependencies": { -+ "enquirer": ">= 2.3.0 < 3" -+ }, -+ "peerDependenciesMeta": { -+ "enquirer": { -+ "optional": true -+ } -+ } -+ }, -+ "node_modules/listr2/node_modules/cli-truncate": { -+ "version": "2.1.0", -+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", -+ "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", -+ "dev": true, -+ "dependencies": { -+ "slice-ansi": "^3.0.0", -+ "string-width": "^4.2.0" -+ }, -+ "engines": { -+ "node": ">=8" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/sindresorhus" -+ } -+ }, -+ "node_modules/listr2/node_modules/emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "dev": true -+ }, -+ "node_modules/listr2/node_modules/is-fullwidth-code-point": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", -+ "dev": true, -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/listr2/node_modules/slice-ansi": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", -+ "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", -+ "dev": true, -+ "dependencies": { -+ "ansi-styles": "^4.0.0", -+ "astral-regex": "^2.0.0", -+ "is-fullwidth-code-point": "^3.0.0" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/listr2/node_modules/string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "dev": true, -+ "dependencies": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, - "node_modules/loader-runner": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", -@@ -5123,6 +5745,84 @@ - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" - }, -+ "node_modules/log-update": { -+ "version": "4.0.0", -+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", -+ "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", -+ "dev": true, -+ "dependencies": { -+ "ansi-escapes": "^4.3.0", -+ "cli-cursor": "^3.1.0", -+ "slice-ansi": "^4.0.0", -+ "wrap-ansi": "^6.2.0" -+ }, -+ "engines": { -+ "node": ">=10" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/sindresorhus" -+ } -+ }, -+ "node_modules/log-update/node_modules/emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "dev": true -+ }, -+ "node_modules/log-update/node_modules/is-fullwidth-code-point": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", -+ "dev": true, -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/log-update/node_modules/slice-ansi": { -+ "version": "4.0.0", -+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", -+ "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", -+ "dev": true, -+ "dependencies": { -+ "ansi-styles": "^4.0.0", -+ "astral-regex": "^2.0.0", -+ "is-fullwidth-code-point": "^3.0.0" -+ }, -+ "engines": { -+ "node": ">=10" -+ }, -+ "funding": { -+ "url": "https://github.com/chalk/slice-ansi?sponsor=1" -+ } -+ }, -+ "node_modules/log-update/node_modules/string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "dev": true, -+ "dependencies": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/log-update/node_modules/wrap-ansi": { -+ "version": "6.2.0", -+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", -+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", -+ "dev": true, -+ "dependencies": { -+ "ansi-styles": "^4.0.0", -+ "string-width": "^4.1.0", -+ "strip-ansi": "^6.0.0" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, - "node_modules/log4js": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz", -@@ -5204,8 +5904,7 @@ - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", -- "dev": true, -- "peer": true -+ "dev": true - }, - "node_modules/map-cache": { - "version": "0.2.2", -@@ -5285,6 +5984,12 @@ - "node": ">=4.3.0 <5.0.0 || >=5.10" - } - }, -+ "node_modules/merge-stream": { -+ "version": "2.0.0", -+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", -+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", -+ "dev": true -+ }, - "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", -@@ -5318,9 +6023,9 @@ - "dev": true - }, - "node_modules/mime": { -- "version": "2.5.2", -- "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", -- "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", -+ "version": "2.6.0", -+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", -+ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "bin": { - "mime": "cli.js" -@@ -5330,19 +6035,19 @@ - } - }, - "node_modules/mime-db": { -- "version": "1.49.0", -- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", -- "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", -+ "version": "1.51.0", -+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", -+ "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { -- "version": "2.1.32", -- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", -- "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", -+ "version": "2.1.34", -+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", -+ "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "dependencies": { -- "mime-db": "1.49.0" -+ "mime-db": "1.51.0" - }, - "engines": { - "node": ">= 0.6" -@@ -5639,9 +6344,9 @@ - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" - }, - "node_modules/node-fetch": { -- "version": "2.6.5", -- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", -- "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", -+ "version": "2.6.6", -+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", -+ "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, -@@ -5714,21 +6419,21 @@ - "dev": true - }, - "node_modules/nodemon": { -- "version": "2.0.13", -- "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.13.tgz", -- "integrity": "sha512-UMXMpsZsv1UXUttCn6gv8eQPhn6DR4BW+txnL3IN5IHqrCwcrT/yWHfL35UsClGXknTH79r5xbu+6J1zNHuSyA==", -+ "version": "2.0.15", -+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", -+ "integrity": "sha512-gdHMNx47Gw7b3kWxJV64NI+Q5nfl0y5DgDbiVtShiwa7Z0IZ07Ll4RLFo6AjrhzMtoEZn5PDE3/c2AbVsiCkpA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { -- "chokidar": "^3.2.2", -- "debug": "^3.2.6", -+ "chokidar": "^3.5.2", -+ "debug": "^3.2.7", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", -- "pstree.remy": "^1.1.7", -+ "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "supports-color": "^5.5.0", - "touch": "^3.1.0", -- "undefsafe": "^2.0.3", -+ "undefsafe": "^2.0.5", - "update-notifier": "^5.1.0" - }, - "bin": { -@@ -5822,6 +6527,18 @@ - "node": ">=4" - } - }, -+ "node_modules/npm-run-path": { -+ "version": "4.0.1", -+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", -+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", -+ "dev": true, -+ "dependencies": { -+ "path-key": "^3.0.0" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, - "node_modules/npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", -@@ -5881,6 +6598,15 @@ - "node": ">=0.10.0" - } - }, -+ "node_modules/object-inspect": { -+ "version": "1.11.1", -+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.1.tgz", -+ "integrity": "sha512-If7BjFlpkzzBeV1cqgT3OSWT3azyoxDGajR+iGnFBfVV2EWyDyWaZZW2ERDjUaY2QM8i5jI3Sj7mhsM4DDAqWA==", -+ "dev": true, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" -+ } -+ }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", -@@ -6020,6 +6746,21 @@ - "node": ">=6" - } - }, -+ "node_modules/p-map": { -+ "version": "4.0.0", -+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", -+ "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", -+ "dev": true, -+ "dependencies": { -+ "aggregate-error": "^3.0.0" -+ }, -+ "engines": { -+ "node": ">=10" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/sindresorhus" -+ } -+ }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", -@@ -6141,6 +6882,15 @@ - "node": ">=0.10.0" - } - }, -+ "node_modules/path-key": { -+ "version": "3.1.1", -+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", -+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", -+ "dev": true, -+ "engines": { -+ "node": ">=8" -+ } -+ }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", -@@ -6275,6 +7025,18 @@ - "node": ">=4" - } - }, -+ "node_modules/prettier": { -+ "version": "2.5.1", -+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", -+ "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", -+ "dev": true, -+ "bin": { -+ "prettier": "bin-prettier.js" -+ }, -+ "engines": { -+ "node": ">=10.13.0" -+ } -+ }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", -@@ -6406,12 +7168,15 @@ - } - }, - "node_modules/qs": { -- "version": "6.7.0", -- "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", -- "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", -+ "version": "6.9.6", -+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", -+ "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==", - "dev": true, - "engines": { - "node": ">=0.6" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/querystring": { -@@ -6467,13 +7232,13 @@ - } - }, - "node_modules/raw-body": { -- "version": "2.4.0", -- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", -- "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", -+ "version": "2.4.2", -+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", -+ "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", - "dev": true, - "dependencies": { -- "bytes": "3.1.0", -- "http-errors": "1.7.2", -+ "bytes": "3.1.1", -+ "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, -@@ -6756,14 +7521,11 @@ - } - }, - "node_modules/rxjs": { -- "version": "6.6.7", -- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", -- "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", -+ "version": "7.4.0", -+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", -+ "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", - "dependencies": { -- "tslib": "^1.9.0" -- }, -- "engines": { -- "npm": ">=2.0.0" -+ "tslib": "~2.1.0" - } - }, - "node_modules/safe-buffer": { -@@ -6914,9 +7676,9 @@ - "dev": true - }, - "node_modules/setprototypeof": { -- "version": "1.1.1", -- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", -- "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", -+ "version": "1.2.0", -+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", -+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "node_modules/sha.js": { -@@ -6944,10 +7706,31 @@ - "node": ">=8" - } - }, -+ "node_modules/shebang-command": { -+ "version": "2.0.0", -+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", -+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", -+ "dev": true, -+ "dependencies": { -+ "shebang-regex": "^3.0.0" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/shebang-regex": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", -+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", -+ "dev": true, -+ "engines": { -+ "node": ">=8" -+ } -+ }, - "node_modules/signal-exit": { -- "version": "3.0.5", -- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", -- "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==" -+ "version": "3.0.6", -+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", -+ "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" - }, - "node_modules/simple-concat": { - "version": "1.0.1", -@@ -7000,6 +7783,34 @@ - "url": "https://github.com/sponsors/sindresorhus" - } - }, -+ "node_modules/slice-ansi": { -+ "version": "5.0.0", -+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", -+ "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", -+ "dev": true, -+ "dependencies": { -+ "ansi-styles": "^6.0.0", -+ "is-fullwidth-code-point": "^4.0.0" -+ }, -+ "engines": { -+ "node": ">=12" -+ }, -+ "funding": { -+ "url": "https://github.com/chalk/slice-ansi?sponsor=1" -+ } -+ }, -+ "node_modules/slice-ansi/node_modules/ansi-styles": { -+ "version": "6.1.0", -+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz", -+ "integrity": "sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==", -+ "dev": true, -+ "engines": { -+ "node": ">=12" -+ }, -+ "funding": { -+ "url": "https://github.com/chalk/ansi-styles?sponsor=1" -+ } -+ }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", -@@ -7123,29 +7934,26 @@ - "dev": true - }, - "node_modules/socket.io": { -- "version": "3.1.2", -- "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-3.1.2.tgz", -- "integrity": "sha512-JubKZnTQ4Z8G4IZWtaAZSiRP3I/inpy8c/Bsx2jrwGrTbKeVU5xd6qkKMHpChYeM3dWZSO0QACiGK+obhBNwYw==", -+ "version": "4.4.0", -+ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.0.tgz", -+ "integrity": "sha512-bnpJxswR9ov0Bw6ilhCvO38/1WPtE3eA2dtxi2Iq4/sFebiDJQzgKNYA7AuVVdGW09nrESXd90NbZqtDd9dzRQ==", - "dev": true, - "dependencies": { -- "@types/cookie": "^0.4.0", -- "@types/cors": "^2.8.8", -- "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "~2.0.0", -- "debug": "~4.3.1", -- "engine.io": "~4.1.0", -- "socket.io-adapter": "~2.1.0", -- "socket.io-parser": "~4.0.3" -+ "debug": "~4.3.2", -+ "engine.io": "~6.1.0", -+ "socket.io-adapter": "~2.3.3", -+ "socket.io-parser": "~4.0.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-adapter": { -- "version": "2.1.0", -- "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.1.0.tgz", -- "integrity": "sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg==", -+ "version": "2.3.3", -+ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz", -+ "integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==", - "dev": true - }, - "node_modules/socket.io-parser": { -@@ -7191,9 +7999,9 @@ - } - }, - "node_modules/source-map-support": { -- "version": "0.5.20", -- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", -- "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", -+ "version": "0.5.21", -+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", -+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", -@@ -7379,17 +8187,57 @@ - "safe-buffer": "~5.1.0" - } - }, -+ "node_modules/string-argv": { -+ "version": "0.3.1", -+ "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", -+ "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", -+ "dev": true, -+ "engines": { -+ "node": ">=0.6.19" -+ } -+ }, - "node_modules/string-width": { -- "version": "4.2.3", -- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "version": "5.0.1", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.0.1.tgz", -+ "integrity": "sha512-5ohWO/M4//8lErlUUtrFy3b11GtNOuMOU0ysKCDXFcfXuuvUXu95akgj/i8ofmaGdN0hCqyl6uu9i8dS/mQp5g==", -+ "dev": true, - "dependencies": { -- "emoji-regex": "^8.0.0", -- "is-fullwidth-code-point": "^3.0.0", -- "strip-ansi": "^6.0.1" -+ "emoji-regex": "^9.2.2", -+ "is-fullwidth-code-point": "^4.0.0", -+ "strip-ansi": "^7.0.1" - }, - "engines": { -- "node": ">=8" -+ "node": ">=12" -+ }, -+ "funding": { -+ "url": "https://github.com/sponsors/sindresorhus" -+ } -+ }, -+ "node_modules/string-width/node_modules/ansi-regex": { -+ "version": "6.0.1", -+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", -+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", -+ "dev": true, -+ "engines": { -+ "node": ">=12" -+ }, -+ "funding": { -+ "url": "https://github.com/chalk/ansi-regex?sponsor=1" -+ } -+ }, -+ "node_modules/string-width/node_modules/strip-ansi": { -+ "version": "7.0.1", -+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", -+ "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", -+ "dev": true, -+ "dependencies": { -+ "ansi-regex": "^6.0.1" -+ }, -+ "engines": { -+ "node": ">=12" -+ }, -+ "funding": { -+ "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi": { -@@ -7400,7 +8248,25 @@ - "ansi-regex": "^5.0.1" - }, - "engines": { -- "node": ">=8" -+ "node": ">=8" -+ } -+ }, -+ "node_modules/strip-bom": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", -+ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", -+ "dev": true, -+ "engines": { -+ "node": ">=4" -+ } -+ }, -+ "node_modules/strip-final-newline": { -+ "version": "2.0.0", -+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", -+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", -+ "dev": true, -+ "engines": { -+ "node": ">=6" - } - }, - "node_modules/strip-json-comments": { -@@ -7642,14 +8508,15 @@ - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, - "node_modules/tmp": { -- "version": "0.0.33", -- "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", -- "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", -+ "version": "0.2.1", -+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", -+ "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", -+ "dev": true, - "dependencies": { -- "os-tmpdir": "~1.0.2" -+ "rimraf": "^3.0.0" - }, - "engines": { -- "node": ">=0.6.0" -+ "node": ">=8.17.0" - } - }, - "node_modules/to-arraybuffer": { -@@ -7794,9 +8661,9 @@ - } - }, - "node_modules/toidentifier": { -- "version": "1.0.0", -- "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", -- "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", -+ "version": "1.0.1", -+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", -+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "engines": { - "node": ">=0.6" -@@ -7885,9 +8752,9 @@ - } - }, - "node_modules/ts-loader/node_modules/loader-utils": { -- "version": "2.0.0", -- "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", -- "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", -+ "version": "2.0.2", -+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", -+ "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", -@@ -7932,13 +8799,12 @@ - "dev": true - }, - "node_modules/ts-node": { -- "version": "10.2.1", -- "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.2.1.tgz", -- "integrity": "sha512-hCnyOyuGmD5wHleOQX6NIjJtYVIO8bPP8F2acWkB4W06wdlkgyvJtubO/I9NkI88hCFECbsEgoLc0VNkYmcSfw==", -+ "version": "10.4.0", -+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", -+ "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", - "dev": true, -- "peer": true, - "dependencies": { -- "@cspotcode/source-map-support": "0.6.1", -+ "@cspotcode/source-map-support": "0.7.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", -@@ -7958,9 +8824,6 @@ - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, -- "engines": { -- "node": ">=12.0.0" -- }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", -@@ -7981,15 +8844,26 @@ - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, -- "peer": true, - "engines": { - "node": ">=0.4.0" - } - }, -+ "node_modules/tsconfig-paths": { -+ "version": "3.12.0", -+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", -+ "integrity": "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==", -+ "dev": true, -+ "dependencies": { -+ "@types/json5": "^0.0.29", -+ "json5": "^1.0.1", -+ "minimist": "^1.2.0", -+ "strip-bom": "^3.0.0" -+ } -+ }, - "node_modules/tslib": { -- "version": "1.14.1", -- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", -- "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" -+ "version": "2.1.0", -+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", -+ "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" - }, - "node_modules/tslint": { - "version": "6.1.3", -@@ -8090,6 +8964,12 @@ - "node": ">=4" - } - }, -+ "node_modules/tslint/node_modules/tslib": { -+ "version": "1.14.1", -+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", -+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", -+ "dev": true -+ }, - "node_modules/tslint/node_modules/tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", -@@ -8109,9 +8989,9 @@ - "dev": true - }, - "node_modules/ttypescript": { -- "version": "1.5.12", -- "resolved": "https://registry.npmjs.org/ttypescript/-/ttypescript-1.5.12.tgz", -- "integrity": "sha512-1ojRyJvpnmgN9kIHmUnQPlEV1gq+VVsxVYjk/NfvMlHSmYxjK5hEvOOU2MQASrbekTUiUM7pR/nXeCc8bzvMOQ==", -+ "version": "1.5.13", -+ "resolved": "https://registry.npmjs.org/ttypescript/-/ttypescript-1.5.13.tgz", -+ "integrity": "sha512-KT/RBfGGlVJFqEI8cVvI3nMsmYcFvPSZh8bU0qX+pAwbi7/ABmYkzn7l/K8skw0xmYjVCoyaV6WLsBQxdadybQ==", - "dev": true, - "dependencies": { - "resolve": ">=1.9.0" -@@ -8211,9 +9091,9 @@ - } - }, - "node_modules/typescript": { -- "version": "4.1.5", -- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz", -- "integrity": "sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==", -+ "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", -@@ -8236,9 +9116,9 @@ - } - }, - "node_modules/ua-parser-js": { -- "version": "0.7.28", -- "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz", -- "integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==", -+ "version": "0.7.31", -+ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", -+ "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", - "dev": true, - "funding": [ - { -@@ -8255,27 +9135,9 @@ - } - }, - "node_modules/undefsafe": { -- "version": "2.0.3", -- "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", -- "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", -- "dev": true, -- "dependencies": { -- "debug": "^2.2.0" -- } -- }, -- "node_modules/undefsafe/node_modules/debug": { -- "version": "2.6.9", -- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", -- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", -- "dev": true, -- "dependencies": { -- "ms": "2.0.0" -- } -- }, -- "node_modules/undefsafe/node_modules/ms": { -- "version": "2.0.0", -- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", -- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", -+ "version": "2.0.5", -+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", -+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true - }, - "node_modules/union-value": { -@@ -9042,6 +9904,15 @@ - "node": ">= 6" - } - }, -+ "node_modules/webpack-log/node_modules/ansi-colors": { -+ "version": "3.2.4", -+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", -+ "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", -+ "dev": true, -+ "engines": { -+ "node": ">=6" -+ } -+ }, - "node_modules/webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", -@@ -9303,50 +10174,37 @@ - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" - }, - "node_modules/wide-align": { -- "version": "1.1.3", -- "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", -- "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", -+ "version": "1.1.5", -+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", -+ "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dependencies": { -- "string-width": "^1.0.2 || 2" -+ "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, -- "node_modules/wide-align/node_modules/ansi-regex": { -- "version": "3.0.0", -- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", -- "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", -- "engines": { -- "node": ">=4" -- } -+ "node_modules/wide-align/node_modules/emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/wide-align/node_modules/is-fullwidth-code-point": { -- "version": "2.0.0", -- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", -- "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { -- "node": ">=4" -+ "node": ">=8" - } - }, - "node_modules/wide-align/node_modules/string-width": { -- "version": "2.1.1", -- "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", -- "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", -- "dependencies": { -- "is-fullwidth-code-point": "^2.0.0", -- "strip-ansi": "^4.0.0" -- }, -- "engines": { -- "node": ">=4" -- } -- }, -- "node_modules/wide-align/node_modules/strip-ansi": { -- "version": "4.0.0", -- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", -- "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { -- "ansi-regex": "^3.0.0" -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" - }, - "engines": { -- "node": ">=4" -+ "node": ">=8" - } - }, - "node_modules/widest-line": { -@@ -9361,6 +10219,35 @@ - "node": ">=8" - } - }, -+ "node_modules/widest-line/node_modules/emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "dev": true -+ }, -+ "node_modules/widest-line/node_modules/is-fullwidth-code-point": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", -+ "dev": true, -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/widest-line/node_modules/string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "dev": true, -+ "dependencies": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", -@@ -9396,6 +10283,35 @@ - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, -+ "node_modules/wrap-ansi/node_modules/emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "dev": true -+ }, -+ "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", -+ "dev": true, -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/wrap-ansi/node_modules/string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "dev": true, -+ "dependencies": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", -@@ -9414,9 +10330,9 @@ - } - }, - "node_modules/ws": { -- "version": "7.5.5", -- "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", -- "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", -+ "version": "7.5.6", -+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", -+ "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", - "dev": true, - "engines": { - "node": ">=8.3.0" -@@ -9476,6 +10392,15 @@ - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, -+ "node_modules/yaml": { -+ "version": "1.10.2", -+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", -+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", -+ "dev": true, -+ "engines": { -+ "node": ">= 6" -+ } -+ }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", -@@ -9503,6 +10428,35 @@ - "node": ">=10" - } - }, -+ "node_modules/yargs/node_modules/emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "dev": true -+ }, -+ "node_modules/yargs/node_modules/is-fullwidth-code-point": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", -+ "dev": true, -+ "engines": { -+ "node": ">=8" -+ } -+ }, -+ "node_modules/yargs/node_modules/string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "dev": true, -+ "dependencies": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ }, -+ "engines": { -+ "node": ">=8" -+ } -+ }, - "node_modules/yargs/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", -@@ -9526,7 +10480,6 @@ - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, -- "peer": true, - "engines": { - "node": ">=6" - } -@@ -9539,37 +10492,19 @@ - "tslib": "^2.0.0" - } - }, -- "node_modules/zone.js/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/zxcvbn": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/zxcvbn/-/zxcvbn-4.4.2.tgz", - "integrity": "sha1-KOwXzwl0PtyrBW3dixsGJizHPDA=" -- }, -- "node/node_modules/form-data": { -- "version": "4.0.0", -- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", -- "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", -- "dependencies": { -- "asynckit": "^0.4.0", -- "combined-stream": "^1.0.8", -- "mime-types": "^2.1.12" -- }, -- "engines": { -- "node": ">= 6" -- } - } - }, - "dependencies": { - "@angular/animations": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-11.2.14.tgz", -- "integrity": "sha512-Heq/nNrCmb3jbkusu+BQszOecfFI/31Oxxj+CDQkqqYpBcswk6bOJLoEE472o+vmgxaXbgeflU9qbIiCQhpMFA==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-12.2.14.tgz", -+ "integrity": "sha512-1BR5u32auVePvXNNP96DB2008V+Ku0OGqeZQl2h4XA9xzES/Zk5WllIJZXqRmWMRBVARfXsfb0RdMty9gcaVjA==", - "requires": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" - }, - "dependencies": { - "tslib": { -@@ -9580,12 +10515,12 @@ - } - }, - "@angular/cdk": { -- "version": "11.2.13", -- "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-11.2.13.tgz", -- "integrity": "sha512-FkE4iCwoLbQxLDUOjV1I7M/6hmpyb7erAjEdWgch7nGRNxF1hqX5Bqf1lvLFKPNCbx5NRI5K7YVAdIUQUR8vug==", -+ "version": "12.2.13", -+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.2.13.tgz", -+ "integrity": "sha512-zSKRhECyFqhingIeyRInIyTvYErt4gWo+x5DQr0b7YLUbU8DZSwWnG4w76Ke2s4U8T7ry1jpJBHoX/e8YBpGMg==", - "requires": { - "parse5": "^5.0.0", -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" - }, - "dependencies": { - "parse5": { -@@ -9602,11 +10537,11 @@ - } - }, - "@angular/common": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/common/-/common-11.2.14.tgz", -- "integrity": "sha512-ZSLV/3j7eCTyLf/8g4yBFLWySjiLz3vLJAGWscYoUpnJWMnug1VRu6zoF/COxCbtORgE+Wz6K0uhfS6MziBGVw==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/common/-/common-12.2.14.tgz", -+ "integrity": "sha512-ffYUYdwZETmFJw0AcWY30WsaWBhJxj/zSmFXWjgEGEGZH56zwbbNwfMZOYZ1jz4haAVxGu+TdXsOl2yMGzN7jQ==", - "requires": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" - }, - "dependencies": { - "tslib": { -@@ -9617,11 +10552,11 @@ - } - }, - "@angular/compiler": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-11.2.14.tgz", -- "integrity": "sha512-XBOK3HgA+/y6Cz7kOX4zcJYmgJ264XnfcbXUMU2cD7Ac+mbNhLPKohWrEiSWalfcjnpf5gRfufQrQP7lpAGu0A==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-12.2.14.tgz", -+ "integrity": "sha512-dwmZi+n66IUzRFlGWu9mjXq170ZEsaDvlNLZzaPgs6vZTa4Kt7PWvIF/Y7TMvnVv/uqNG6kOhfmOkf6rfz1Gjg==", - "requires": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" - }, - "dependencies": { - "tslib": { -@@ -9632,11 +10567,11 @@ - } - }, - "@angular/core": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/core/-/core-11.2.14.tgz", -- "integrity": "sha512-vpR4XqBGitk1Faph37CSpemwIYTmJ3pdIVNoHKP6jLonpWu+0azkchf0f7oD8/2ivj2F81opcIw0tcsy/D/5Vg==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/core/-/core-12.2.14.tgz", -+ "integrity": "sha512-dlVk7yqUHL2R/eCmM8LsWuxhEBfzg0y1zHt0UqCuFwlCoiw+IG4HFy4OlZEUw9NUEZJSv0aDv3sWqxLkvK5vvg==", - "requires": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" - }, - "dependencies": { - "tslib": { -@@ -9647,11 +10582,11 @@ - } - }, - "@angular/forms": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-11.2.14.tgz", -- "integrity": "sha512-4LWqY6KEIk1AZQFnk+4PJSOCamlD4tumuVN06gO4D0dZo9Cx+GcvW6pM6N0CPubRvPs3sScCnu20WT11HNWC1w==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-12.2.14.tgz", -+ "integrity": "sha512-/9/gSJUBXVRVdRnzgJnALAQZYJATuGDMkFC9ms9DEMG4PMAhe9x4if1lJjN6noz5RAom3qNuVBNWaYAPUxlcBQ==", - "requires": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" - }, - "dependencies": { - "tslib": { -@@ -9662,11 +10597,11 @@ - } - }, - "@angular/platform-browser": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-11.2.14.tgz", -- "integrity": "sha512-fb7b7ss/gRoP8wLAN17W62leMgjynuyjEPU2eUoAAazsG9f2cgM+z3rK29GYncDVyYQxZUZYnjSqvL6GSXx86A==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-12.2.14.tgz", -+ "integrity": "sha512-fWcE2rnJ3ZCISa1oPfsIDV7FBZBoLFEdDuMXAiDYqDPKvF/E5U5nHrS+K4SlLAi094bMobtTOReNWl/Ienniyw==", - "requires": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" - }, - "dependencies": { - "tslib": { -@@ -9677,11 +10612,11 @@ - } - }, - "@angular/platform-browser-dynamic": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-11.2.14.tgz", -- "integrity": "sha512-TWTPdFs6iBBcp+/YMsgCRQwdHpWGq8KjeJDJ2tfatGgBD3Gqt2YaHOMST1zPW6RkrmupytTejuVqXzeaKWFxuw==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-12.2.14.tgz", -+ "integrity": "sha512-0NPF7mS91Tct8rBmOLZPmnLSuS4kbLHXo6eTgrg80OC0vlzBiQwGDVW4X3KncCoX9CpevaGJCdSMc+uPNsFOUQ==", - "requires": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" - }, - "dependencies": { - "tslib": { -@@ -9692,11 +10627,11 @@ - } - }, - "@angular/router": { -- "version": "11.2.14", -- "resolved": "https://registry.npmjs.org/@angular/router/-/router-11.2.14.tgz", -- "integrity": "sha512-3aYBmj+zrEL9yf/ntIQxHIYaWShZOBKP3U07X2mX+TPMpGlvHDnR7L6bWhQVZwewzMMz7YVR16ldg50IFuAlfA==", -+ "version": "12.2.14", -+ "resolved": "https://registry.npmjs.org/@angular/router/-/router-12.2.14.tgz", -+ "integrity": "sha512-yP5grSnqBvc4vNhtYdcxDgDYIebUKs5f0xyFkUJM5030UnQ0CV45tBsSxHMkQbPZucIfOuxpRy8xy5+4GizuwQ==", - "requires": { -- "tslib": "^2.0.0" -+ "tslib": "^2.2.0" - }, - "dependencies": { - "tslib": { -@@ -9707,12 +10642,12 @@ - } - }, - "@babel/code-frame": { -- "version": "7.14.5", -- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", -- "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", -+ "version": "7.16.0", -+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", -+ "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", - "dev": true, - "requires": { -- "@babel/highlight": "^7.14.5" -+ "@babel/highlight": "^7.16.0" - } - }, - "@babel/helper-validator-identifier": { -@@ -9722,12 +10657,12 @@ - "dev": true - }, - "@babel/highlight": { -- "version": "7.14.5", -- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", -- "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", -+ "version": "7.16.0", -+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", -+ "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", - "dev": true, - "requires": { -- "@babel/helper-validator-identifier": "^7.14.5", -+ "@babel/helper-validator-identifier": "^7.15.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, -@@ -9787,22 +10722,22 @@ - "@bitwarden/jslib-angular": { - "version": "file:angular", - "requires": { -- "@angular/animations": "^11.2.11", -- "@angular/cdk": "^11.2.10", -- "@angular/common": "^11.2.11", -- "@angular/compiler": "^11.2.11", -- "@angular/core": "^11.2.11", -- "@angular/forms": "^11.2.11", -- "@angular/platform-browser": "^11.2.11", -- "@angular/platform-browser-dynamic": "^11.2.11", -- "@angular/router": "^11.2.11", -+ "@angular/animations": "^12.2.13", -+ "@angular/cdk": "^12.2.13", -+ "@angular/common": "^12.2.13", -+ "@angular/compiler": "^12.2.13", -+ "@angular/core": "^12.2.13", -+ "@angular/forms": "^12.2.13", -+ "@angular/platform-browser": "^12.2.13", -+ "@angular/platform-browser-dynamic": "^12.2.13", -+ "@angular/router": "^12.2.13", - "@bitwarden/jslib-common": "file:../common", - "@types/duo_web_sdk": "^2.7.1", - "duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git", - "rimraf": "^3.0.2", -- "rxjs": "6.6.7", -+ "rxjs": "^7.4.0", - "tldjs": "^2.3.1", -- "typescript": "4.1.5", -+ "typescript": "4.3.5", - "zone.js": "0.11.4" - } - }, -@@ -9812,7 +10747,7 @@ - "@microsoft/signalr": "5.0.10", - "@microsoft/signalr-protocol-msgpack": "5.0.10", - "@types/lunr": "^2.3.3", -- "@types/node": "^14.17.1", -+ "@types/node": "^16.11.12", - "@types/node-forge": "^0.9.7", - "@types/papaparse": "^5.2.5", - "@types/tldjs": "^2.3.0", -@@ -9823,9 +10758,9 @@ - "node-forge": "^0.10.0", - "papaparse": "^5.3.0", - "rimraf": "^3.0.2", -- "rxjs": "6.6.7", -+ "rxjs": "^7.4.0", - "tldjs": "^2.3.1", -- "typescript": "4.1.5", -+ "typescript": "4.3.5", - "zxcvbn": "^4.4.2" - } - }, -@@ -9834,15 +10769,15 @@ - "requires": { - "@bitwarden/jslib-common": "file:../common", - "@nodert-win10-rs4/windows.security.credentials.ui": "^0.4.4", -- "@types/node": "^14.17.1", -- "electron": "14.2.0", -+ "@types/node": "^16.11.12", -+ "electron": "16.0.7", - "electron-log": "4.4.1", - "electron-store": "8.0.1", -- "electron-updater": "4.3.9", -+ "electron-updater": "4.6.1", - "forcefocus": "^1.1.0", - "keytar": "7.7.0", - "rimraf": "^3.0.2", -- "typescript": "4.1.5" -+ "typescript": "4.3.5" - } - }, - "@bitwarden/jslib-node": { -@@ -9851,7 +10786,7 @@ - "@bitwarden/jslib-common": "file:../common", - "@types/inquirer": "^7.3.1", - "@types/lowdb": "^1.0.10", -- "@types/node": "^14.17.1", -+ "@types/node": "^16.11.12", - "@types/node-fetch": "^2.5.10", - "chalk": "^4.1.1", - "commander": "7.2.0", -@@ -9861,47 +10796,33 @@ - "lowdb": "1.0.0", - "node-fetch": "^2.6.1", - "rimraf": "^3.0.2", -- "typescript": "4.1.5" -- }, -- "dependencies": { -- "form-data": { -- "version": "4.0.0", -- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", -- "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", -- "requires": { -- "asynckit": "^0.4.0", -- "combined-stream": "^1.0.8", -- "mime-types": "^2.1.12" -- } -- } -+ "typescript": "4.3.5" - } - }, - "@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", -- "dev": true, -- "peer": true -+ "dev": true - }, - "@cspotcode/source-map-support": { -- "version": "0.6.1", -- "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.6.1.tgz", -- "integrity": "sha512-DX3Z+T5dt1ockmPdobJS/FAsQPW4V4SrWEhD2iYQT2Cb2tQsiMnYxrcUH9By/Z3B+v0S5LMBkQtV/XOBbpLEOg==", -+ "version": "0.7.0", -+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", -+ "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", - "dev": true, -- "peer": true, - "requires": { - "@cspotcode/source-map-consumer": "0.8.0" - } - }, - "@electron/get": { -- "version": "1.13.0", -- "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.13.0.tgz", -- "integrity": "sha512-+SjZhRuRo+STTO1Fdhzqnv9D2ZhjxXP6egsJ9kiO8dtP68cDx7dFCwWi64dlMQV7sWcfW1OYCW4wviEBzmRsfQ==", -+ "version": "1.13.1", -+ "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.13.1.tgz", -+ "integrity": "sha512-U5vkXDZ9DwXtkPqlB45tfYnnYBN8PePp1z/XDCupnSpdrxT8/ThCv9WCwPLf9oqiSGZTkH6dx2jDUPuoXpjkcA==", - "requires": { - "debug": "^4.1.1", - "env-paths": "^2.2.0", - "fs-extra": "^8.1.0", -- "global-agent": "^2.0.2", -+ "global-agent": "^3.0.0", - "global-tunnel-ng": "^2.7.1", - "got": "^9.6.0", - "progress": "^2.0.3", -@@ -9984,34 +10905,30 @@ - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", -- "dev": true, -- "peer": true -+ "dev": true - }, - "@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", -- "dev": true, -- "peer": true -+ "dev": true - }, - "@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", -- "dev": true, -- "peer": true -+ "dev": true - }, - "@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", -- "dev": true, -- "peer": true -+ "dev": true - }, - "@types/component-emitter": { -- "version": "1.2.10", -- "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", -- "integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg==", -+ "version": "1.2.11", -+ "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", -+ "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==", - "dev": true - }, - "@types/cookie": { -@@ -10040,18 +10957,41 @@ - "requires": { - "@types/through": "*", - "rxjs": "^6.4.0" -+ }, -+ "dependencies": { -+ "rxjs": { -+ "version": "6.6.7", -+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", -+ "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", -+ "dev": true, -+ "requires": { -+ "tslib": "^1.9.0" -+ } -+ }, -+ "tslib": { -+ "version": "1.14.1", -+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", -+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", -+ "dev": true -+ } - } - }, - "@types/jasmine": { -- "version": "3.9.1", -- "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.9.1.tgz", -- "integrity": "sha512-PVpjh8S8lqKFKurWSKdFATlfBHGPzgy0PoDdzQ+rr78jTQ0aacyh9YndzZcAUPxhk4kRujItFFGQdUJ7flHumw==", -+ "version": "3.10.2", -+ "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.10.2.tgz", -+ "integrity": "sha512-qs4xjVm4V/XjM6owGm/x6TNmhGl5iKX8dkTdsgdgl9oFnqgzxLepnS7rN9Tdo7kDmnFD/VEqKrW57cGD2odbEg==", -+ "dev": true -+ }, -+ "@types/json5": { -+ "version": "0.0.29", -+ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", -+ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", - "dev": true - }, - "@types/lodash": { -- "version": "4.14.175", -- "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.175.tgz", -- "integrity": "sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw==", -+ "version": "4.14.178", -+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", -+ "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==", - "dev": true - }, - "@types/lowdb": { -@@ -10070,9 +11010,10 @@ - "dev": true - }, - "@types/node": { -- "version": "14.17.20", -- "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.20.tgz", -- "integrity": "sha512-gI5Sl30tmhXsqkNvopFydP7ASc4c2cLfGNQrVKN3X90ADFWFsPEsotm/8JHSUJQKTHbwowAHtcJPeyVhtKv0TQ==" -+ "version": "16.11.13", -+ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.13.tgz", -+ "integrity": "sha512-eUXZzHLHoZqj1frtUetNkUetYoJ6X55UmrVnFD4DMhVeAmwLjniZhtBmsRiemQh4uq4G3vUra/Ws/hs9vEvL3Q==", -+ "dev": true - }, - "@types/node-fetch": { - "version": "2.5.12", -@@ -10082,6 +11023,19 @@ - "requires": { - "@types/node": "*", - "form-data": "^3.0.0" -+ }, -+ "dependencies": { -+ "form-data": { -+ "version": "3.0.1", -+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", -+ "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", -+ "dev": true, -+ "requires": { -+ "asynckit": "^0.4.0", -+ "combined-stream": "^1.0.8", -+ "mime-types": "^2.1.12" -+ } -+ } - } - }, - "@types/node-forge": { -@@ -10094,18 +11048,18 @@ - } - }, - "@types/papaparse": { -- "version": "5.2.6", -- "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.2.6.tgz", -- "integrity": "sha512-xGKSd0UTn58N1h0+zf8mW863Rv8BvXcGibEgKFtBIXZlcDXAmX/T4RdDO2mwmrmOypUDt5vRgo2v32a78JdqUA==", -+ "version": "5.3.1", -+ "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.1.tgz", -+ "integrity": "sha512-1lbngk9wty2kCyQB42LjqSa12SEop3t9wcEC7/xYr3ujTSTmv7HWKjKYXly0GkMfQ42PRb2lFPFEibDOiMXS0g==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/semver": { -- "version": "7.3.8", -- "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.8.tgz", -- "integrity": "sha512-D/2EJvAlCEtYFEYmmlGwbGXuK886HzyCc3nZX/tkFTQdEU8jZDAgiv08P162yB17y4ZXZoq7yFAnW4GDBb9Now==" -+ "version": "7.3.9", -+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", -+ "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==" - }, - "@types/through": { - "version": "0.0.30", -@@ -10346,9 +11300,9 @@ - } - }, - "acorn": { -- "version": "8.5.0", -- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", -- "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", -+ "version": "8.6.0", -+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", -+ "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==", - "dev": true - }, - "acorn-globals": { -@@ -10383,6 +11337,16 @@ - "debug": "4" - } - }, -+ "aggregate-error": { -+ "version": "3.1.0", -+ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", -+ "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", -+ "dev": true, -+ "requires": { -+ "clean-stack": "^2.0.0", -+ "indent-string": "^4.0.0" -+ } -+ }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", -@@ -10411,9 +11375,9 @@ - }, - "dependencies": { - "ajv": { -- "version": "8.6.3", -- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", -- "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", -+ "version": "8.8.2", -+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", -+ "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", -@@ -10442,12 +11406,37 @@ - "dev": true, - "requires": { - "string-width": "^4.1.0" -+ }, -+ "dependencies": { -+ "emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "dev": true -+ }, -+ "is-fullwidth-code-point": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", -+ "dev": true -+ }, -+ "string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "dev": true, -+ "requires": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ } -+ } - } - }, - "ansi-colors": { -- "version": "3.2.4", -- "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", -- "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", -+ "version": "4.1.1", -+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", -+ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-escapes": { -@@ -10499,8 +11488,7 @@ - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", -- "dev": true, -- "peer": true -+ "dev": true - }, - "argparse": { - "version": "1.0.10", -@@ -10588,6 +11576,12 @@ - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, -+ "astral-regex": { -+ "version": "2.0.0", -+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", -+ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", -+ "dev": true -+ }, - "async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", -@@ -10678,9 +11672,9 @@ - } - }, - "base64-arraybuffer": { -- "version": "0.1.4", -- "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", -- "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=", -+ "version": "1.0.1", -+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.1.tgz", -+ "integrity": "sha512-vFIUq7FdLtjZMhATwDul5RZWv2jpXQ09Pd6jcVEOvIsqCWTRFD/ONHNfyOS8dA/Ippi5dsIgpyKWKZaAKZltbA==", - "dev": true - }, - "base64-js": { -@@ -10741,21 +11735,21 @@ - "dev": true - }, - "body-parser": { -- "version": "1.19.0", -- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", -- "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", -+ "version": "1.19.1", -+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", -+ "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", - "dev": true, - "requires": { -- "bytes": "3.1.0", -+ "bytes": "3.1.1", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", -- "http-errors": "1.7.2", -+ "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", -- "qs": "6.7.0", -- "raw-body": "2.4.0", -- "type-is": "~1.6.17" -+ "qs": "6.9.6", -+ "raw-body": "2.4.2", -+ "type-is": "~1.6.18" - }, - "dependencies": { - "debug": { -@@ -10797,6 +11791,29 @@ - "wrap-ansi": "^7.0.0" - }, - "dependencies": { -+ "emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "dev": true -+ }, -+ "is-fullwidth-code-point": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", -+ "dev": true -+ }, -+ "string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "dev": true, -+ "requires": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ } -+ }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", -@@ -10961,9 +11978,9 @@ - "dev": true - }, - "builder-util-runtime": { -- "version": "8.7.5", -- "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.7.5.tgz", -- "integrity": "sha512-fgUFHKtMNjdvH6PDRFntdIGUPgwZ69sXsAqEulCtoiqgWes5agrMq/Ud274zjJRTbckYh2PHh8/1CpFc6dpsbQ==", -+ "version": "8.9.1", -+ "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.9.1.tgz", -+ "integrity": "sha512-c8a8J3wK6BIVLW7ls+7TRK9igspTbzWmUqxFbgK0m40Ggm6efUbxtWVCGIjc+dtchyr5qAMAUL6iEGRdS/6vwg==", - "requires": { - "debug": "^4.3.2", - "sax": "^1.2.4" -@@ -10982,9 +11999,9 @@ - "dev": true - }, - "bytes": { -- "version": "3.1.0", -- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", -- "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", -+ "version": "3.1.1", -+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", -+ "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==", - "dev": true - }, - "cacache": { -@@ -11068,9 +12085,9 @@ - } - }, - "camelcase": { -- "version": "6.2.0", -- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", -- "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", -+ "version": "6.2.1", -+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz", -+ "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==", - "dev": true - }, - "chalk": { -@@ -11158,6 +12175,12 @@ - "static-extend": "^0.1.1" - } - }, -+ "clean-stack": { -+ "version": "2.2.0", -+ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", -+ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", -+ "dev": true -+ }, - "cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", -@@ -11172,6 +12195,16 @@ - "restore-cursor": "^3.1.0" - } - }, -+ "cli-truncate": { -+ "version": "3.1.0", -+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", -+ "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", -+ "dev": true, -+ "requires": { -+ "slice-ansi": "^5.0.0", -+ "string-width": "^5.0.0" -+ } -+ }, - "cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", -@@ -11186,6 +12219,31 @@ - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" -+ }, -+ "dependencies": { -+ "emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "dev": true -+ }, -+ "is-fullwidth-code-point": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", -+ "dev": true -+ }, -+ "string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "dev": true, -+ "requires": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ } -+ } - } - }, - "clone-deep": { -@@ -11235,6 +12293,12 @@ - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, -+ "colorette": { -+ "version": "2.0.16", -+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", -+ "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", -+ "dev": true -+ }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", -@@ -11284,9 +12348,9 @@ - } - }, - "concurrently": { -- "version": "6.2.2", -- "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-6.2.2.tgz", -- "integrity": "sha512-7a45BjVakAl3pprLOeqaOoZfIWZRmdC68NkjyzPbKu0/pE6CRmqS3l8RG7Q2cX9LnRHkB0oPM6af8PS7NEQvYQ==", -+ "version": "6.4.0", -+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-6.4.0.tgz", -+ "integrity": "sha512-HZ3D0RTQMH3oS4gvtYj1P+NBc6PzE2McEra6yEFcQKrUQ9HvtTGU4Dbne083F034p+LRb7kWU0tPRNvSGs1UCQ==", - "dev": true, - "requires": { - "chalk": "^4.1.0", -@@ -11297,12 +12361,29 @@ - "supports-color": "^8.1.0", - "tree-kill": "^1.2.2", - "yargs": "^16.2.0" -+ }, -+ "dependencies": { -+ "rxjs": { -+ "version": "6.6.7", -+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", -+ "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", -+ "dev": true, -+ "requires": { -+ "tslib": "^1.9.0" -+ } -+ }, -+ "tslib": { -+ "version": "1.14.1", -+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", -+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", -+ "dev": true -+ } - } - }, - "conf": { -- "version": "10.0.3", -- "resolved": "https://registry.npmjs.org/conf/-/conf-10.0.3.tgz", -- "integrity": "sha512-4gtQ/Q36qVxBzMe6B7gWOAfni1VdhuHkIzxydHkclnwGmgN+eW4bb6jj73vigCfr7d3WlmqawvhZrpCUCTPYxQ==", -+ "version": "10.1.1", -+ "resolved": "https://registry.npmjs.org/conf/-/conf-10.1.1.tgz", -+ "integrity": "sha512-z2civwq/k8TMYtcn3SVP0Peso4otIWnHtcTuHhQ0zDZDdP4NTxqEc8owfkz4zBsdMYdn/LFcE+ZhbCeqkhtq3Q==", - "requires": { - "ajv": "^8.6.3", - "ajv-formats": "^2.1.1", -@@ -11317,9 +12398,9 @@ - }, - "dependencies": { - "ajv": { -- "version": "8.6.3", -- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", -- "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", -+ "version": "8.8.2", -+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", -+ "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", -@@ -11484,12 +12565,6 @@ - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, -- "core-js": { -- "version": "3.18.1", -- "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.18.1.tgz", -- "integrity": "sha512-vJlUi/7YdlCZeL6fXvWNaLUPh/id12WXj3MbkMw5uOyF0PfWPBNOCNbs53YqgrvtujLNlt9JQpruyIKkUZ+PKA==", -- "optional": true -- }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", -@@ -11554,8 +12629,29 @@ - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", -+ "dev": true -+ }, -+ "cross-spawn": { -+ "version": "7.0.3", -+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", -+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, -- "peer": true -+ "requires": { -+ "path-key": "^3.1.0", -+ "shebang-command": "^2.0.0", -+ "which": "^2.0.1" -+ }, -+ "dependencies": { -+ "which": { -+ "version": "2.0.2", -+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", -+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", -+ "dev": true, -+ "requires": { -+ "isexe": "^2.0.0" -+ } -+ } -+ } - }, - "crypto-browserify": { - "version": "3.12.0", -@@ -11629,9 +12725,9 @@ - } - }, - "date-fns": { -- "version": "2.24.0", -- "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.24.0.tgz", -- "integrity": "sha512-6ujwvwgPID6zbI0o7UbURi2vlLDR9uP26+tW6Lg+Ji3w7dd0i3DOcjcClLjLPranT60SSEFBwdSyYwn/ZkPIuw==", -+ "version": "2.27.0", -+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.27.0.tgz", -+ "integrity": "sha512-sj+J0Mo2p2X1e306MHq282WS4/A8Pz/95GIFcsPNMPMZVI3EUrAdSv90al1k+p74WGLCruMXk23bfEDZa71X9Q==", - "dev": true - }, - "date-format": { -@@ -11656,9 +12752,9 @@ - } - }, - "debug": { -- "version": "4.3.2", -- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", -- "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", -+ "version": "4.3.3", -+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", -+ "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "requires": { - "ms": "2.1.2" - } -@@ -11857,13 +12953,20 @@ - "dev": true - }, - "electron": { -- "version": "14.2.0", -- "resolved": "https://registry.npmjs.org/electron/-/electron-14.2.0.tgz", -- "integrity": "sha512-6CmAv1P0xcwK3FQOSA27fHI36/wctSFVgj46VODn56srXXQWeolkK1VzeAFNE613iAuuH9jJdHvE3gz+c7XkNA==", -+ "version": "16.0.7", -+ "resolved": "https://registry.npmjs.org/electron/-/electron-16.0.7.tgz", -+ "integrity": "sha512-/IMwpBf2svhA1X/7Q58RV+Nn0fvUJsHniG4TizaO7q4iKFYSQ6hBvsLz+cylcZ8hRMKmVy5G1XaMNJID2ah23w==", - "requires": { -- "@electron/get": "^1.0.1", -+ "@electron/get": "^1.13.0", - "@types/node": "^14.6.2", - "extract-zip": "^1.0.3" -+ }, -+ "dependencies": { -+ "@types/node": { -+ "version": "14.18.0", -+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.0.tgz", -+ "integrity": "sha512-0GeIl2kmVMXEnx8tg1SlG6Gg8vkqirrW752KqolYo1PHevhhZN3bhJ67qHj+bQaINhX0Ra3TlWwRvMCd9iEfNQ==" -+ } - } - }, - "electron-log": { -@@ -11888,15 +12991,15 @@ - } - }, - "electron-updater": { -- "version": "4.3.9", -- "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.3.9.tgz", -- "integrity": "sha512-LCNfedSwZfS4Hza+pDyPR05LqHtGorCStaBgVpRnfKxOlZcvpYEX0AbMeH5XUtbtGRoH2V8osbbf2qKPNb7AsA==", -+ "version": "4.6.1", -+ "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.6.1.tgz", -+ "integrity": "sha512-YsU1mHqXLrXXmBMsxhxy24PrbaB8rnpZDPmFa2gOkTYk/Ch13+R0fjsRSpPYvqtskVVY0ux8fu+HnUkVkqc7og==", - "requires": { -- "@types/semver": "^7.3.5", -- "builder-util-runtime": "8.7.5", -+ "@types/semver": "^7.3.6", -+ "builder-util-runtime": "8.9.1", - "fs-extra": "^10.0.0", - "js-yaml": "^4.1.0", -- "lazy-val": "^1.0.4", -+ "lazy-val": "^1.0.5", - "lodash.escaperegexp": "^4.1.2", - "lodash.isequal": "^4.5.0", - "semver": "^7.3.5" -@@ -11986,9 +13089,10 @@ - } - }, - "emoji-regex": { -- "version": "8.0.0", -- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" -+ "version": "9.2.2", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", -+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", -+ "dev": true - }, - "emojis-list": { - "version": "3.0.0", -@@ -12011,36 +13115,39 @@ - } - }, - "engine.io": { -- "version": "4.1.1", -- "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.1.1.tgz", -- "integrity": "sha512-t2E9wLlssQjGw0nluF6aYyfX8LwYU8Jj0xct+pAhfWfv/YrBn6TSNtEYsgxHIfaMqfrLx07czcMg9bMN6di+3w==", -+ "version": "6.1.0", -+ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.0.tgz", -+ "integrity": "sha512-ErhZOVu2xweCjEfYcTdkCnEYUiZgkAcBBAhW4jbIvNG8SLU3orAqoJCiytZjYF7eTpVmmCrLDjLIEaPlUAs1uw==", - "dev": true, - "requires": { -+ "@types/cookie": "^0.4.1", -+ "@types/cors": "^2.8.12", -+ "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", -- "engine.io-parser": "~4.0.0", -- "ws": "~7.4.2" -+ "engine.io-parser": "~5.0.0", -+ "ws": "~8.2.3" - }, - "dependencies": { - "ws": { -- "version": "7.4.6", -- "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", -- "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", -+ "version": "8.2.3", -+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", -+ "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true, - "requires": {} - } - } - }, - "engine.io-parser": { -- "version": "4.0.3", -- "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.3.tgz", -- "integrity": "sha512-xEAAY0msNnESNPc00e19y5heTPX4y/TJ36gr8t1voOaNmTojP9b3oK3BbJLFufW2XFPQaaijpFewm2g2Um3uqA==", -+ "version": "5.0.2", -+ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.2.tgz", -+ "integrity": "sha512-wuiO7qO/OEkPJSFueuATIXtrxF7/6GTbAO9QLv7nnbjwZ5tYhLm9zxvLwxstRs0dcT0KUlWTjtIOs1T86jt12g==", - "dev": true, - "requires": { -- "base64-arraybuffer": "0.1.4" -+ "base64-arraybuffer": "~1.0.1" - } - }, - "enhanced-resolve": { -@@ -12054,6 +13161,15 @@ - "tapable": "^1.0.0" - } - }, -+ "enquirer": { -+ "version": "2.3.6", -+ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", -+ "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", -+ "dev": true, -+ "requires": { -+ "ansi-colors": "^4.1.1" -+ } -+ }, - "ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", -@@ -12173,9 +13289,9 @@ - } - }, - "estraverse": { -- "version": "5.2.0", -- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", -- "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", -+ "version": "5.3.0", -+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", -+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "esutils": { -@@ -12219,6 +13335,23 @@ - "safe-buffer": "^5.1.1" - } - }, -+ "execa": { -+ "version": "5.1.1", -+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", -+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", -+ "dev": true, -+ "requires": { -+ "cross-spawn": "^7.0.3", -+ "get-stream": "^6.0.0", -+ "human-signals": "^2.1.0", -+ "is-stream": "^2.0.0", -+ "merge-stream": "^2.0.0", -+ "npm-run-path": "^4.0.1", -+ "onetime": "^5.1.2", -+ "signal-exit": "^3.0.3", -+ "strip-final-newline": "^2.0.0" -+ } -+ }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", -@@ -12279,6 +13412,16 @@ - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" -+ }, -+ "dependencies": { -+ "tmp": { -+ "version": "0.0.33", -+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", -+ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", -+ "requires": { -+ "os-tmpdir": "~1.0.2" -+ } -+ } - } - }, - "extglob": { -@@ -12522,9 +13665,9 @@ - } - }, - "follow-redirects": { -- "version": "1.14.4", -- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", -- "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==", -+ "version": "1.14.6", -+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.6.tgz", -+ "integrity": "sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A==", - "dev": true - }, - "for-in": { -@@ -12543,10 +13686,9 @@ - } - }, - "form-data": { -- "version": "3.0.1", -- "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", -- "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", -- "dev": true, -+ "version": "4.0.0", -+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", -+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", -@@ -12673,12 +13815,10 @@ - "dev": true - }, - "get-stream": { -- "version": "4.1.0", -- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", -- "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", -- "requires": { -- "pump": "^3.0.0" -- } -+ "version": "6.0.1", -+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", -+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", -+ "dev": true - }, - "get-value": { - "version": "2.0.6", -@@ -12715,13 +13855,12 @@ - } - }, - "global-agent": { -- "version": "2.2.0", -- "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.2.0.tgz", -- "integrity": "sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg==", -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", -+ "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", - "optional": true, - "requires": { - "boolean": "^3.0.1", -- "core-js": "^3.6.5", - "es6-error": "^4.1.1", - "matcher": "^3.0.0", - "roarr": "^2.15.3", -@@ -12801,6 +13940,16 @@ - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" -+ }, -+ "dependencies": { -+ "get-stream": { -+ "version": "4.1.0", -+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", -+ "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", -+ "requires": { -+ "pump": "^3.0.0" -+ } -+ } - } - }, - "graceful-fs": { -@@ -12951,24 +14100,16 @@ - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" - }, - "http-errors": { -- "version": "1.7.2", -- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", -- "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", -+ "version": "1.8.1", -+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", -+ "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "dev": true, - "requires": { - "depd": "~1.1.2", -- "inherits": "2.0.3", -- "setprototypeof": "1.1.1", -+ "inherits": "2.0.4", -+ "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", -- "toidentifier": "1.0.0" -- }, -- "dependencies": { -- "inherits": { -- "version": "2.0.3", -- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", -- "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", -- "dev": true -- } -+ "toidentifier": "1.0.1" - } - }, - "http-proxy": { -@@ -13008,6 +14149,18 @@ - "debug": "4" - } - }, -+ "human-signals": { -+ "version": "2.1.0", -+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", -+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", -+ "dev": true -+ }, -+ "husky": { -+ "version": "7.0.4", -+ "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz", -+ "integrity": "sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==", -+ "dev": true -+ }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", -@@ -13045,6 +14198,12 @@ - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, -+ "indent-string": { -+ "version": "4.0.0", -+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", -+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", -+ "dev": true -+ }, - "infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", -@@ -13090,6 +14249,41 @@ - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" -+ }, -+ "dependencies": { -+ "emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" -+ }, -+ "is-fullwidth-code-point": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" -+ }, -+ "rxjs": { -+ "version": "6.6.7", -+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", -+ "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", -+ "requires": { -+ "tslib": "^1.9.0" -+ } -+ }, -+ "string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "requires": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ } -+ }, -+ "tslib": { -+ "version": "1.14.1", -+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", -+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" -+ } - } - }, - "is-accessor-descriptor": { -@@ -13137,9 +14331,9 @@ - } - }, - "is-core-module": { -- "version": "2.7.0", -- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.7.0.tgz", -- "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", -+ "version": "2.8.0", -+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", -+ "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", - "dev": true, - "requires": { - "has": "^1.0.3" -@@ -13203,9 +14397,10 @@ - "dev": true - }, - "is-fullwidth-code-point": { -- "version": "3.0.0", -- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" -+ "version": "4.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", -+ "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", -+ "dev": true - }, - "is-glob": { - "version": "4.0.3", -@@ -13269,6 +14464,12 @@ - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" - }, -+ "is-stream": { -+ "version": "2.0.1", -+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", -+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", -+ "dev": true -+ }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", -@@ -13320,21 +14521,82 @@ - "dev": true - }, - "jasmine": { -- "version": "3.9.0", -- "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.9.0.tgz", -- "integrity": "sha512-JgtzteG7xnqZZ51fg7N2/wiQmXon09szkALcRMTgCMX4u/m17gVJFjObnvw5FXkZOWuweHPaPRVB6DI2uN0wVA==", -+ "version": "3.10.0", -+ "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.10.0.tgz", -+ "integrity": "sha512-2Y42VsC+3CQCTzTwJezOvji4qLORmKIE0kwowWC+934Krn6ZXNQYljiwK5st9V3PVx96BSiDYXSB60VVah3IlQ==", - "dev": true, - "requires": { - "glob": "^7.1.6", -- "jasmine-core": "~3.9.0" -+ "jasmine-core": "~3.10.0" - } - }, - "jasmine-core": { -- "version": "3.9.0", -- "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.9.0.tgz", -- "integrity": "sha512-Tv3kVbPCGVrjsnHBZ38NsPU3sDOtNa0XmbG2baiyJqdb5/SPpDO6GVwJYtUryl6KB4q1Ssckwg612ES9Z0dreQ==", -+ "version": "3.10.1", -+ "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.10.1.tgz", -+ "integrity": "sha512-ooZWSDVAdh79Rrj4/nnfklL3NQVra0BcuhcuWoAwwi+znLDoUeH87AFfeX8s+YeYi6xlv5nveRyaA1v7CintfA==", - "dev": true - }, -+ "jasmine-ts": { -+ "version": "0.4.0", -+ "resolved": "https://registry.npmjs.org/jasmine-ts/-/jasmine-ts-0.4.0.tgz", -+ "integrity": "sha512-bIAWJKUwxfuZfGI1ctEbny7+dsyFzsN0eLzOokxh0qIUCofai2WUEKoe3MMllkGEBXJzbEVYr2IyhJBv4j7SBA==", -+ "dev": true, -+ "requires": { -+ "yargs": "^17.0.1" -+ }, -+ "dependencies": { -+ "emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "dev": true -+ }, -+ "is-fullwidth-code-point": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", -+ "dev": true -+ }, -+ "string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "dev": true, -+ "requires": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ } -+ }, -+ "y18n": { -+ "version": "5.0.8", -+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", -+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", -+ "dev": true -+ }, -+ "yargs": { -+ "version": "17.3.1", -+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", -+ "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", -+ "dev": true, -+ "requires": { -+ "cliui": "^7.0.2", -+ "escalade": "^3.1.1", -+ "get-caller-file": "^2.0.5", -+ "require-directory": "^2.1.1", -+ "string-width": "^4.2.3", -+ "y18n": "^5.0.5", -+ "yargs-parser": "^21.0.0" -+ } -+ }, -+ "yargs-parser": { -+ "version": "21.0.0", -+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz", -+ "integrity": "sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA==", -+ "dev": true -+ } -+ } -+ }, - "jasmine-ts-console-reporter": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/jasmine-ts-console-reporter/-/jasmine-ts-console-reporter-3.1.1.tgz", -@@ -13396,6 +14658,19 @@ - "whatwg-url": "^8.5.0", - "ws": "^7.4.6", - "xml-name-validator": "^3.0.0" -+ }, -+ "dependencies": { -+ "form-data": { -+ "version": "3.0.1", -+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", -+ "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", -+ "dev": true, -+ "requires": { -+ "asynckit": "^0.4.0", -+ "combined-stream": "^1.0.8", -+ "mime-types": "^2.1.12" -+ } -+ } - } - }, - "json-buffer": { -@@ -13444,9 +14719,9 @@ - } - }, - "karma": { -- "version": "6.3.4", -- "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.4.tgz", -- "integrity": "sha512-hbhRogUYIulfkBTZT7xoPrCYhRBnBoqbbL4fszWD0ReFGUxU+LYBr3dwKdAluaDQ/ynT9/7C+Lf7pPNW4gSx4Q==", -+ "version": "6.3.9", -+ "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.9.tgz", -+ "integrity": "sha512-E/MqdLM9uVIhfuyVnrhlGBu4miafBdXEAEqCmwdEMh3n17C7UWC/8Kvm3AYKr91gc7scutekZ0xv6rxRaUCtnw==", - "dev": true, - "requires": { - "body-parser": "^1.19.0", -@@ -13467,10 +14742,10 @@ - "qjobs": "^1.2.0", - "range-parser": "^1.2.1", - "rimraf": "^3.0.2", -- "socket.io": "^3.1.0", -+ "socket.io": "^4.2.0", - "source-map": "^0.6.1", - "tmp": "^0.2.1", -- "ua-parser-js": "^0.7.28", -+ "ua-parser-js": "^0.7.30", - "yargs": "^16.1.1" - }, - "dependencies": { -@@ -13479,15 +14754,6 @@ - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true -- }, -- "tmp": { -- "version": "0.2.1", -- "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", -- "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", -- "dev": true, -- "requires": { -- "rimraf": "^3.0.0" -- } - } - } - }, -@@ -13519,9 +14785,9 @@ - } - }, - "karma-firefox-launcher": { -- "version": "2.1.1", -- "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.1.tgz", -- "integrity": "sha512-VzDMgPseXak9DtfyE1O5bB2BwsMy1zzO1kUxVW1rP0yhC4tDNJ0p3JoFdzvrK4QqVzdqUMa9Rx9YzkdFp8hz3Q==", -+ "version": "2.1.2", -+ "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.2.tgz", -+ "integrity": "sha512-VV9xDQU1QIboTrjtGVD4NCfzIH7n01ZXqy/qpBhnOeGVOkG5JYPEm8kuSd7psHE6WouZaQ9Ool92g8LFweSNMA==", - "dev": true, - "requires": { - "is-wsl": "^2.2.0", -@@ -13643,14 +14909,118 @@ - "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", - "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==" - }, -- "levn": { -- "version": "0.3.0", -- "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", -- "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", -+ "levn": { -+ "version": "0.3.0", -+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", -+ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", -+ "dev": true, -+ "requires": { -+ "prelude-ls": "~1.1.2", -+ "type-check": "~0.3.2" -+ } -+ }, -+ "lilconfig": { -+ "version": "2.0.4", -+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", -+ "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", -+ "dev": true -+ }, -+ "lint-staged": { -+ "version": "12.1.2", -+ "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.1.2.tgz", -+ "integrity": "sha512-bSMcQVqMW98HLLLR2c2tZ+vnDCnx4fd+0QJBQgN/4XkdspGRPc8DGp7UuOEBe1ApCfJ+wXXumYnJmU+wDo7j9A==", -+ "dev": true, -+ "requires": { -+ "cli-truncate": "^3.1.0", -+ "colorette": "^2.0.16", -+ "commander": "^8.3.0", -+ "debug": "^4.3.2", -+ "enquirer": "^2.3.6", -+ "execa": "^5.1.1", -+ "lilconfig": "2.0.4", -+ "listr2": "^3.13.3", -+ "micromatch": "^4.0.4", -+ "normalize-path": "^3.0.0", -+ "object-inspect": "^1.11.0", -+ "string-argv": "^0.3.1", -+ "supports-color": "^9.0.2", -+ "yaml": "^1.10.2" -+ }, -+ "dependencies": { -+ "commander": { -+ "version": "8.3.0", -+ "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", -+ "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", -+ "dev": true -+ }, -+ "supports-color": { -+ "version": "9.2.1", -+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.1.tgz", -+ "integrity": "sha512-Obv7ycoCTG51N7y175StI9BlAXrmgZrFhZOb0/PyjHBher/NmsdBgbbQ1Inhq+gIhz6+7Gb+jWF2Vqi7Mf1xnQ==", -+ "dev": true -+ } -+ } -+ }, -+ "listr2": { -+ "version": "3.13.5", -+ "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.13.5.tgz", -+ "integrity": "sha512-3n8heFQDSk+NcwBn3CgxEibZGaRzx+pC64n3YjpMD1qguV4nWus3Al+Oo3KooqFKTQEJ1v7MmnbnyyNspgx3NA==", - "dev": true, - "requires": { -- "prelude-ls": "~1.1.2", -- "type-check": "~0.3.2" -+ "cli-truncate": "^2.1.0", -+ "colorette": "^2.0.16", -+ "log-update": "^4.0.0", -+ "p-map": "^4.0.0", -+ "rfdc": "^1.3.0", -+ "rxjs": "^7.4.0", -+ "through": "^2.3.8", -+ "wrap-ansi": "^7.0.0" -+ }, -+ "dependencies": { -+ "cli-truncate": { -+ "version": "2.1.0", -+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", -+ "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", -+ "dev": true, -+ "requires": { -+ "slice-ansi": "^3.0.0", -+ "string-width": "^4.2.0" -+ } -+ }, -+ "emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "dev": true -+ }, -+ "is-fullwidth-code-point": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", -+ "dev": true -+ }, -+ "slice-ansi": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", -+ "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", -+ "dev": true, -+ "requires": { -+ "ansi-styles": "^4.0.0", -+ "astral-regex": "^2.0.0", -+ "is-fullwidth-code-point": "^3.0.0" -+ } -+ }, -+ "string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "dev": true, -+ "requires": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ } -+ } - } - }, - "loader-runner": { -@@ -13694,6 +15064,65 @@ - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" - }, -+ "log-update": { -+ "version": "4.0.0", -+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", -+ "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", -+ "dev": true, -+ "requires": { -+ "ansi-escapes": "^4.3.0", -+ "cli-cursor": "^3.1.0", -+ "slice-ansi": "^4.0.0", -+ "wrap-ansi": "^6.2.0" -+ }, -+ "dependencies": { -+ "emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "dev": true -+ }, -+ "is-fullwidth-code-point": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", -+ "dev": true -+ }, -+ "slice-ansi": { -+ "version": "4.0.0", -+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", -+ "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", -+ "dev": true, -+ "requires": { -+ "ansi-styles": "^4.0.0", -+ "astral-regex": "^2.0.0", -+ "is-fullwidth-code-point": "^3.0.0" -+ } -+ }, -+ "string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "dev": true, -+ "requires": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ } -+ }, -+ "wrap-ansi": { -+ "version": "6.2.0", -+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", -+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", -+ "dev": true, -+ "requires": { -+ "ansi-styles": "^4.0.0", -+ "string-width": "^4.1.0", -+ "strip-ansi": "^6.0.0" -+ } -+ } -+ } -+ }, - "log4js": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz", -@@ -13759,8 +15188,7 @@ - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", -- "dev": true, -- "peer": true -+ "dev": true - }, - "map-cache": { - "version": "0.2.2", -@@ -13821,6 +15249,12 @@ - "readable-stream": "^2.0.1" - } - }, -+ "merge-stream": { -+ "version": "2.0.0", -+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", -+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", -+ "dev": true -+ }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", -@@ -13850,22 +15284,22 @@ - } - }, - "mime": { -- "version": "2.5.2", -- "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", -- "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", -+ "version": "2.6.0", -+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", -+ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true - }, - "mime-db": { -- "version": "1.49.0", -- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", -- "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==" -+ "version": "1.51.0", -+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", -+ "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" - }, - "mime-types": { -- "version": "2.1.32", -- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", -- "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", -+ "version": "2.1.34", -+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", -+ "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "requires": { -- "mime-db": "1.49.0" -+ "mime-db": "1.51.0" - } - }, - "mimic-fn": { -@@ -14117,9 +15551,9 @@ - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" - }, - "node-fetch": { -- "version": "2.6.5", -- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", -- "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", -+ "version": "2.6.6", -+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", -+ "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", - "requires": { - "whatwg-url": "^5.0.0" - }, -@@ -14190,20 +15624,20 @@ - } - }, - "nodemon": { -- "version": "2.0.13", -- "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.13.tgz", -- "integrity": "sha512-UMXMpsZsv1UXUttCn6gv8eQPhn6DR4BW+txnL3IN5IHqrCwcrT/yWHfL35UsClGXknTH79r5xbu+6J1zNHuSyA==", -+ "version": "2.0.15", -+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", -+ "integrity": "sha512-gdHMNx47Gw7b3kWxJV64NI+Q5nfl0y5DgDbiVtShiwa7Z0IZ07Ll4RLFo6AjrhzMtoEZn5PDE3/c2AbVsiCkpA==", - "dev": true, - "requires": { -- "chokidar": "^3.2.2", -- "debug": "^3.2.6", -+ "chokidar": "^3.5.2", -+ "debug": "^3.2.7", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.0.4", -- "pstree.remy": "^1.1.7", -+ "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "supports-color": "^5.5.0", - "touch": "^3.1.0", -- "undefsafe": "^2.0.3", -+ "undefsafe": "^2.0.5", - "update-notifier": "^5.1.0" - }, - "dependencies": { -@@ -14268,6 +15702,15 @@ - "pify": "^3.0.0" - } - }, -+ "npm-run-path": { -+ "version": "4.0.1", -+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", -+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", -+ "dev": true, -+ "requires": { -+ "path-key": "^3.0.0" -+ } -+ }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", -@@ -14317,6 +15760,12 @@ - } - } - }, -+ "object-inspect": { -+ "version": "1.11.1", -+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.1.tgz", -+ "integrity": "sha512-If7BjFlpkzzBeV1cqgT3OSWT3azyoxDGajR+iGnFBfVV2EWyDyWaZZW2ERDjUaY2QM8i5jI3Sj7mhsM4DDAqWA==", -+ "dev": true -+ }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", -@@ -14420,6 +15869,15 @@ - "p-limit": "^2.0.0" - } - }, -+ "p-map": { -+ "version": "4.0.0", -+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", -+ "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", -+ "dev": true, -+ "requires": { -+ "aggregate-error": "^3.0.0" -+ } -+ }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", -@@ -14522,6 +15980,12 @@ - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, -+ "path-key": { -+ "version": "3.1.1", -+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", -+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", -+ "dev": true -+ }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", -@@ -14619,6 +16083,12 @@ - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" - }, -+ "prettier": { -+ "version": "2.5.1", -+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", -+ "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", -+ "dev": true -+ }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", -@@ -14739,9 +16209,9 @@ - "dev": true - }, - "qs": { -- "version": "6.7.0", -- "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", -- "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", -+ "version": "6.9.6", -+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", -+ "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==", - "dev": true - }, - "querystring": { -@@ -14787,13 +16257,13 @@ - "dev": true - }, - "raw-body": { -- "version": "2.4.0", -- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", -- "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", -+ "version": "2.4.2", -+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", -+ "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", - "dev": true, - "requires": { -- "bytes": "3.1.0", -- "http-errors": "1.7.2", -+ "bytes": "3.1.1", -+ "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } -@@ -15024,11 +16494,11 @@ - } - }, - "rxjs": { -- "version": "6.6.7", -- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", -- "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", -+ "version": "7.4.0", -+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", -+ "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", - "requires": { -- "tslib": "^1.9.0" -+ "tslib": "~2.1.0" - } - }, - "safe-buffer": { -@@ -15153,9 +16623,9 @@ - "dev": true - }, - "setprototypeof": { -- "version": "1.1.1", -- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", -- "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", -+ "version": "1.2.0", -+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", -+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "sha.js": { -@@ -15177,10 +16647,25 @@ - "kind-of": "^6.0.2" - } - }, -+ "shebang-command": { -+ "version": "2.0.0", -+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", -+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", -+ "dev": true, -+ "requires": { -+ "shebang-regex": "^3.0.0" -+ } -+ }, -+ "shebang-regex": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", -+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", -+ "dev": true -+ }, - "signal-exit": { -- "version": "3.0.5", -- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", -- "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==" -+ "version": "3.0.6", -+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", -+ "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" - }, - "simple-concat": { - "version": "1.0.1", -@@ -15212,6 +16697,24 @@ - } - } - }, -+ "slice-ansi": { -+ "version": "5.0.0", -+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", -+ "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", -+ "dev": true, -+ "requires": { -+ "ansi-styles": "^6.0.0", -+ "is-fullwidth-code-point": "^4.0.0" -+ }, -+ "dependencies": { -+ "ansi-styles": { -+ "version": "6.1.0", -+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz", -+ "integrity": "sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==", -+ "dev": true -+ } -+ } -+ }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", -@@ -15317,26 +16820,23 @@ - } - }, - "socket.io": { -- "version": "3.1.2", -- "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-3.1.2.tgz", -- "integrity": "sha512-JubKZnTQ4Z8G4IZWtaAZSiRP3I/inpy8c/Bsx2jrwGrTbKeVU5xd6qkKMHpChYeM3dWZSO0QACiGK+obhBNwYw==", -+ "version": "4.4.0", -+ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.0.tgz", -+ "integrity": "sha512-bnpJxswR9ov0Bw6ilhCvO38/1WPtE3eA2dtxi2Iq4/sFebiDJQzgKNYA7AuVVdGW09nrESXd90NbZqtDd9dzRQ==", - "dev": true, - "requires": { -- "@types/cookie": "^0.4.0", -- "@types/cors": "^2.8.8", -- "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "~2.0.0", -- "debug": "~4.3.1", -- "engine.io": "~4.1.0", -- "socket.io-adapter": "~2.1.0", -- "socket.io-parser": "~4.0.3" -+ "debug": "~4.3.2", -+ "engine.io": "~6.1.0", -+ "socket.io-adapter": "~2.3.3", -+ "socket.io-parser": "~4.0.4" - } - }, - "socket.io-adapter": { -- "version": "2.1.0", -- "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.1.0.tgz", -- "integrity": "sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg==", -+ "version": "2.3.3", -+ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz", -+ "integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==", - "dev": true - }, - "socket.io-parser": { -@@ -15376,9 +16876,9 @@ - } - }, - "source-map-support": { -- "version": "0.5.20", -- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", -- "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", -+ "version": "0.5.21", -+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", -+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", -@@ -15546,14 +17046,38 @@ - "safe-buffer": "~5.1.0" - } - }, -+ "string-argv": { -+ "version": "0.3.1", -+ "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", -+ "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", -+ "dev": true -+ }, - "string-width": { -- "version": "4.2.3", -- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "version": "5.0.1", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.0.1.tgz", -+ "integrity": "sha512-5ohWO/M4//8lErlUUtrFy3b11GtNOuMOU0ysKCDXFcfXuuvUXu95akgj/i8ofmaGdN0hCqyl6uu9i8dS/mQp5g==", -+ "dev": true, - "requires": { -- "emoji-regex": "^8.0.0", -- "is-fullwidth-code-point": "^3.0.0", -- "strip-ansi": "^6.0.1" -+ "emoji-regex": "^9.2.2", -+ "is-fullwidth-code-point": "^4.0.0", -+ "strip-ansi": "^7.0.1" -+ }, -+ "dependencies": { -+ "ansi-regex": { -+ "version": "6.0.1", -+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", -+ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", -+ "dev": true -+ }, -+ "strip-ansi": { -+ "version": "7.0.1", -+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", -+ "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", -+ "dev": true, -+ "requires": { -+ "ansi-regex": "^6.0.1" -+ } -+ } - } - }, - "strip-ansi": { -@@ -15564,6 +17088,18 @@ - "ansi-regex": "^5.0.1" - } - }, -+ "strip-bom": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", -+ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", -+ "dev": true -+ }, -+ "strip-final-newline": { -+ "version": "2.0.0", -+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", -+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", -+ "dev": true -+ }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", -@@ -15748,11 +17284,12 @@ - } - }, - "tmp": { -- "version": "0.0.33", -- "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", -- "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", -+ "version": "0.2.1", -+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", -+ "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", -+ "dev": true, - "requires": { -- "os-tmpdir": "~1.0.2" -+ "rimraf": "^3.0.0" - } - }, - "to-arraybuffer": { -@@ -15868,9 +17405,9 @@ - } - }, - "toidentifier": { -- "version": "1.0.0", -- "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", -- "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", -+ "version": "1.0.1", -+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", -+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true - }, - "touch": { -@@ -15931,9 +17468,9 @@ - } - }, - "loader-utils": { -- "version": "2.0.0", -- "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", -- "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", -+ "version": "2.0.2", -+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", -+ "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", - "dev": true, - "requires": { - "big.js": "^5.2.2", -@@ -15968,13 +17505,12 @@ - } - }, - "ts-node": { -- "version": "10.2.1", -- "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.2.1.tgz", -- "integrity": "sha512-hCnyOyuGmD5wHleOQX6NIjJtYVIO8bPP8F2acWkB4W06wdlkgyvJtubO/I9NkI88hCFECbsEgoLc0VNkYmcSfw==", -+ "version": "10.4.0", -+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", -+ "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", - "dev": true, -- "peer": true, - "requires": { -- "@cspotcode/source-map-support": "0.6.1", -+ "@cspotcode/source-map-support": "0.7.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", -@@ -15992,15 +17528,26 @@ - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", -- "dev": true, -- "peer": true -+ "dev": true - } - } - }, -+ "tsconfig-paths": { -+ "version": "3.12.0", -+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", -+ "integrity": "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==", -+ "dev": true, -+ "requires": { -+ "@types/json5": "^0.0.29", -+ "json5": "^1.0.1", -+ "minimist": "^1.2.0", -+ "strip-bom": "^3.0.0" -+ } -+ }, - "tslib": { -- "version": "1.14.1", -- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", -- "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" -+ "version": "2.1.0", -+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", -+ "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" - }, - "tslint": { - "version": "6.1.3", -@@ -16079,6 +17626,12 @@ - "has-flag": "^3.0.0" - } - }, -+ "tslib": { -+ "version": "1.14.1", -+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", -+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", -+ "dev": true -+ }, - "tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", -@@ -16097,9 +17650,9 @@ - "dev": true - }, - "ttypescript": { -- "version": "1.5.12", -- "resolved": "https://registry.npmjs.org/ttypescript/-/ttypescript-1.5.12.tgz", -- "integrity": "sha512-1ojRyJvpnmgN9kIHmUnQPlEV1gq+VVsxVYjk/NfvMlHSmYxjK5hEvOOU2MQASrbekTUiUM7pR/nXeCc8bzvMOQ==", -+ "version": "1.5.13", -+ "resolved": "https://registry.npmjs.org/ttypescript/-/ttypescript-1.5.13.tgz", -+ "integrity": "sha512-KT/RBfGGlVJFqEI8cVvI3nMsmYcFvPSZh8bU0qX+pAwbi7/ABmYkzn7l/K8skw0xmYjVCoyaV6WLsBQxdadybQ==", - "dev": true, - "requires": { - "resolve": ">=1.9.0" -@@ -16169,9 +17722,9 @@ - } - }, - "typescript": { -- "version": "4.1.5", -- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz", -- "integrity": "sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==", -+ "version": "4.3.5", -+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", -+ "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", - "dev": true - }, - "typescript-transform-paths": { -@@ -16184,36 +17737,16 @@ - } - }, - "ua-parser-js": { -- "version": "0.7.28", -- "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz", -- "integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==", -+ "version": "0.7.31", -+ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", -+ "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", - "dev": true - }, - "undefsafe": { -- "version": "2.0.3", -- "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", -- "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", -- "dev": true, -- "requires": { -- "debug": "^2.2.0" -- }, -- "dependencies": { -- "debug": { -- "version": "2.6.9", -- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", -- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", -- "dev": true, -- "requires": { -- "ms": "2.0.0" -- } -- }, -- "ms": { -- "version": "2.0.0", -- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", -- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", -- "dev": true -- } -- } -+ "version": "2.0.5", -+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", -+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", -+ "dev": true - }, - "union-value": { - "version": "1.0.1", -@@ -16998,6 +18531,14 @@ - "requires": { - "ansi-colors": "^3.0.0", - "uuid": "^3.3.2" -+ }, -+ "dependencies": { -+ "ansi-colors": { -+ "version": "3.2.4", -+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", -+ "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", -+ "dev": true -+ } - } - }, - "webpack-sources": { -@@ -17059,38 +18600,31 @@ - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" - }, - "wide-align": { -- "version": "1.1.3", -- "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", -- "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", -+ "version": "1.1.5", -+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", -+ "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "requires": { -- "string-width": "^1.0.2 || 2" -+ "string-width": "^1.0.2 || 2 || 3 || 4" - }, - "dependencies": { -- "ansi-regex": { -- "version": "3.0.0", -- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", -- "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" -+ "emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "is-fullwidth-code-point": { -- "version": "2.0.0", -- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", -- "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "string-width": { -- "version": "2.1.1", -- "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", -- "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", -- "requires": { -- "is-fullwidth-code-point": "^2.0.0", -- "strip-ansi": "^4.0.0" -- } -- }, -- "strip-ansi": { -- "version": "4.0.0", -- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", -- "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { -- "ansi-regex": "^3.0.0" -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" - } - } - } -@@ -17102,6 +18636,31 @@ - "dev": true, - "requires": { - "string-width": "^4.0.0" -+ }, -+ "dependencies": { -+ "emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "dev": true -+ }, -+ "is-fullwidth-code-point": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", -+ "dev": true -+ }, -+ "string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "dev": true, -+ "requires": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ } -+ } - } - }, - "word-wrap": { -@@ -17128,6 +18687,31 @@ - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" -+ }, -+ "dependencies": { -+ "emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "dev": true -+ }, -+ "is-fullwidth-code-point": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", -+ "dev": true -+ }, -+ "string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "dev": true, -+ "requires": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ } -+ } - } - }, - "wrappy": { -@@ -17148,9 +18732,9 @@ - } - }, - "ws": { -- "version": "7.5.5", -- "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz", -- "integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==", -+ "version": "7.5.6", -+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", -+ "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", - "dev": true, - "requires": {} - }, -@@ -17190,6 +18774,12 @@ - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, -+ "yaml": { -+ "version": "1.10.2", -+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", -+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", -+ "dev": true -+ }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", -@@ -17205,6 +18795,29 @@ - "yargs-parser": "^20.2.2" - }, - "dependencies": { -+ "emoji-regex": { -+ "version": "8.0.0", -+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", -+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", -+ "dev": true -+ }, -+ "is-fullwidth-code-point": { -+ "version": "3.0.0", -+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", -+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", -+ "dev": true -+ }, -+ "string-width": { -+ "version": "4.2.3", -+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", -+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", -+ "dev": true, -+ "requires": { -+ "emoji-regex": "^8.0.0", -+ "is-fullwidth-code-point": "^3.0.0", -+ "strip-ansi": "^6.0.1" -+ } -+ }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", -@@ -17232,8 +18845,7 @@ - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", -- "dev": true, -- "peer": true -+ "dev": true - }, - "zone.js": { - "version": "0.11.4", -@@ -17241,13 +18853,6 @@ - "integrity": "sha512-DDh2Ab+A/B+9mJyajPjHFPWfYU1H+pdun4wnnk0OcQTNjem1XQSZ2CDW+rfZEUDjv5M19SBqAkjZi0x5wuB5Qw==", - "requires": { - "tslib": "^2.0.0" -- }, -- "dependencies": { -- "tslib": { -- "version": "2.3.1", -- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", -- "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" -- } - } - }, - "zxcvbn": { -diff --git a/jslib/package.json b/jslib/package.json -index 0ff173fa..ef9d5ccd 100644 ---- a/jslib/package.json -+++ b/jslib/package.json -@@ -16,20 +16,26 @@ - "clean": "rimraf dist/**/*", - "build": "npm run clean && ttsc", - "build:watch": "npm run clean && ttsc -watch", -- "lint": "tslint '*/src/**/*.ts' 'spec/**/*.ts'", -+ "lint": "tslint '*/src/**/*.ts' 'spec/**/*.ts' && prettier --check .", - "lint:fix": "tslint '*/src/**/*.ts' 'spec/**/*.ts' --fix", -+ "prettier": "prettier --write .", - "test": "karma start ./spec/support/karma.conf.js --single-run", - "test:watch": "karma start ./spec/support/karma.conf.js", -- "test:node": "npm run build && jasmine", -- "test:node:watch": "concurrently -k -n TSC,Node -c yellow,cyan \"npm run build:watch\" \"nodemon -w ./dist --delay 500ms --exec jasmine\"" -+ "test:node": "jasmine-ts -r tsconfig-paths/register", -+ "test:node:watch": "nodemon -w ./angular -w ./common -w ./electron -w ./node -w ./shared -w ./spec --ext \"ts,js,mjs,json\" --exec jasmine-ts -r tsconfig-paths/register", -+ "prepare": "husky install" - }, - "devDependencies": { - "@fluffy-spoon/substitute": "^1.202.0", - "@types/jasmine": "^3.7.6", -- "@types/node": "^14.17.1", -+ "@types/node": "^16.11.12", -+ "commander": "7.2.0", - "concurrently": "^6.1.0", -+ "form-data": "4.0.0", -+ "husky": "^7.0.4", - "jasmine": "^3.7.0", - "jasmine-core": "^3.7.1", -+ "jasmine-ts": "^0.4.0", - "jasmine-ts-console-reporter": "^3.1.1", - "jsdom": "^16.5.3", - "karma": "^6.3.2", -@@ -41,13 +47,18 @@ - "karma-jasmine-html-reporter": "^1.5.4", - "karma-safari-launcher": "^1.0.0", - "karma-webpack": "^4.0.2", -+ "lint-staged": "^12.1.2", - "nodemon": "^2.0.7", -+ "prettier": "2.5.1", - "rimraf": "^3.0.2", -+ "rxjs": "^7.4.0", - "ts-loader": "^8.1.0", -+ "ts-node": "^10.4.0", -+ "tsconfig-paths": "^3.12.0", - "tslint": "^6.1.3", - "ttypescript": "^1.5.12", - "typemoq": "^2.1.0", -- "typescript": "4.1.5", -+ "typescript": "4.3.5", - "typescript-transform-paths": "^2.2.3", - "webpack": "^4.46.0" - }, -@@ -58,7 +69,10 @@ - "@bitwarden/jslib-node": "file:node" - }, - "engines": { -- "node": "~14", -- "npm": "~7" -+ "node": "~16", -+ "npm": "~8" -+ }, -+ "lint-staged": { -+ "*": "prettier --ignore-unknown --write" - } - } -diff --git a/jslib/shared/tsconfig.json b/jslib/shared/tsconfig.json -new file mode 100644 -index 00000000..3a70f6b9 ---- /dev/null -+++ b/jslib/shared/tsconfig.json -@@ -0,0 +1,15 @@ -+{ -+ "compilerOptions": { -+ "pretty": true, -+ "moduleResolution": "node", -+ "noImplicitAny": true, -+ "target": "ES6", -+ "module": "commonjs", -+ "lib": ["es5", "es6", "es7", "dom"], -+ "sourceMap": true, -+ "allowSyntheticDefaultImports": true, -+ "experimentalDecorators": true, -+ "emitDecoratorMetadata": true, -+ "outDir": "dist" -+ } -+} -diff --git a/jslib/spec/common/importers/firefoxCsvImporter.spec.ts b/jslib/spec/common/importers/firefoxCsvImporter.spec.ts -index 81f31e0d..fc42c6ec 100644 ---- a/jslib/spec/common/importers/firefoxCsvImporter.spec.ts -+++ b/jslib/spec/common/importers/firefoxCsvImporter.spec.ts -@@ -1,73 +1,73 @@ --import { FirefoxCsvImporter as Importer } from 'jslib-common/importers/firefoxCsvImporter'; -+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 { 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'; -+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, -- }), -- }, -+ { -+ 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); -+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) { -- if (data.expected.hasOwnProperty(property)) { -- expect(cipher.hasOwnProperty(property)).toBe(true); -- expect(cipher[property]).toEqual(data.expected[property]); -- } -- } -- }); -+ const cipher = result.ciphers.shift(); -+ let property: keyof typeof data.expected; -+ for (property in data.expected) { -+ if (data.expected.hasOwnProperty(property)) { -+ expect(cipher.hasOwnProperty(property)).toBe(true); -+ expect(cipher[property]).toEqual(data.expected[property]); -+ } -+ } - }); -+ }); - }); -diff --git a/jslib/spec/common/importers/fsecureFskImporter.spec.ts b/jslib/spec/common/importers/fsecureFskImporter.spec.ts -new file mode 100644 -index 00000000..63d082df ---- /dev/null -+++ b/jslib/spec/common/importers/fsecureFskImporter.spec.ts -@@ -0,0 +1,77 @@ -+import { FSecureFskImporter as Importer } from "jslib-common/importers/fsecureFskImporter"; -+ -+const TestDataWithStyleSetToWebsite: string = JSON.stringify({ -+ data: { -+ "8d58b5cf252dd06fbd98f5289e918ab1": { -+ color: "#00baff", -+ reatedDate: 1609302913, -+ creditCvv: "", -+ creditExpiry: "", -+ creditNumber: "", -+ favorite: 0, -+ modifiedDate: 1609302913, -+ notes: "note", -+ password: "word", -+ passwordList: [], -+ passwordModifiedDate: 1609302913, -+ rev: 1, -+ service: "My first pass", -+ style: "website", -+ type: 1, -+ url: "https://bitwarden.com", -+ username: "pass", -+ }, -+ }, -+}); -+ -+const TestDataWithStyleSetToGlobe: string = JSON.stringify({ -+ data: { -+ "8d58b5cf252dd06fbd98f5289e918ab1": { -+ color: "#00baff", -+ reatedDate: 1609302913, -+ creditCvv: "", -+ creditExpiry: "", -+ creditNumber: "", -+ favorite: 0, -+ modifiedDate: 1609302913, -+ notes: "note", -+ password: "word", -+ passwordList: [], -+ passwordModifiedDate: 1609302913, -+ rev: 1, -+ service: "My first pass", -+ style: "globe", -+ type: 1, -+ url: "https://bitwarden.com", -+ username: "pass", -+ }, -+ }, -+}); -+ -+describe("FSecure FSK Importer", () => { -+ it("should parse data with style set to website", async () => { -+ const importer = new Importer(); -+ const result = await importer.parse(TestDataWithStyleSetToWebsite); -+ expect(result != null).toBe(true); -+ -+ const cipher = result.ciphers.shift(); -+ expect(cipher.login.username).toEqual("pass"); -+ expect(cipher.login.password).toEqual("word"); -+ expect(cipher.login.uris.length).toEqual(1); -+ const uriView = cipher.login.uris.shift(); -+ expect(uriView.uri).toEqual("https://bitwarden.com"); -+ }); -+ -+ it("should parse data with style set to globe", async () => { -+ const importer = new Importer(); -+ const result = await importer.parse(TestDataWithStyleSetToGlobe); -+ expect(result != null).toBe(true); -+ -+ const cipher = result.ciphers.shift(); -+ expect(cipher.login.username).toEqual("pass"); -+ expect(cipher.login.password).toEqual("word"); -+ expect(cipher.login.uris.length).toEqual(1); -+ const uriView = cipher.login.uris.shift(); -+ expect(uriView.uri).toEqual("https://bitwarden.com"); -+ }); -+}); -diff --git a/jslib/spec/common/importers/keepass2XmlImporter.spec.ts b/jslib/spec/common/importers/keepass2XmlImporter.spec.ts -index abeb4911..ff0f2d14 100644 ---- a/jslib/spec/common/importers/keepass2XmlImporter.spec.ts -+++ b/jslib/spec/common/importers/keepass2XmlImporter.spec.ts -@@ -1,4 +1,4 @@ --import { KeePass2XmlImporter as Importer } from 'jslib-common/importers/keepass2XmlImporter'; -+import { KeePass2XmlImporter as Importer } from "jslib-common/importers/keepass2XmlImporter"; - - const TestData: string = ` - -@@ -180,10 +180,10 @@ line2 - - `; - --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); -- }); -+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); -+ }); - }); -diff --git a/jslib/spec/common/importers/keeperJsonImporter.spec.ts b/jslib/spec/common/importers/keeperJsonImporter.spec.ts -new file mode 100644 -index 00000000..3a4f34c2 ---- /dev/null -+++ b/jslib/spec/common/importers/keeperJsonImporter.spec.ts -@@ -0,0 +1,109 @@ -+import { Utils } from "jslib-common/misc/utils"; -+ -+import { KeeperJsonImporter as Importer } from "jslib-common/importers/keeperImporters/keeperJsonImporter"; -+ -+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]); -+ }); -+}); -diff --git a/jslib/spec/common/importers/lastpassCsvImporter.spec.ts b/jslib/spec/common/importers/lastpassCsvImporter.spec.ts -index f42ea676..935db38c 100644 ---- a/jslib/spec/common/importers/lastpassCsvImporter.spec.ts -+++ b/jslib/spec/common/importers/lastpassCsvImporter.spec.ts -@@ -1,33 +1,33 @@ --import { LastPassCsvImporter as Importer } from 'jslib-common/importers/lastpassCsvImporter'; -+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'; -+import { ImportResult } from "jslib-common/models/domain/importResult"; -+import { CipherView } from "jslib-common/models/view/cipherView"; -+import { FieldView } from "jslib-common/models/view/fieldView"; - --import { CipherType } from 'jslib-common/enums/cipherType'; --import { FieldType } from 'jslib-common/enums/fieldType'; -+import { CipherType } from "jslib-common/enums/cipherType"; -+import { FieldType } from "jslib-common/enums/fieldType"; - - function baseExcept(result: ImportResult) { -- expect(result).not.toBeNull(); -- expect(result.success).toBe(true); -- expect(result.ciphers.length).toBe(1); -+ 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.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'); -+ 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 -+ { -+ title: "should parse expiration date", -+ csv: `url,username,password,extra,name,grouping,fav - http://sn,,,"NoteType:Credit Card - Name on Card:John Doe - Type: -@@ -37,32 +37,32 @@ 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, -- }), -- ], -+ 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 -+ ], -+ }), -+ }, -+ { -+ title: "should parse blank card note", -+ csv: `url,username,password,extra,name,grouping,fav - http://sn,,,"NoteType:Credit Card - Name on Card: - Type: -@@ -71,28 +71,28 @@ 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, -- }), -- ], -+ 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 -+ ], -+ }), -+ }, -+ { -+ 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 -@@ -101,36 +101,36 @@ 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, -- }), -- ], -+ 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, - }), -- }, -- { -- title: 'should parse card expiration date w/ no month', -- csv: `url,username,password,extra,name,grouping,fav -+ 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 -@@ -139,64 +139,64 @@ 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, -- }), -- ], -+ 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); -+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) { -- if (data.expected.hasOwnProperty(property)) { -- expect(cipher.hasOwnProperty(property)).toBe(true); -- expect(cipher[property]).toEqual(data.expected[property]); -- } -- } -- }); -+ const cipher = result.ciphers.shift(); -+ let property: keyof typeof data.expected; -+ for (property in data.expected) { -+ if (data.expected.hasOwnProperty(property)) { -+ 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 -+ 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 importer = new Importer(); -+ const result = await importer.parse(input); -+ baseExcept(result); - -- const cipher = result.ciphers[0]; -- expectLogin(cipher); -- }); --}); -\ No newline at end of file -+ const cipher = result.ciphers[0]; -+ expectLogin(cipher); -+ }); -+}); -diff --git a/jslib/spec/common/importers/nordpassCsvImporter.spec.ts b/jslib/spec/common/importers/nordpassCsvImporter.spec.ts -index 4e8cfcbf..7ac99bd2 100644 ---- a/jslib/spec/common/importers/nordpassCsvImporter.spec.ts -+++ b/jslib/spec/common/importers/nordpassCsvImporter.spec.ts -@@ -1,181 +1,182 @@ --import { NordPassCsvImporter as Importer } from 'jslib-common/importers/nordpassCsvImporter'; -+import { NordPassCsvImporter as Importer } from "jslib-common/importers/nordpassCsvImporter"; - --import { CipherType } from 'jslib-common/enums/cipherType'; --import { SecureNoteType } from 'jslib-common/enums/secureNoteType'; --import { CipherView } from 'jslib-common/models/view/cipherView'; --import { IdentityView } from 'jslib-common/models/view/identityView'; -+import { CipherType } from "jslib-common/enums/cipherType"; -+import { SecureNoteType } from "jslib-common/enums/secureNoteType"; -+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'; -+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', -- }), -- }, -+ { -+ 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.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'); -+ 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'); -+ 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'); -+ 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.type).toBe(CipherType.SecureNote); - -- expect(cipher.name).toBe('MySuperSecureNoteTitle'); -- expect(cipher.secureNote.type).toBe(SecureNoteType.Generic); -- expect(cipher.notes).toBe('MySuperSecureNote'); -+ 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'); -+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"); -+ }); - }); -diff --git a/jslib/spec/common/importers/onepassword1PifImporter.spec.ts b/jslib/spec/common/importers/onepassword1PifImporter.spec.ts -index 4c26a001..ffa2417a 100644 ---- a/jslib/spec/common/importers/onepassword1PifImporter.spec.ts -+++ b/jslib/spec/common/importers/onepassword1PifImporter.spec.ts -@@ -1,513 +1,527 @@ --import { FieldType } from 'jslib-common/enums/fieldType'; --import { OnePassword1PifImporter as Importer } from 'jslib-common/importers/onepasswordImporters/onepassword1PifImporter'; -+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, -- }, -- ], -+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", - }, -- URLs: [ -+ ], -+ sections: [ -+ { -+ fields: [ - { -- label: 'website', -- url: 'https://www.google.com', -+ k: "concealed", -+ n: "AAAAAAAAAAAABBBBBBBBBBBCCCCCCCCC", -+ v: "console-password-123", -+ t: "console password", - }, -- ], -- txTimestamp: 1508941334, -- createdAt: 1390426636, -- typeName: 'webforms.WebForm', -- }); -+ ], -+ 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', -- }, -+ 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", -+ }, - ], -- url: 'google.com', -- ps: 26, -- ainfo: 'googluser', -- }, -- details: { -- passwordHistory: [ -- { -- value: 'oldpass1', -- time: 1553394449, -+ }, -+ ], -+ }, -+}); -+ -+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", - }, -- { -- value: 'oldpass2', -- time: 1553394457, -+ n: "firstname", -+ v: "Frank", -+ a: { -+ guarded: "yes", - }, -- { -- value: 'oldpass3', -- time: 1553394458, -+ t: "first name", -+ }, -+ { -+ k: "string", -+ inputTraits: { -+ autocapitalization: "Words", - }, -- { -- value: 'oldpass4', -- time: 1553394459, -+ n: "initial", -+ v: "MD", -+ a: { -+ guarded: "yes", - }, -- { -- value: 'oldpass5', -- time: 1553394460, -+ t: "initial", -+ }, -+ { -+ k: "string", -+ inputTraits: { -+ autocapitalization: "Words", - }, -- { -- value: 'oldpass6', -- time: 1553394461, -+ 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: [ -- { -- type: 'T', -- id: 'username', -- name: 'username', -- value: 'googluser', -- designation: 'username', -+ { -+ k: "address", -+ inputTraits: { -+ autocapitalization: "Sentences", - }, -- { -- type: 'P', -- id: 'password', -- name: 'password', -- value: '12345678901', -- designation: 'password', -+ n: "address", -+ v: { -+ street: "Mainstreet 1", -+ city: "Berlin", -+ country: "de", -+ zip: "223344", - }, -- ], -- 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', -- }, -- ], -+ 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", -+ }, - ], -- }, --}); -- --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', -+ title: "Address", -+ name: "address", -+ }, -+ { -+ fields: [ -+ { -+ k: "string", -+ n: "username", -+ a: { -+ guarded: "yes", - }, -- { -- 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', -+ t: "username", -+ }, -+ { -+ k: "string", -+ n: "reminderq", -+ t: "reminder question", -+ }, -+ { -+ k: "string", -+ n: "remindera", -+ t: "reminder answer", -+ }, -+ { -+ k: "string", -+ inputTraits: { -+ keyboard: "EmailAddress", - }, -- { -- 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', -+ n: "email", -+ v: "test@web.de", -+ a: { -+ guarded: "yes", - }, -- { -- title: 'Related Items', -- name: 'linked items', -+ 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", -+ }, - ], -- 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', -+ 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); -+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'); -- }); -+ 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); -+ 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 ciphers = result.ciphers; -+ expect(ciphers.length).toEqual(1); - -- const cipher = ciphers.shift(); -- const fields = cipher.fields; -- expect(fields.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); -- }); -+ 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'); -+ 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'); -+ 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); -- }); -+ // 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(); -+ 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'); -- }); -+ 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(); -+ 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'); -- }); -+ 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"); -+ }); - }); -diff --git a/jslib/spec/common/importers/onepasswordMacCsvImporter.spec.ts b/jslib/spec/common/importers/onepasswordMacCsvImporter.spec.ts -index 47326db6..ed643c8d 100644 ---- a/jslib/spec/common/importers/onepasswordMacCsvImporter.spec.ts -+++ b/jslib/spec/common/importers/onepasswordMacCsvImporter.spec.ts -@@ -1,71 +1,75 @@ --import { OnePasswordMacCsvImporter as Importer } from 'jslib-common/importers/onepasswordImporters/onepasswordMacCsvImporter'; -+import { OnePasswordMacCsvImporter as Importer } from "jslib-common/importers/onepasswordImporters/onepasswordMacCsvImporter"; - --import { CipherType } from 'jslib-common/enums/cipherType'; --import { CipherView } from 'jslib-common/models/view/cipherView'; -+import { CipherType } from "jslib-common/enums/cipherType"; -+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'; -+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.type).toBe(CipherType.Identity); - -- expect(cipher.identity).toEqual(jasmine.objectContaining({ -- firstName: 'first name', -- middleName: 'mi', -- lastName: 'last name', -- username: 'userNam3', -- company: 'bitwarden', -- phone: '8005555555', -- email: 'email@bitwarden.com', -- })); -+ expect(cipher.identity).toEqual( -+ jasmine.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'); -+ expect(cipher.notes).toContain("address\ncity state zip\nUnited States"); - } - - function expectCreditCard(cipher: CipherView) { -- expect(cipher.type).toBe(CipherType.Card); -+ expect(cipher.type).toBe(CipherType.Card); - -- expect(cipher.card).toEqual(jasmine.objectContaining({ -- number: '4111111111111111', -- code: '111', -- cardholderName: 'test', -- expMonth: '1', -- expYear: '2030', -- })); -+ expect(cipher.card).toEqual( -+ jasmine.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); -+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); -- }); -+ 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); -+ 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); -- }); -+ 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); -+ 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]); -- }); -+ expect(result).not.toBeNull(); -+ expect(result.success).toBe(true); -+ expect(result.ciphers.length).toBe(4); -+ expectIdentity(result.ciphers[1]); -+ expectCreditCard(result.ciphers[2]); -+ }); - }); -diff --git a/jslib/spec/common/importers/onepasswordWinCsvImporter.spec.ts b/jslib/spec/common/importers/onepasswordWinCsvImporter.spec.ts -index 3b11d15a..a8415d73 100644 ---- a/jslib/spec/common/importers/onepasswordWinCsvImporter.spec.ts -+++ b/jslib/spec/common/importers/onepasswordWinCsvImporter.spec.ts -@@ -1,82 +1,88 @@ --import { OnePasswordWinCsvImporter as Importer } from 'jslib-common/importers/onepasswordImporters/onepasswordWinCsvImporter'; -+import { OnePasswordWinCsvImporter as Importer } from "jslib-common/importers/onepasswordImporters/onepasswordWinCsvImporter"; - --import { CipherType } from 'jslib-common/enums/cipherType'; --import { FieldType } from 'jslib-common/enums/fieldType'; --import { CipherView } from 'jslib-common/models/view/cipherView'; --import { FieldView } from 'jslib-common/models/view/fieldView'; -+import { CipherType } from "jslib-common/enums/cipherType"; -+import { FieldType } from "jslib-common/enums/fieldType"; -+import { CipherView } from "jslib-common/models/view/cipherView"; -+import { FieldView } from "jslib-common/models/view/fieldView"; - --import { data as creditCardData } from './testData/onePasswordCsv/creditCard.windows.csv'; --import { data as identityData } from './testData/onePasswordCsv/identity.windows.csv'; --import { data as multiTypeData } from './testData/onePasswordCsv/multipleItems.windows.csv'; -+import { data as creditCardData } from "./testData/onePasswordCsv/creditCard.windows.csv"; -+import { data as identityData } from "./testData/onePasswordCsv/identity.windows.csv"; -+import { data as multiTypeData } from "./testData/onePasswordCsv/multipleItems.windows.csv"; - - function expectIdentity(cipher: CipherView) { -- expect(cipher.type).toBe(CipherType.Identity); -+ expect(cipher.type).toBe(CipherType.Identity); - -- expect(cipher.identity).toEqual(jasmine.objectContaining({ -- firstName: 'first name', -- middleName: 'mi', -- lastName: 'last name', -- username: 'userNam3', -- company: 'bitwarden', -- phone: '8005555555', -- email: 'email@bitwarden.com', -- })); -+ expect(cipher.identity).toEqual( -+ jasmine.objectContaining({ -+ firstName: "first name", -+ middleName: "mi", -+ lastName: "last name", -+ username: "userNam3", -+ company: "bitwarden", -+ phone: "8005555555", -+ email: "email@bitwarden.com", -+ }) -+ ); - -- expect(cipher.fields).toEqual(jasmine.arrayContaining([ -- Object.assign(new FieldView(), { -- type: FieldType.Text, -- name: 'address', -- value: 'address city state zip us', -- }), -- ])); -+ expect(cipher.fields).toEqual( -+ jasmine.arrayContaining([ -+ Object.assign(new FieldView(), { -+ type: FieldType.Text, -+ name: "address", -+ value: "address city state zip us", -+ }), -+ ]) -+ ); - } - - function expectCreditCard(cipher: CipherView) { -- expect(cipher.type).toBe(CipherType.Card); -+ expect(cipher.type).toBe(CipherType.Card); - -- expect(cipher.card).toEqual(jasmine.objectContaining({ -- number: '4111111111111111', -- code: '111', -- cardholderName: 'test', -- expMonth: '1', -- expYear: '1970', -- })); -+ expect(cipher.card).toEqual( -+ jasmine.objectContaining({ -+ number: "4111111111111111", -+ code: "111", -+ cardholderName: "test", -+ expMonth: "1", -+ expYear: "1970", -+ }) -+ ); - } - --describe('1Password windows CSV Importer', () => { -- let importer: Importer; -- beforeEach(() => { -- importer = new Importer(); -- }); -+describe("1Password windows CSV Importer", () => { -+ let importer: Importer; -+ beforeEach(() => { -+ importer = new Importer(); -+ }); - -- it('should parse identity records', async () => { -- const result = await importer.parse(identityData); -+ it("should parse identity records", async () => { -+ const result = await importer.parse(identityData); - -- expect(result).not.toBeNull(); -- expect(result.success).toBe(true); -- expect(result.ciphers.length).toBe(1); -- const cipher = result.ciphers[0]; -- expectIdentity(cipher); -- }); -+ expect(result).not.toBeNull(); -+ expect(result.success).toBe(true); -+ expect(result.ciphers.length).toBe(1); -+ const cipher = result.ciphers[0]; -+ expectIdentity(cipher); -+ }); - -- it('should parse credit card records', async () => { -- const result = await importer.parse(creditCardData); -+ 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); -- }); -+ expect(result).not.toBeNull(); -+ expect(result.success).toBe(true); -+ expect(result.ciphers.length).toBe(1); -+ const cipher = result.ciphers[0]; -+ expectCreditCard(cipher); -+ }); - -- it('should parse csv\'s with multiple record types', async () => { -- const result = await importer.parse(multiTypeData); -+ it("should parse csv's with multiple record types", async () => { -+ const result = await importer.parse(multiTypeData); - -- expect(result).not.toBeNull(); -- expect(result.success).toBe(true); -- expect(result.ciphers.length).toBe(4); -+ expect(result).not.toBeNull(); -+ expect(result.success).toBe(true); -+ expect(result.ciphers.length).toBe(4); - -- expectIdentity(result.ciphers[1]); -- expectCreditCard(result.ciphers[2]); -- }); -+ expectIdentity(result.ciphers[1]); -+ expectCreditCard(result.ciphers[2]); -+ }); - }); -diff --git a/jslib/spec/common/importers/testData/keeperJson/testData.ts b/jslib/spec/common/importers/testData/keeperJson/testData.ts -new file mode 100644 -index 00000000..fee8f54c ---- /dev/null -+++ b/jslib/spec/common/importers/testData/keeperJson/testData.ts -@@ -0,0 +1,90 @@ -+import { KeeperJsonExport } from "jslib-common/importers/keeperImporters/types/keeperJsonTypes"; -+ -+export const testData: KeeperJsonExport = { -+ shared_folders: [ -+ { -+ path: "My Customer 1", -+ manage_users: true, -+ manage_records: true, -+ can_edit: true, -+ can_share: true, -+ permissions: [ -+ { -+ uid: "kVM96KGEoGxhskZoSTd_jw", -+ manage_users: true, -+ manage_records: true, -+ }, -+ { -+ name: "user@mycompany.com", -+ manage_users: true, -+ manage_records: true, -+ }, -+ ], -+ }, -+ { -+ path: "Testing\\My Customer 2", -+ manage_users: true, -+ manage_records: true, -+ can_edit: true, -+ can_share: true, -+ permissions: [ -+ { -+ uid: "ih1CggiQ-3ENXcn4G0sl-g", -+ manage_users: true, -+ manage_records: true, -+ }, -+ { -+ name: "user@mycompany.com", -+ manage_users: true, -+ manage_records: true, -+ }, -+ ], -+ }, -+ ], -+ records: [ -+ { -+ title: "Bank Account 1", -+ login: "customer1234", -+ password: "4813fJDHF4239fdk", -+ login_url: "https://chase.com", -+ notes: "These are some notes.", -+ custom_fields: { -+ "Account Number": "123-456-789", -+ }, -+ folders: [ -+ { -+ folder: "Optional Private Folder 1", -+ }, -+ ], -+ }, -+ { -+ title: "Bank Account 2", -+ login: "mybankusername", -+ password: "w4k4k193f$^&@#*%2", -+ login_url: "https://amex.com", -+ notes: "Some great information here.", -+ custom_fields: { -+ "Security Group": "Public", -+ "IP Address": "12.45.67.8", -+ "TFC:Keeper": -+ "otpauth://totp/Amazon:me@company.com?secret=JBSWY3DPEHPK3PXP&issuer=Amazon&algorithm=SHA1&digits=6&period=30", -+ }, -+ folders: [ -+ { -+ folder: "Optional Private Folder 1", -+ }, -+ { -+ shared_folder: "My Customer 1", -+ can_edit: true, -+ can_share: true, -+ }, -+ ], -+ }, -+ { -+ title: "Some Account", -+ login: "someUserName", -+ password: "w4k4k1wergf$^&@#*%2", -+ login_url: "https://example.com", -+ }, -+ ], -+}; -diff --git a/jslib/spec/common/importers/testData/nordpassCsv/nordpass.secureNote.csv.ts b/jslib/spec/common/importers/testData/nordpassCsv/nordpass.secureNote.csv.ts -index 7d8c2307..0e4dcb2e 100644 ---- a/jslib/spec/common/importers/testData/nordpassCsv/nordpass.secureNote.csv.ts -+++ b/jslib/spec/common/importers/testData/nordpassCsv/nordpass.secureNote.csv.ts -@@ -1,3 +1,3 @@ - export const data = `name,url,username,password,note,cardholdername,cardnumber,cvc,expirydate,zipcode,folder,full_name,phone_number,email,address1,address2,city,country,state - notesFolder,,,,,,,,,,,,,,,,,, --MySuperSecureNoteTitle,,,,MySuperSecureNote,,,,,,notesFolder,,,,,,,,`; -\ No newline at end of file -+MySuperSecureNoteTitle,,,,MySuperSecureNote,,,,,,notesFolder,,,,,,,,`; -diff --git a/jslib/spec/common/importers/testData/onePasswordCsv/creditCard.windows.csv.ts b/jslib/spec/common/importers/testData/onePasswordCsv/creditCard.windows.csv.ts -index f9dd9808..927cabec 100644 ---- a/jslib/spec/common/importers/testData/onePasswordCsv/creditCard.windows.csv.ts -+++ b/jslib/spec/common/importers/testData/onePasswordCsv/creditCard.windows.csv.ts -@@ -1,2 +1,2 @@ --export const data = `"UUID","TITLE","SCOPE","AUTOSUBMIT","1: CARDHOLDER NAME","2: NUMBER","3: VERIFICATION NUMBER","4: EXPIRY DATE","SECTION 2: SECTION_PZET7LEKRQXZUINIEGH5ABA2UY","SECTION_PZET7LEKRQXZUINIEGH5ABA2UY 1: LABEL" --"sd26pt226etnsijbl3kqzi5bmm","test card","Default","Default","test","4111111111111111","111","1/3/1970 12:23 AM","section","field (phone)"`; -+export const data = `"UUID","TITLE","SCOPE","AUTOSUBMIT","1: CARDHOLDER NAME","2: NUMBER","3: VERIFICATION NUMBER","4: EXPIRY DATE","SECTION 2: SECTION_PZET7LEKRQXZUINIEGH5ABA2UY","SECTION_PZET7LEKRQXZUINIEGH5ABA2UY 1: LABEL" -+"sd26pt226etnsijbl3kqzi5bmm","test card","Default","Default","test","4111111111111111","111","1/3/1970 12:23 AM","section","field (phone)"`; -diff --git a/jslib/spec/common/importers/testData/onePasswordCsv/identity.windows.csv.ts b/jslib/spec/common/importers/testData/onePasswordCsv/identity.windows.csv.ts -index 3e198e17..d936b42b 100644 ---- a/jslib/spec/common/importers/testData/onePasswordCsv/identity.windows.csv.ts -+++ b/jslib/spec/common/importers/testData/onePasswordCsv/identity.windows.csv.ts -@@ -1,2 +1,2 @@ --export const data = `"UUID","TITLE","SCOPE","AUTOSUBMIT","TAGS","NOTES","SECTION 1: NAME","NAME 1: FIRST NAME","NAME 2: INITIAL","NAME 3: LAST NAME","NAME 4: BIRTH DATE","NAME 5: OCCUPATION","NAME 6: COMPANY","NAME 7: DEPARTMENT","NAME 8: JOB TITLE","SECTION 2: ADDRESS","ADDRESS 1: ADDRESS","ADDRESS 2: DEFAULT PHONE","SECTION 3: INTERNET","INTERNET 1: USERNAME","INTERNET 2: EMAIL","SECTION 4: MFJQKMWEOYDZDFH4YMR7WLJKIY","MFJQKMWEOYDZDFH4YMR7WLJKIY 1: SECTION FIELD","MFJQKMWEOYDZDFH4YMR7WLJKIY 2: SECTION FIELD" --"6v56y5z4tejwg37jsettta7d7m","Identity Item","Default","Default","Starter Kit","It’s you! 🖐 Select Edit to fill in more details, like your address and contact information.","Identification","first name","mi","last name","12/2/2020 4:01 AM","occupation","bitwarden","department","job title","Address","address city state zip us","8005555555","Internet Details","userNam3","email@bitwarden.com","💡 Did you know?","1Password can fill names and addresses into webpages:","https://support.1password.com/credit-card-address-filling/"`; -+export const data = `"UUID","TITLE","SCOPE","AUTOSUBMIT","TAGS","NOTES","SECTION 1: NAME","NAME 1: FIRST NAME","NAME 2: INITIAL","NAME 3: LAST NAME","NAME 4: BIRTH DATE","NAME 5: OCCUPATION","NAME 6: COMPANY","NAME 7: DEPARTMENT","NAME 8: JOB TITLE","SECTION 2: ADDRESS","ADDRESS 1: ADDRESS","ADDRESS 2: DEFAULT PHONE","SECTION 3: INTERNET","INTERNET 1: USERNAME","INTERNET 2: EMAIL","SECTION 4: MFJQKMWEOYDZDFH4YMR7WLJKIY","MFJQKMWEOYDZDFH4YMR7WLJKIY 1: SECTION FIELD","MFJQKMWEOYDZDFH4YMR7WLJKIY 2: SECTION FIELD" -+"6v56y5z4tejwg37jsettta7d7m","Identity Item","Default","Default","Starter Kit","It’s you! 🖐 Select Edit to fill in more details, like your address and contact information.","Identification","first name","mi","last name","12/2/2020 4:01 AM","occupation","bitwarden","department","job title","Address","address city state zip us","8005555555","Internet Details","userNam3","email@bitwarden.com","💡 Did you know?","1Password can fill names and addresses into webpages:","https://support.1password.com/credit-card-address-filling/"`; -diff --git a/jslib/spec/common/importers/testData/onePasswordCsv/multipleItems.windows.csv.ts b/jslib/spec/common/importers/testData/onePasswordCsv/multipleItems.windows.csv.ts -index 918b7a52..08733e7c 100644 ---- a/jslib/spec/common/importers/testData/onePasswordCsv/multipleItems.windows.csv.ts -+++ b/jslib/spec/common/importers/testData/onePasswordCsv/multipleItems.windows.csv.ts -@@ -1,5 +1,5 @@ --export const data = `"UUID","TITLE","USERNAME","PASSWORD","URL","URLS","EMAIL","MASTER-PASSWORD","ACCOUNT-KEY","SCOPE","AUTOSUBMIT","TAGS","NOTES","SECTION 1: WXHDKEQREE3TH6QRFCPFPSD3AE","WXHDKEQREE3TH6QRFCPFPSD3AE 1: SECRET KEY","SECTION 1: NAME","NAME 1: FIRST NAME","NAME 2: INITIAL","NAME 3: LAST NAME","NAME 4: BIRTH DATE","NAME 5: OCCUPATION","NAME 6: COMPANY","NAME 7: DEPARTMENT","NAME 8: JOB TITLE","SECTION 2: ADDRESS","ADDRESS 1: ADDRESS","ADDRESS 2: DEFAULT PHONE","SECTION 3: INTERNET","INTERNET 1: USERNAME","INTERNET 2: EMAIL","SECTION 4: MFJQKMWEOYDZDFH4YMR7WLJKIY","MFJQKMWEOYDZDFH4YMR7WLJKIY 1: SECTION FIELD","MFJQKMWEOYDZDFH4YMR7WLJKIY 2: SECTION FIELD","1: CARDHOLDER NAME","2: NUMBER","3: VERIFICATION NUMBER","4: EXPIRY DATE","SECTION 2: SECTION_PZET7LEKRQXZUINIEGH5ABA2UY","SECTION_PZET7LEKRQXZUINIEGH5ABA2UY 1: LABEL","SECTION 1: 4PQVXPR4BMOPGC3DBMTP5U4OFY","4PQVXPR4BMOPGC3DBMTP5U4OFY 1: SECTION FIELD","4PQVXPR4BMOPGC3DBMTP5U4OFY 2: SECTION FIELD","SECTION 2: M2NTUZZBFOFTPAYXVXE6EMZ5JU","M2NTUZZBFOFTPAYXVXE6EMZ5JU 1: SECTION FIELD","M2NTUZZBFOFTPAYXVXE6EMZ5JU 2: SECTION FIELD","SECTION 3: WC3KPAWH6ZAEQB2ARJB6WYZ3DQ","WC3KPAWH6ZAEQB2ARJB6WYZ3DQ 1: SECTION FIELD","WC3KPAWH6ZAEQB2ARJB6WYZ3DQ 2: SECTION FIELD","WC3KPAWH6ZAEQB2ARJB6WYZ3DQ 3: SECTION FIELD","SECTION 4: TOHUYJEJEMGMI6GEQAZ2LJODFE","TOHUYJEJEMGMI6GEQAZ2LJODFE 1: SECTION FIELD","TOHUYJEJEMGMI6GEQAZ2LJODFE 2: SECTION FIELD","SECTION 5: O26UWJJTXRAANG3ONYYOUUJHDM","O26UWJJTXRAANG3ONYYOUUJHDM 1: SECTION FIELD","O26UWJJTXRAANG3ONYYOUUJHDM 2: WATCH VIDEOS","O26UWJJTXRAANG3ONYYOUUJHDM 3: GET SUPPORT","O26UWJJTXRAANG3ONYYOUUJHDM 4: READ THE BLOG","O26UWJJTXRAANG3ONYYOUUJHDM 5: CONTACT US" --"xjq32axcswefpcxu2mtxxqnufa","1Password Account","email@bitwarden.com","the account's password","https://my.1password.com","https://my.1password.com","email@bitwarden.com","the account's password","A3-76TR2N-NJG3TZ-9NXFX-WT8GF-6YQC9-R2659","Default","Default","Starter Kit","You can use this login to sign in to your account on 1password.com.","🔑 Secret Key","the account's secret key","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","" --"6v56y5z4tejwg37jsettta7d7m","Identity Item","","","","","","","","Default","Default","Starter Kit","It’s you! 🖐 Select Edit to fill in more details, like your address and contact information.","","","Identification","first name","mi","last name","12/2/2020 4:01 AM","occupation","bitwarden","department","job title","Address","address city state zip us","8005555555","Internet Details","userNam3","email@bitwarden.com","💡 Did you know?","1Password can fill names and addresses into webpages:","https://support.1password.com/credit-card-address-filling/","","","","","","","","","","","","","","","","","","","","","","","","","" --"sd26pt226etnsijbl3kqzi5bmm","test card","","","","","","","","Default","Default","","","","","","","","","","","","","","","","","","","","","","","test","4111111111111111","111","1/3/1970 12:23 AM","section","field (phone)","","","","","","","","","","","","","","","","","","","" --"oml2sgit3yk7737kxdis65o4xq","🎉 Welcome to 1Password!","","","","","","","","Default","Default","Starter Kit","Follow these steps to get started.","","","","","","","","","","","","","","","","","","","","","","","","","","","1️⃣ Get the apps","https://1password.com/downloads","Install 1Password everywhere you need your passwords.","2️⃣ Get 1Password in your browser","https://1password.com/downloads/#browsers","Install 1Password in your browser to save and fill passwords.","3️⃣ Save your first password","1. Sign in to your favorite website.","2. 1Password will ask to save your username and password.","3. Click Save Login.","4️⃣ Fill passwords and more","https://support.1password.com/explore/extension/","Save and fill passwords, credit cards, and addresses.","📚 Learn 1Password","Check out our videos and articles:","https://youtube.com/1PasswordVideos","https://support.1password.com/","https://blog.1password.com/","https://support.1password.com/contact-us/"`; -+export const data = `"UUID","TITLE","USERNAME","PASSWORD","URL","URLS","EMAIL","MASTER-PASSWORD","ACCOUNT-KEY","SCOPE","AUTOSUBMIT","TAGS","NOTES","SECTION 1: WXHDKEQREE3TH6QRFCPFPSD3AE","WXHDKEQREE3TH6QRFCPFPSD3AE 1: SECRET KEY","SECTION 1: NAME","NAME 1: FIRST NAME","NAME 2: INITIAL","NAME 3: LAST NAME","NAME 4: BIRTH DATE","NAME 5: OCCUPATION","NAME 6: COMPANY","NAME 7: DEPARTMENT","NAME 8: JOB TITLE","SECTION 2: ADDRESS","ADDRESS 1: ADDRESS","ADDRESS 2: DEFAULT PHONE","SECTION 3: INTERNET","INTERNET 1: USERNAME","INTERNET 2: EMAIL","SECTION 4: MFJQKMWEOYDZDFH4YMR7WLJKIY","MFJQKMWEOYDZDFH4YMR7WLJKIY 1: SECTION FIELD","MFJQKMWEOYDZDFH4YMR7WLJKIY 2: SECTION FIELD","1: CARDHOLDER NAME","2: NUMBER","3: VERIFICATION NUMBER","4: EXPIRY DATE","SECTION 2: SECTION_PZET7LEKRQXZUINIEGH5ABA2UY","SECTION_PZET7LEKRQXZUINIEGH5ABA2UY 1: LABEL","SECTION 1: 4PQVXPR4BMOPGC3DBMTP5U4OFY","4PQVXPR4BMOPGC3DBMTP5U4OFY 1: SECTION FIELD","4PQVXPR4BMOPGC3DBMTP5U4OFY 2: SECTION FIELD","SECTION 2: M2NTUZZBFOFTPAYXVXE6EMZ5JU","M2NTUZZBFOFTPAYXVXE6EMZ5JU 1: SECTION FIELD","M2NTUZZBFOFTPAYXVXE6EMZ5JU 2: SECTION FIELD","SECTION 3: WC3KPAWH6ZAEQB2ARJB6WYZ3DQ","WC3KPAWH6ZAEQB2ARJB6WYZ3DQ 1: SECTION FIELD","WC3KPAWH6ZAEQB2ARJB6WYZ3DQ 2: SECTION FIELD","WC3KPAWH6ZAEQB2ARJB6WYZ3DQ 3: SECTION FIELD","SECTION 4: TOHUYJEJEMGMI6GEQAZ2LJODFE","TOHUYJEJEMGMI6GEQAZ2LJODFE 1: SECTION FIELD","TOHUYJEJEMGMI6GEQAZ2LJODFE 2: SECTION FIELD","SECTION 5: O26UWJJTXRAANG3ONYYOUUJHDM","O26UWJJTXRAANG3ONYYOUUJHDM 1: SECTION FIELD","O26UWJJTXRAANG3ONYYOUUJHDM 2: WATCH VIDEOS","O26UWJJTXRAANG3ONYYOUUJHDM 3: GET SUPPORT","O26UWJJTXRAANG3ONYYOUUJHDM 4: READ THE BLOG","O26UWJJTXRAANG3ONYYOUUJHDM 5: CONTACT US" -+"xjq32axcswefpcxu2mtxxqnufa","1Password Account","email@bitwarden.com","the account's password","https://my.1password.com","https://my.1password.com","email@bitwarden.com","the account's password","A3-76TR2N-NJG3TZ-9NXFX-WT8GF-6YQC9-R2659","Default","Default","Starter Kit","You can use this login to sign in to your account on 1password.com.","🔑 Secret Key","the account's secret key","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","" -+"6v56y5z4tejwg37jsettta7d7m","Identity Item","","","","","","","","Default","Default","Starter Kit","It’s you! 🖐 Select Edit to fill in more details, like your address and contact information.","","","Identification","first name","mi","last name","12/2/2020 4:01 AM","occupation","bitwarden","department","job title","Address","address city state zip us","8005555555","Internet Details","userNam3","email@bitwarden.com","💡 Did you know?","1Password can fill names and addresses into webpages:","https://support.1password.com/credit-card-address-filling/","","","","","","","","","","","","","","","","","","","","","","","","","" -+"sd26pt226etnsijbl3kqzi5bmm","test card","","","","","","","","Default","Default","","","","","","","","","","","","","","","","","","","","","","","test","4111111111111111","111","1/3/1970 12:23 AM","section","field (phone)","","","","","","","","","","","","","","","","","","","" -+"oml2sgit3yk7737kxdis65o4xq","🎉 Welcome to 1Password!","","","","","","","","Default","Default","Starter Kit","Follow these steps to get started.","","","","","","","","","","","","","","","","","","","","","","","","","","","1️⃣ Get the apps","https://1password.com/downloads","Install 1Password everywhere you need your passwords.","2️⃣ Get 1Password in your browser","https://1password.com/downloads/#browsers","Install 1Password in your browser to save and fill passwords.","3️⃣ Save your first password","1. Sign in to your favorite website.","2. 1Password will ask to save your username and password.","3. Click Save Login.","4️⃣ Fill passwords and more","https://support.1password.com/explore/extension/","Save and fill passwords, credit cards, and addresses.","📚 Learn 1Password","Check out our videos and articles:","https://youtube.com/1PasswordVideos","https://support.1password.com/","https://blog.1password.com/","https://support.1password.com/contact-us/"`; -diff --git a/jslib/spec/common/importers/testData/safeInCloud/testData.xml.ts b/jslib/spec/common/importers/testData/safeInCloud/testData.xml.ts -index 5f11612d..4c3d7b77 100644 ---- a/jslib/spec/common/importers/testData/safeInCloud/testData.xml.ts -+++ b/jslib/spec/common/importers/testData/safeInCloud/testData.xml.ts -@@ -59,4 +59,4 @@ export const data = ` - 3 - - --`; -\ No newline at end of file -+`; -diff --git a/jslib/spec/common/misc/sequentialize.spec.ts b/jslib/spec/common/misc/sequentialize.spec.ts -index e347a786..4165eca2 100644 ---- a/jslib/spec/common/misc/sequentialize.spec.ts -+++ b/jslib/spec/common/misc/sequentialize.spec.ts -@@ -1,141 +1,127 @@ --import { sequentialize } from 'jslib-common/misc/sequentialize'; -- --describe('sequentialize decorator', () => { -- it('should call the function once', async () => { -- const foo = new Foo(); -- const promises = []; -- for (let i = 0; i < 10; i++) { -- promises.push(foo.bar(1)); -- } -- await Promise.all(promises); -- -- expect(foo.calls).toBe(1); -- }); -- -- it('should call the function once for each instance of the object', async () => { -- const foo = new Foo(); -- const foo2 = new Foo(); -- const promises = []; -- for (let i = 0; i < 10; i++) { -- promises.push(foo.bar(1)); -- promises.push(foo2.bar(1)); -- } -- await Promise.all(promises); -- -- expect(foo.calls).toBe(1); -- expect(foo2.calls).toBe(1); -- }); -- -- it('should call the function once with key function', async () => { -- const foo = new Foo(); -- const promises = []; -- for (let i = 0; i < 10; i++) { -- promises.push(foo.baz(1)); -- } -- await Promise.all(promises); -- -- expect(foo.calls).toBe(1); -- }); -- -- it('should call the function again when already resolved', async () => { -- const foo = new Foo(); -- await foo.bar(1); -- expect(foo.calls).toBe(1); -- await foo.bar(1); -- expect(foo.calls).toBe(2); -- }); -- -- it('should call the function again when already resolved with a key function', async () => { -- const foo = new Foo(); -- await foo.baz(1); -- expect(foo.calls).toBe(1); -- await foo.baz(1); -- expect(foo.calls).toBe(2); -- }); -- -- it('should call the function for each argument', async () => { -- const foo = new Foo(); -- await Promise.all([ -- foo.bar(1), -- foo.bar(1), -- foo.bar(2), -- foo.bar(2), -- foo.bar(3), -- foo.bar(3), -- ]); -- expect(foo.calls).toBe(3); -- }); -- -- it('should call the function for each argument with key function', async () => { -- const foo = new Foo(); -- await Promise.all([ -- foo.baz(1), -- foo.baz(1), -- foo.baz(2), -- foo.baz(2), -- foo.baz(3), -- foo.baz(3), -- ]); -- expect(foo.calls).toBe(3); -- }); -+import { sequentialize } from "jslib-common/misc/sequentialize"; -+ -+describe("sequentialize decorator", () => { -+ it("should call the function once", async () => { -+ const foo = new Foo(); -+ const promises = []; -+ for (let i = 0; i < 10; i++) { -+ promises.push(foo.bar(1)); -+ } -+ await Promise.all(promises); -+ -+ expect(foo.calls).toBe(1); -+ }); -+ -+ it("should call the function once for each instance of the object", async () => { -+ const foo = new Foo(); -+ const foo2 = new Foo(); -+ const promises = []; -+ for (let i = 0; i < 10; i++) { -+ promises.push(foo.bar(1)); -+ promises.push(foo2.bar(1)); -+ } -+ await Promise.all(promises); - -- it('should return correct result for each call', async () => { -- const foo = new Foo(); -- const allRes: number[] = []; -- -- await Promise.all([ -- foo.bar(1).then(res => allRes.push(res)), -- foo.bar(1).then(res => allRes.push(res)), -- foo.bar(2).then(res => allRes.push(res)), -- foo.bar(2).then(res => allRes.push(res)), -- foo.bar(3).then(res => allRes.push(res)), -- foo.bar(3).then(res => allRes.push(res)), -- ]); -- expect(foo.calls).toBe(3); -- expect(allRes.length).toBe(6); -- allRes.sort(); -- expect(allRes).toEqual([2, 2, 4, 4, 6, 6]); -- }); -+ expect(foo.calls).toBe(1); -+ expect(foo2.calls).toBe(1); -+ }); - -- it('should return correct result for each call with key function', async () => { -- const foo = new Foo(); -- const allRes: number[] = []; -- -- await Promise.all([ -- foo.baz(1).then(res => allRes.push(res)), -- foo.baz(1).then(res => allRes.push(res)), -- foo.baz(2).then(res => allRes.push(res)), -- foo.baz(2).then(res => allRes.push(res)), -- foo.baz(3).then(res => allRes.push(res)), -- foo.baz(3).then(res => allRes.push(res)), -- ]); -- expect(foo.calls).toBe(3); -- expect(allRes.length).toBe(6); -- allRes.sort(); -- expect(allRes).toEqual([3, 3, 6, 6, 9, 9]); -- }); -+ it("should call the function once with key function", async () => { -+ const foo = new Foo(); -+ const promises = []; -+ for (let i = 0; i < 10; i++) { -+ promises.push(foo.baz(1)); -+ } -+ await Promise.all(promises); -+ -+ expect(foo.calls).toBe(1); -+ }); -+ -+ it("should call the function again when already resolved", async () => { -+ const foo = new Foo(); -+ await foo.bar(1); -+ expect(foo.calls).toBe(1); -+ await foo.bar(1); -+ expect(foo.calls).toBe(2); -+ }); -+ -+ it("should call the function again when already resolved with a key function", async () => { -+ const foo = new Foo(); -+ await foo.baz(1); -+ expect(foo.calls).toBe(1); -+ await foo.baz(1); -+ expect(foo.calls).toBe(2); -+ }); -+ -+ it("should call the function for each argument", async () => { -+ const foo = new Foo(); -+ await Promise.all([foo.bar(1), foo.bar(1), foo.bar(2), foo.bar(2), foo.bar(3), foo.bar(3)]); -+ expect(foo.calls).toBe(3); -+ }); -+ -+ it("should call the function for each argument with key function", async () => { -+ const foo = new Foo(); -+ await Promise.all([foo.baz(1), foo.baz(1), foo.baz(2), foo.baz(2), foo.baz(3), foo.baz(3)]); -+ expect(foo.calls).toBe(3); -+ }); -+ -+ it("should return correct result for each call", async () => { -+ const foo = new Foo(); -+ const allRes: number[] = []; -+ -+ await Promise.all([ -+ foo.bar(1).then((res) => allRes.push(res)), -+ foo.bar(1).then((res) => allRes.push(res)), -+ foo.bar(2).then((res) => allRes.push(res)), -+ foo.bar(2).then((res) => allRes.push(res)), -+ foo.bar(3).then((res) => allRes.push(res)), -+ foo.bar(3).then((res) => allRes.push(res)), -+ ]); -+ expect(foo.calls).toBe(3); -+ expect(allRes.length).toBe(6); -+ allRes.sort(); -+ expect(allRes).toEqual([2, 2, 4, 4, 6, 6]); -+ }); -+ -+ it("should return correct result for each call with key function", async () => { -+ const foo = new Foo(); -+ const allRes: number[] = []; -+ -+ await Promise.all([ -+ foo.baz(1).then((res) => allRes.push(res)), -+ foo.baz(1).then((res) => allRes.push(res)), -+ foo.baz(2).then((res) => allRes.push(res)), -+ foo.baz(2).then((res) => allRes.push(res)), -+ foo.baz(3).then((res) => allRes.push(res)), -+ foo.baz(3).then((res) => allRes.push(res)), -+ ]); -+ expect(foo.calls).toBe(3); -+ expect(allRes.length).toBe(6); -+ allRes.sort(); -+ expect(allRes).toEqual([3, 3, 6, 6, 9, 9]); -+ }); - }); - - class Foo { -- calls = 0; -- -- @sequentialize(args => 'bar' + args[0]) -- bar(a: number): Promise { -- this.calls++; -- return new Promise(res => { -- setTimeout(() => { -- res(a * 2); -- }, Math.random() * 100); -- }); -- } -- -- @sequentialize(args => 'baz' + args[0]) -- baz(a: number): Promise { -- this.calls++; -- return new Promise(res => { -- setTimeout(() => { -- res(a * 3); -- }, Math.random() * 100); -- }); -- } -+ calls = 0; -+ -+ @sequentialize((args) => "bar" + args[0]) -+ bar(a: number): Promise { -+ this.calls++; -+ return new Promise((res) => { -+ setTimeout(() => { -+ res(a * 2); -+ }, Math.random() * 100); -+ }); -+ } -+ -+ @sequentialize((args) => "baz" + args[0]) -+ baz(a: number): Promise { -+ this.calls++; -+ return new Promise((res) => { -+ setTimeout(() => { -+ res(a * 3); -+ }, Math.random() * 100); -+ }); -+ } - } -diff --git a/jslib/spec/common/misc/throttle.spec.ts b/jslib/spec/common/misc/throttle.spec.ts -index c61e2aef..5a8cd27e 100644 ---- a/jslib/spec/common/misc/throttle.spec.ts -+++ b/jslib/spec/common/misc/throttle.spec.ts -@@ -1,110 +1,110 @@ --import { sequentialize } from 'jslib-common/misc/sequentialize'; --import { throttle } from 'jslib-common/misc/throttle'; -+import { sequentialize } from "jslib-common/misc/sequentialize"; -+import { throttle } from "jslib-common/misc/throttle"; - --describe('throttle decorator', () => { -- it('should call the function once at a time', async () => { -- const foo = new Foo(); -- const promises = []; -- for (let i = 0; i < 10; i++) { -- promises.push(foo.bar(1)); -- } -- await Promise.all(promises); -+describe("throttle decorator", () => { -+ it("should call the function once at a time", async () => { -+ const foo = new Foo(); -+ const promises = []; -+ for (let i = 0; i < 10; i++) { -+ promises.push(foo.bar(1)); -+ } -+ await Promise.all(promises); - -- expect(foo.calls).toBe(10); -- }); -+ expect(foo.calls).toBe(10); -+ }); - -- it('should call the function once at a time for each object', async () => { -- const foo = new Foo(); -- const foo2 = new Foo(); -- const promises = []; -- for (let i = 0; i < 10; i++) { -- promises.push(foo.bar(1)); -- promises.push(foo2.bar(1)); -- } -- await Promise.all(promises); -+ it("should call the function once at a time for each object", async () => { -+ const foo = new Foo(); -+ const foo2 = new Foo(); -+ const promises = []; -+ for (let i = 0; i < 10; i++) { -+ promises.push(foo.bar(1)); -+ promises.push(foo2.bar(1)); -+ } -+ await Promise.all(promises); - -- expect(foo.calls).toBe(10); -- expect(foo2.calls).toBe(10); -- }); -+ expect(foo.calls).toBe(10); -+ expect(foo2.calls).toBe(10); -+ }); - -- it('should call the function limit at a time', async () => { -- const foo = new Foo(); -- const promises = []; -- for (let i = 0; i < 10; i++) { -- promises.push(foo.baz(1)); -- } -- await Promise.all(promises); -+ it("should call the function limit at a time", async () => { -+ const foo = new Foo(); -+ const promises = []; -+ for (let i = 0; i < 10; i++) { -+ promises.push(foo.baz(1)); -+ } -+ await Promise.all(promises); - -- expect(foo.calls).toBe(10); -- }); -+ expect(foo.calls).toBe(10); -+ }); - -- it('should call the function limit at a time for each object', async () => { -- const foo = new Foo(); -- const foo2 = new Foo(); -- const promises = []; -- for (let i = 0; i < 10; i++) { -- promises.push(foo.baz(1)); -- promises.push(foo2.baz(1)); -- } -- await Promise.all(promises); -+ it("should call the function limit at a time for each object", async () => { -+ const foo = new Foo(); -+ const foo2 = new Foo(); -+ const promises = []; -+ for (let i = 0; i < 10; i++) { -+ promises.push(foo.baz(1)); -+ promises.push(foo2.baz(1)); -+ } -+ await Promise.all(promises); - -- expect(foo.calls).toBe(10); -- expect(foo2.calls).toBe(10); -- }); -+ expect(foo.calls).toBe(10); -+ expect(foo2.calls).toBe(10); -+ }); - -- it('should work together with sequentialize', async () => { -- const foo = new Foo(); -- const promises = []; -- for (let i = 0; i < 10; i++) { -- promises.push(foo.qux(Math.floor(i / 2) * 2)); -- } -- await Promise.all(promises); -+ it("should work together with sequentialize", async () => { -+ const foo = new Foo(); -+ const promises = []; -+ for (let i = 0; i < 10; i++) { -+ promises.push(foo.qux(Math.floor(i / 2) * 2)); -+ } -+ await Promise.all(promises); - -- expect(foo.calls).toBe(5); -- }); -+ expect(foo.calls).toBe(5); -+ }); - }); - - class Foo { -- calls = 0; -- inflight = 0; -+ calls = 0; -+ inflight = 0; - -- @throttle(1, () => 'bar') -- bar(a: number) { -- this.calls++; -- this.inflight++; -- return new Promise(res => { -- setTimeout(() => { -- expect(this.inflight).toBe(1); -- this.inflight--; -- res(a * 2); -- }, Math.random() * 10); -- }); -- } -+ @throttle(1, () => "bar") -+ bar(a: number) { -+ this.calls++; -+ this.inflight++; -+ return new Promise((res) => { -+ setTimeout(() => { -+ expect(this.inflight).toBe(1); -+ this.inflight--; -+ res(a * 2); -+ }, Math.random() * 10); -+ }); -+ } - -- @throttle(5, () => 'baz') -- baz(a: number) { -- this.calls++; -- this.inflight++; -- return new Promise(res => { -- setTimeout(() => { -- expect(this.inflight).toBeLessThanOrEqual(5); -- this.inflight--; -- res(a * 3); -- }, Math.random() * 10); -- }); -- } -+ @throttle(5, () => "baz") -+ baz(a: number) { -+ this.calls++; -+ this.inflight++; -+ return new Promise((res) => { -+ setTimeout(() => { -+ expect(this.inflight).toBeLessThanOrEqual(5); -+ this.inflight--; -+ res(a * 3); -+ }, Math.random() * 10); -+ }); -+ } - -- @sequentialize(args => 'qux' + args[0]) -- @throttle(1, () => 'qux') -- qux(a: number) { -- this.calls++; -- this.inflight++; -- return new Promise(res => { -- setTimeout(() => { -- expect(this.inflight).toBe(1); -- this.inflight--; -- res(a * 3); -- }, Math.random() * 10); -- }); -- } -+ @sequentialize((args) => "qux" + args[0]) -+ @throttle(1, () => "qux") -+ qux(a: number) { -+ this.calls++; -+ this.inflight++; -+ return new Promise((res) => { -+ setTimeout(() => { -+ expect(this.inflight).toBe(1); -+ this.inflight--; -+ res(a * 3); -+ }, Math.random() * 10); -+ }); -+ } - } -diff --git a/jslib/spec/common/misc/utils.spec.ts b/jslib/spec/common/misc/utils.spec.ts -index 97c3d1e5..eec41e4b 100644 ---- a/jslib/spec/common/misc/utils.spec.ts -+++ b/jslib/spec/common/misc/utils.spec.ts -@@ -1,71 +1,73 @@ --import { Utils } from 'jslib-common/misc/utils'; -+import { Utils } from "jslib-common/misc/utils"; - --describe('Utils Service', () => { -- describe('getDomain', () => { -- it('should fail for invalid urls', () => { -- expect(Utils.getDomain(null)).toBeNull(); -- expect(Utils.getDomain(undefined)).toBeNull(); -- expect(Utils.getDomain(' ')).toBeNull(); -- expect(Utils.getDomain('https://bit!:"_&ward.com')).toBeNull(); -- expect(Utils.getDomain('bitwarden')).toBeNull(); -- }); -+describe("Utils Service", () => { -+ describe("getDomain", () => { -+ it("should fail for invalid urls", () => { -+ expect(Utils.getDomain(null)).toBeNull(); -+ expect(Utils.getDomain(undefined)).toBeNull(); -+ expect(Utils.getDomain(" ")).toBeNull(); -+ expect(Utils.getDomain('https://bit!:"_&ward.com')).toBeNull(); -+ expect(Utils.getDomain("bitwarden")).toBeNull(); -+ }); - -- it('should fail for data urls', () => { -- expect(Utils.getDomain('data:image/jpeg;base64,AAA')).toBeNull(); -- }); -+ it("should fail for data urls", () => { -+ expect(Utils.getDomain("data:image/jpeg;base64,AAA")).toBeNull(); -+ }); - -- it('should handle urls without protocol', () => { -- expect(Utils.getDomain('bitwarden.com')).toBe('bitwarden.com'); -- expect(Utils.getDomain('wrong://bitwarden.com')).toBe('bitwarden.com'); -- }); -+ it("should handle urls without protocol", () => { -+ expect(Utils.getDomain("bitwarden.com")).toBe("bitwarden.com"); -+ expect(Utils.getDomain("wrong://bitwarden.com")).toBe("bitwarden.com"); -+ }); - -- it('should handle valid urls', () => { -- expect(Utils.getDomain('https://bitwarden')).toBe('bitwarden'); -- expect(Utils.getDomain('https://bitwarden.com')).toBe('bitwarden.com'); -- expect(Utils.getDomain('http://bitwarden.com')).toBe('bitwarden.com'); -- expect(Utils.getDomain('http://vault.bitwarden.com')).toBe('bitwarden.com'); -- expect(Utils.getDomain('https://user:password@bitwarden.com:8080/password/sites?and&query#hash')) -- .toBe('bitwarden.com'); -- expect(Utils.getDomain('https://bitwarden.unknown')).toBe('bitwarden.unknown'); -- }); -+ it("should handle valid urls", () => { -+ expect(Utils.getDomain("https://bitwarden")).toBe("bitwarden"); -+ expect(Utils.getDomain("https://bitwarden.com")).toBe("bitwarden.com"); -+ expect(Utils.getDomain("http://bitwarden.com")).toBe("bitwarden.com"); -+ expect(Utils.getDomain("http://vault.bitwarden.com")).toBe("bitwarden.com"); -+ expect( -+ Utils.getDomain("https://user:password@bitwarden.com:8080/password/sites?and&query#hash") -+ ).toBe("bitwarden.com"); -+ expect(Utils.getDomain("https://bitwarden.unknown")).toBe("bitwarden.unknown"); -+ }); - -- it('should support localhost and IP', () => { -- expect(Utils.getDomain('https://localhost')).toBe('localhost'); -- expect(Utils.getDomain('https://192.168.1.1')).toBe('192.168.1.1'); -- }); -+ it("should support localhost and IP", () => { -+ expect(Utils.getDomain("https://localhost")).toBe("localhost"); -+ expect(Utils.getDomain("https://192.168.1.1")).toBe("192.168.1.1"); -+ }); - -- it('should reject invalid hostnames', () => { -- expect(Utils.getDomain('https://mywebsite.com$.mywebsite.com')).toBeNull(); -- expect(Utils.getDomain('https://mywebsite.com!.mywebsite.com')).toBeNull(); -- }); -+ it("should reject invalid hostnames", () => { -+ expect(Utils.getDomain("https://mywebsite.com$.mywebsite.com")).toBeNull(); -+ expect(Utils.getDomain("https://mywebsite.com!.mywebsite.com")).toBeNull(); - }); -+ }); - -- describe('getHostname', () => { -- it('should fail for invalid urls', () => { -- expect(Utils.getHostname(null)).toBeNull(); -- expect(Utils.getHostname(undefined)).toBeNull(); -- expect(Utils.getHostname(' ')).toBeNull(); -- expect(Utils.getHostname('https://bit!:"_&ward.com')).toBeNull(); -- expect(Utils.getHostname('bitwarden')).toBeNull(); -- }); -+ describe("getHostname", () => { -+ it("should fail for invalid urls", () => { -+ expect(Utils.getHostname(null)).toBeNull(); -+ expect(Utils.getHostname(undefined)).toBeNull(); -+ expect(Utils.getHostname(" ")).toBeNull(); -+ expect(Utils.getHostname('https://bit!:"_&ward.com')).toBeNull(); -+ expect(Utils.getHostname("bitwarden")).toBeNull(); -+ }); - -- it('should handle valid urls', () => { -- expect(Utils.getHostname('bitwarden.com')).toBe('bitwarden.com'); -- expect(Utils.getHostname('https://bitwarden.com')).toBe('bitwarden.com'); -- expect(Utils.getHostname('http://bitwarden.com')).toBe('bitwarden.com'); -- expect(Utils.getHostname('http://vault.bitwarden.com')).toBe('vault.bitwarden.com'); -- }); -+ it("should handle valid urls", () => { -+ expect(Utils.getHostname("bitwarden.com")).toBe("bitwarden.com"); -+ expect(Utils.getHostname("https://bitwarden.com")).toBe("bitwarden.com"); -+ expect(Utils.getHostname("http://bitwarden.com")).toBe("bitwarden.com"); -+ expect(Utils.getHostname("http://vault.bitwarden.com")).toBe("vault.bitwarden.com"); -+ }); - -- it('should support localhost and IP', () => { -- expect(Utils.getHostname('https://localhost')).toBe('localhost'); -- expect(Utils.getHostname('https://192.168.1.1')).toBe('192.168.1.1'); -- }); -+ it("should support localhost and IP", () => { -+ expect(Utils.getHostname("https://localhost")).toBe("localhost"); -+ expect(Utils.getHostname("https://192.168.1.1")).toBe("192.168.1.1"); - }); -+ }); - -- describe('newGuid', () => { -- it('should create a valid guid', () => { -- const validGuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; -- expect(Utils.newGuid()).toMatch(validGuid); -- }); -+ describe("newGuid", () => { -+ it("should create a valid guid", () => { -+ const validGuid = -+ /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; -+ expect(Utils.newGuid()).toMatch(validGuid); - }); -+ }); - }); -diff --git a/jslib/spec/common/services/cipher.service.spec.ts b/jslib/spec/common/services/cipher.service.spec.ts -index 83dce24c..e1bf461c 100644 ---- a/jslib/spec/common/services/cipher.service.spec.ts -+++ b/jslib/spec/common/services/cipher.service.spec.ts -@@ -1,64 +1,71 @@ --import { Arg, Substitute, SubstituteOf } from '@fluffy-spoon/substitute'; -+import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; - --import { ApiService } from 'jslib-common/abstractions/api.service'; --import { CryptoService } from 'jslib-common/abstractions/crypto.service'; --import { FileUploadService } from 'jslib-common/abstractions/fileUpload.service'; --import { I18nService } from 'jslib-common/abstractions/i18n.service'; --import { LogService } from 'jslib-common/abstractions/log.service'; --import { SearchService } from 'jslib-common/abstractions/search.service'; --import { SettingsService } from 'jslib-common/abstractions/settings.service'; --import { StorageService } from 'jslib-common/abstractions/storage.service'; --import { UserService } from 'jslib-common/abstractions/user.service'; --import { Utils } from 'jslib-common/misc/utils'; --import { Cipher } from 'jslib-common/models/domain/cipher'; --import { EncArrayBuffer } from 'jslib-common/models/domain/encArrayBuffer'; --import { EncString } from 'jslib-common/models/domain/encString'; --import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey'; -+import { ApiService } from "jslib-common/abstractions/api.service"; -+import { CryptoService } from "jslib-common/abstractions/crypto.service"; -+import { FileUploadService } from "jslib-common/abstractions/fileUpload.service"; -+import { I18nService } from "jslib-common/abstractions/i18n.service"; -+import { LogService } from "jslib-common/abstractions/log.service"; -+import { SearchService } from "jslib-common/abstractions/search.service"; -+import { SettingsService } from "jslib-common/abstractions/settings.service"; -+import { StateService } from "jslib-common/abstractions/state.service"; - --import { CipherService } from 'jslib-common/services/cipher.service'; -+import { Utils } from "jslib-common/misc/utils"; -+import { Cipher } from "jslib-common/models/domain/cipher"; -+import { EncArrayBuffer } from "jslib-common/models/domain/encArrayBuffer"; -+import { EncString } from "jslib-common/models/domain/encString"; -+import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey"; - --const ENCRYPTED_TEXT = 'This data has been encrypted'; -+import { CipherService } from "jslib-common/services/cipher.service"; -+ -+const ENCRYPTED_TEXT = "This data has been encrypted"; - const ENCRYPTED_BYTES = new EncArrayBuffer(Utils.fromUtf8ToArray(ENCRYPTED_TEXT).buffer); - --describe('Cipher Service', () => { -- let cryptoService: SubstituteOf; -- let userService: SubstituteOf; -- let settingsService: SubstituteOf; -- let apiService: SubstituteOf; -- let fileUploadService: SubstituteOf; -- let storageService: SubstituteOf; -- let i18nService: SubstituteOf; -- let searchService: SubstituteOf; -- let logService: SubstituteOf; -+describe("Cipher Service", () => { -+ let cryptoService: SubstituteOf; -+ let stateService: SubstituteOf; -+ let settingsService: SubstituteOf; -+ let apiService: SubstituteOf; -+ let fileUploadService: SubstituteOf; -+ let i18nService: SubstituteOf; -+ let searchService: SubstituteOf; -+ let logService: SubstituteOf; - -- let cipherService: CipherService; -+ let cipherService: CipherService; - -- beforeEach(() => { -- cryptoService = Substitute.for(); -- userService = Substitute.for(); -- settingsService = Substitute.for(); -- apiService = Substitute.for(); -- fileUploadService = Substitute.for(); -- storageService = Substitute.for(); -- i18nService = Substitute.for(); -- searchService = Substitute.for(); -- logService = Substitute.for(); -+ beforeEach(() => { -+ cryptoService = Substitute.for(); -+ stateService = Substitute.for(); -+ settingsService = Substitute.for(); -+ apiService = Substitute.for(); -+ fileUploadService = Substitute.for(); -+ i18nService = Substitute.for(); -+ searchService = Substitute.for(); -+ logService = Substitute.for(); - -- cryptoService.encryptToBytes(Arg.any(), Arg.any()).resolves(ENCRYPTED_BYTES); -- cryptoService.encrypt(Arg.any(), Arg.any()).resolves(new EncString(ENCRYPTED_TEXT)); -+ cryptoService.encryptToBytes(Arg.any(), Arg.any()).resolves(ENCRYPTED_BYTES); -+ cryptoService.encrypt(Arg.any(), Arg.any()).resolves(new EncString(ENCRYPTED_TEXT)); - -- cipherService = new CipherService(cryptoService, userService, settingsService, apiService, fileUploadService, -- storageService, i18nService, () => searchService, logService); -- }); -+ cipherService = new CipherService( -+ cryptoService, -+ settingsService, -+ apiService, -+ fileUploadService, -+ i18nService, -+ () => searchService, -+ logService, -+ stateService -+ ); -+ }); - -- it('attachments upload encrypted file contents', async () => { -- const key = new SymmetricCryptoKey(new Uint8Array(32).buffer); -- const fileName = 'filename'; -- const fileData = new Uint8Array(10).buffer; -- cryptoService.getOrgKey(Arg.any()).resolves(new SymmetricCryptoKey(new Uint8Array(32).buffer)); -+ it("attachments upload encrypted file contents", async () => { -+ const fileName = "filename"; -+ const fileData = new Uint8Array(10).buffer; -+ cryptoService.getOrgKey(Arg.any()).resolves(new SymmetricCryptoKey(new Uint8Array(32).buffer)); - -- await cipherService.saveAttachmentRawWithServer(new Cipher(), fileName, fileData); -+ await cipherService.saveAttachmentRawWithServer(new Cipher(), fileName, fileData); - -- fileUploadService.received(1).uploadCipherAttachment(Arg.any(), Arg.any(), new EncString(ENCRYPTED_TEXT), ENCRYPTED_BYTES); -- }); -+ fileUploadService -+ .received(1) -+ .uploadCipherAttachment(Arg.any(), Arg.any(), new EncString(ENCRYPTED_TEXT), ENCRYPTED_BYTES); -+ }); - }); -diff --git a/jslib/spec/common/services/consoleLog.service.spec.ts b/jslib/spec/common/services/consoleLog.service.spec.ts -index 5f0a1412..a7f7eddb 100644 ---- a/jslib/spec/common/services/consoleLog.service.spec.ts -+++ b/jslib/spec/common/services/consoleLog.service.spec.ts -@@ -1,4 +1,4 @@ --import { ConsoleLogService } from 'jslib-common/services/consoleLog.service'; -+import { ConsoleLogService } from "jslib-common/services/consoleLog.service"; - - const originalConsole = console; - let caughtMessage: any; -@@ -6,90 +6,97 @@ let caughtMessage: any; - declare var console: any; - - export function interceptConsole(interceptions: any): object { -- console = { -- // tslint:disable-next-line -- log: function () { -- interceptions.log = arguments; -- }, -- // tslint:disable-next-line -- warn: function () { -- interceptions.warn = arguments; -- }, -- // tslint:disable-next-line -- error: function () { -- interceptions.error = arguments; -- }, -- }; -- return interceptions; -+ console = { -+ // tslint:disable-next-line -+ log: function () { -+ interceptions.log = arguments; -+ }, -+ // tslint:disable-next-line -+ warn: function () { -+ interceptions.warn = arguments; -+ }, -+ // tslint:disable-next-line -+ error: function () { -+ interceptions.error = arguments; -+ }, -+ }; -+ return interceptions; - } - - export function restoreConsole() { -- console = originalConsole; -+ console = originalConsole; - } - --describe('ConsoleLogService', () => { -- let logService: ConsoleLogService; -- beforeEach(() => { -- caughtMessage = {}; -- interceptConsole(caughtMessage); -- logService = new ConsoleLogService(true); -+describe("ConsoleLogService", () => { -+ let logService: ConsoleLogService; -+ beforeEach(() => { -+ caughtMessage = {}; -+ interceptConsole(caughtMessage); -+ logService = new ConsoleLogService(true); -+ }); -+ -+ afterAll(() => { -+ restoreConsole(); -+ }); -+ -+ it("filters messages below the set threshold", () => { -+ logService = new ConsoleLogService(true, (level) => true); -+ logService.debug("debug"); -+ logService.info("info"); -+ logService.warning("warning"); -+ logService.error("error"); -+ -+ expect(caughtMessage).toEqual({}); -+ }); -+ it("only writes debug messages in dev mode", () => { -+ logService = new ConsoleLogService(false); -+ -+ logService.debug("debug message"); -+ expect(caughtMessage.log).toBeUndefined(); -+ }); -+ -+ it("writes debug/info messages to console.log", () => { -+ logService.debug("this is a debug message"); -+ expect(caughtMessage).toEqual({ -+ log: jasmine.arrayWithExactContents(["this is a debug message"]), - }); - -- afterAll(() => { -- restoreConsole(); -+ logService.info("this is an info message"); -+ expect(caughtMessage).toEqual({ -+ log: jasmine.arrayWithExactContents(["this is an info message"]), - }); -- -- it('filters messages below the set threshold', () => { -- logService = new ConsoleLogService(true, level => true); -- logService.debug('debug'); -- logService.info('info'); -- logService.warning('warning'); -- logService.error('error'); -- -- expect(caughtMessage).toEqual({}); -- }); -- it('only writes debug messages in dev mode', () => { -- logService = new ConsoleLogService(false); -- -- logService.debug('debug message'); -- expect(caughtMessage.log).toBeUndefined(); -- }); -- -- -- it('writes debug/info messages to console.log', () => { -- logService.debug('this is a debug message'); -- expect(caughtMessage).toEqual({ log: jasmine.arrayWithExactContents(['this is a debug message']) }); -- -- logService.info('this is an info message'); -- expect(caughtMessage).toEqual({ log: jasmine.arrayWithExactContents(['this is an info message']) }); -+ }); -+ it("writes warning messages to console.warn", () => { -+ logService.warning("this is a warning message"); -+ expect(caughtMessage).toEqual({ -+ warn: jasmine.arrayWithExactContents(["this is a warning message"]), - }); -- it('writes warning messages to console.warn', () => { -- logService.warning('this is a warning message'); -- expect(caughtMessage).toEqual({ warn: jasmine.arrayWithExactContents(['this is a warning message']) }); -- }); -- it('writes error messages to console.error', () => { -- logService.error('this is an error message'); -- expect(caughtMessage).toEqual({ error: jasmine.arrayWithExactContents(['this is an error message']) }); -- }); -- -- it('times with output to info', async () => { -- logService.time(); -- await new Promise(r => setTimeout(r, 250)); -- const duration = logService.timeEnd(); -- expect(duration[0]).toBe(0); -- expect(duration[1]).toBeGreaterThan(0); -- expect(duration[1]).toBeLessThan(500 * 10e6); -- -- expect(caughtMessage).toEqual(jasmine.arrayContaining([])); -- expect(caughtMessage.log.length).toBe(1); -- expect(caughtMessage.log[0]).toEqual(jasmine.stringMatching(/^default: \d+\.?\d*ms$/)); -- }); -- -- it('filters time output', async () => { -- logService = new ConsoleLogService(true, level => true); -- logService.time(); -- logService.timeEnd(); -- -- expect(caughtMessage).toEqual({}); -+ }); -+ it("writes error messages to console.error", () => { -+ logService.error("this is an error message"); -+ expect(caughtMessage).toEqual({ -+ error: jasmine.arrayWithExactContents(["this is an error message"]), - }); -+ }); -+ -+ it("times with output to info", async () => { -+ logService.time(); -+ await new Promise((r) => setTimeout(r, 250)); -+ const duration = logService.timeEnd(); -+ expect(duration[0]).toBe(0); -+ expect(duration[1]).toBeGreaterThan(0); -+ expect(duration[1]).toBeLessThan(500 * 10e6); -+ -+ expect(caughtMessage).toEqual(jasmine.arrayContaining([])); -+ expect(caughtMessage.log.length).toBe(1); -+ expect(caughtMessage.log[0]).toEqual(jasmine.stringMatching(/^default: \d+\.?\d*ms$/)); -+ }); -+ -+ it("filters time output", async () => { -+ logService = new ConsoleLogService(true, (level) => true); -+ logService.time(); -+ logService.timeEnd(); -+ -+ expect(caughtMessage).toEqual({}); -+ }); - }); -diff --git a/jslib/spec/common/services/export.service.spec.ts b/jslib/spec/common/services/export.service.spec.ts -index a9d57d2b..62402d78 100644 ---- a/jslib/spec/common/services/export.service.spec.ts -+++ b/jslib/spec/common/services/export.service.spec.ts -@@ -1,123 +1,135 @@ --import { Substitute, SubstituteOf } from '@fluffy-spoon/substitute'; -+import { Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; - --import { ApiService } from 'jslib-common/abstractions/api.service'; --import { CipherService } from 'jslib-common/abstractions/cipher.service'; --import { CryptoService } from 'jslib-common/abstractions/crypto.service'; --import { FolderService } from 'jslib-common/abstractions/folder.service'; -+import { ApiService } from "jslib-common/abstractions/api.service"; -+import { CipherService } from "jslib-common/abstractions/cipher.service"; -+import { CryptoService } from "jslib-common/abstractions/crypto.service"; -+import { FolderService } from "jslib-common/abstractions/folder.service"; - --import { ExportService } from 'jslib-common/services/export.service'; -+import { ExportService } from "jslib-common/services/export.service"; - --import { Cipher } from 'jslib-common/models/domain/cipher'; --import { EncString } from 'jslib-common/models/domain/encString'; --import { Login } from 'jslib-common/models/domain/login'; --import { CipherWithIds as CipherExport } from 'jslib-common/models/export/cipherWithIds'; -+import { Cipher } from "jslib-common/models/domain/cipher"; -+import { EncString } from "jslib-common/models/domain/encString"; -+import { Login } from "jslib-common/models/domain/login"; -+import { CipherWithIds as CipherExport } from "jslib-common/models/export/cipherWithIds"; - --import { CipherType } from 'jslib-common/enums/cipherType'; --import { CipherView } from 'jslib-common/models/view/cipherView'; --import { LoginView } from 'jslib-common/models/view/loginView'; -+import { CipherType } from "jslib-common/enums/cipherType"; -+import { CipherView } from "jslib-common/models/view/cipherView"; -+import { LoginView } from "jslib-common/models/view/loginView"; - --import { BuildTestObject, GetUniqueString } from '../../utils'; -+import { BuildTestObject, GetUniqueString } from "../../utils"; - - const UserCipherViews = [ -- generateCipherView(false), -- generateCipherView(false), -- generateCipherView(true), -+ generateCipherView(false), -+ generateCipherView(false), -+ generateCipherView(true), - ]; - - const UserCipherDomains = [ -- generateCipherDomain(false), -- generateCipherDomain(false), -- generateCipherDomain(true), -+ generateCipherDomain(false), -+ generateCipherDomain(false), -+ generateCipherDomain(true), - ]; - - function generateCipherView(deleted: boolean) { -- return BuildTestObject({ -- id: GetUniqueString('id'), -- notes: GetUniqueString('notes'), -- type: CipherType.Login, -- login: BuildTestObject({ -- username: GetUniqueString('username'), -- password: GetUniqueString('password'), -- }, LoginView), -- collectionIds: null, -- deletedDate: deleted ? new Date() : null, -- }, CipherView); -+ return BuildTestObject( -+ { -+ id: GetUniqueString("id"), -+ notes: GetUniqueString("notes"), -+ type: CipherType.Login, -+ login: BuildTestObject( -+ { -+ username: GetUniqueString("username"), -+ password: GetUniqueString("password"), -+ }, -+ LoginView -+ ), -+ collectionIds: null, -+ deletedDate: deleted ? new Date() : null, -+ }, -+ CipherView -+ ); - } - - function generateCipherDomain(deleted: boolean) { -- return BuildTestObject({ -- id: GetUniqueString('id'), -- notes: new EncString(GetUniqueString('notes')), -- type: CipherType.Login, -- login: BuildTestObject({ -- username: new EncString(GetUniqueString('username')), -- password: new EncString(GetUniqueString('password')), -- }, Login), -- collectionIds: null, -- deletedDate: deleted ? new Date() : null, -- }, Cipher); -+ return BuildTestObject( -+ { -+ id: GetUniqueString("id"), -+ notes: new EncString(GetUniqueString("notes")), -+ type: CipherType.Login, -+ login: BuildTestObject( -+ { -+ username: new EncString(GetUniqueString("username")), -+ password: new EncString(GetUniqueString("password")), -+ }, -+ Login -+ ), -+ collectionIds: null, -+ deletedDate: deleted ? new Date() : null, -+ }, -+ Cipher -+ ); - } - - function expectEqualCiphers(ciphers: CipherView[] | Cipher[], jsonResult: string) { -- const actual = JSON.stringify(JSON.parse(jsonResult).items); -- const items: CipherExport[] = []; -- ciphers.forEach((c: CipherView | Cipher) => { -- const item = new CipherExport(); -- item.build(c); -- items.push(item); -- }); -- -- expect(actual).toEqual(JSON.stringify(items)); -+ const actual = JSON.stringify(JSON.parse(jsonResult).items); -+ const items: CipherExport[] = []; -+ ciphers.forEach((c: CipherView | Cipher) => { -+ const item = new CipherExport(); -+ item.build(c); -+ items.push(item); -+ }); -+ -+ expect(actual).toEqual(JSON.stringify(items)); - } - --describe('ExportService', () => { -- let exportService: ExportService; -- let apiService: SubstituteOf; -- let cipherService: SubstituteOf; -- let folderService: SubstituteOf; -- let cryptoService: SubstituteOf; -+describe("ExportService", () => { -+ let exportService: ExportService; -+ let apiService: SubstituteOf; -+ let cipherService: SubstituteOf; -+ let folderService: SubstituteOf; -+ let cryptoService: SubstituteOf; - -- beforeEach(() => { -- apiService = Substitute.for(); -- cipherService = Substitute.for(); -- folderService = Substitute.for(); -- cryptoService = Substitute.for(); -+ beforeEach(() => { -+ apiService = Substitute.for(); -+ cipherService = Substitute.for(); -+ folderService = Substitute.for(); -+ cryptoService = Substitute.for(); - -- folderService.getAllDecrypted().resolves([]); -- folderService.getAll().resolves([]); -+ folderService.getAllDecrypted().resolves([]); -+ folderService.getAll().resolves([]); - -- exportService = new ExportService(folderService, cipherService, apiService, cryptoService); -- }); -+ exportService = new ExportService(folderService, cipherService, apiService, cryptoService); -+ }); - -- it('exports unecrypted user ciphers', async () => { -- cipherService.getAllDecrypted().resolves(UserCipherViews.slice(0, 1)); -+ it("exports unecrypted user ciphers", async () => { -+ cipherService.getAllDecrypted().resolves(UserCipherViews.slice(0, 1)); - -- const actual = await exportService.getExport('json'); -+ const actual = await exportService.getExport("json"); - -- expectEqualCiphers(UserCipherViews.slice(0, 1), actual); -- }); -+ expectEqualCiphers(UserCipherViews.slice(0, 1), actual); -+ }); - -- it('exports encrypted json user ciphers', async () => { -- cipherService.getAll().resolves(UserCipherDomains.slice(0, 1)); -+ it("exports encrypted json user ciphers", async () => { -+ cipherService.getAll().resolves(UserCipherDomains.slice(0, 1)); - -- const actual = await exportService.getExport('encrypted_json'); -+ const actual = await exportService.getExport("encrypted_json"); - -- expectEqualCiphers(UserCipherDomains.slice(0, 1), actual); -- }); -+ expectEqualCiphers(UserCipherDomains.slice(0, 1), actual); -+ }); - -- it('does not unecrypted export trashed user items', async () => { -- cipherService.getAllDecrypted().resolves(UserCipherViews); -+ it("does not unecrypted export trashed user items", async () => { -+ cipherService.getAllDecrypted().resolves(UserCipherViews); - -- const actual = await exportService.getExport('json'); -+ const actual = await exportService.getExport("json"); - -- expectEqualCiphers(UserCipherViews.slice(0, 2), actual); -- }); -+ expectEqualCiphers(UserCipherViews.slice(0, 2), actual); -+ }); - -- it('does not encrypted export trashed user items', async () => { -- cipherService.getAll().resolves(UserCipherDomains); -+ it("does not encrypted export trashed user items", async () => { -+ cipherService.getAll().resolves(UserCipherDomains); - -- const actual = await exportService.getExport('encrypted_json'); -+ const actual = await exportService.getExport("encrypted_json"); - -- expectEqualCiphers(UserCipherDomains.slice(0, 2), actual); -- }); -+ expectEqualCiphers(UserCipherDomains.slice(0, 2), actual); -+ }); - }); -diff --git a/jslib/spec/electron/services/electronLog.service.spec.ts b/jslib/spec/electron/services/electronLog.service.spec.ts -index 8bbb46ee..30b52ce0 100644 ---- a/jslib/spec/electron/services/electronLog.service.spec.ts -+++ b/jslib/spec/electron/services/electronLog.service.spec.ts -@@ -1,9 +1,9 @@ --import { ElectronLogService } from 'jslib-electron/services/electronLog.service'; -+import { ElectronLogService } from "jslib-electron/services/electronLog.service"; - --describe('ElectronLogService', () => { -- it('sets dev based on electron method', () => { -- process.env.ELECTRON_IS_DEV = '1'; -- const logService = new ElectronLogService(); -- expect(logService).toEqual(jasmine.objectContaining({ isDev: true }) as any); -- }); -+describe("ElectronLogService", () => { -+ it("sets dev based on electron method", () => { -+ process.env.ELECTRON_IS_DEV = "1"; -+ const logService = new ElectronLogService(); -+ expect(logService).toEqual(jasmine.objectContaining({ isDev: true }) as any); -+ }); - }); -diff --git a/jslib/spec/electron/utils.spec.ts b/jslib/spec/electron/utils.spec.ts -index b350a82d..f9e95a2d 100644 ---- a/jslib/spec/electron/utils.spec.ts -+++ b/jslib/spec/electron/utils.spec.ts -@@ -1,27 +1,27 @@ --import { cleanUserAgent } from 'jslib-electron/utils'; -+import { cleanUserAgent } from "jslib-electron/utils"; - - const expectedUserAgent = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${process.versions.chrome} Safari/537.36`; - --describe('cleanUserAgent', () => { -- it('cleans mac agent', () => { -- const initialMacAgent = `Mozilla/5.0 (Macintosh; Intel Mac OS X 11_6_0) AppleWebKit/537.36 (KHTML, like Gecko) Bitwarden/${process.version} Chrome/${process.versions.chrome} Electron/${process.versions.electron} Safari/537.36`; -- expect(cleanUserAgent(initialMacAgent)).toEqual(expectedUserAgent); -- }); -+describe("cleanUserAgent", () => { -+ it("cleans mac agent", () => { -+ const initialMacAgent = `Mozilla/5.0 (Macintosh; Intel Mac OS X 11_6_0) AppleWebKit/537.36 (KHTML, like Gecko) Bitwarden/${process.version} Chrome/${process.versions.chrome} Electron/${process.versions.electron} Safari/537.36`; -+ expect(cleanUserAgent(initialMacAgent)).toEqual(expectedUserAgent); -+ }); - -- it('cleans windows agent', () => { -- const initialWindowsAgent = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Bitwarden/${process.version} Chrome/${process.versions.chrome} Electron/${process.versions.electron} Safari/537.36`; -- expect(cleanUserAgent(initialWindowsAgent)).toEqual(expectedUserAgent); -- }); -+ it("cleans windows agent", () => { -+ const initialWindowsAgent = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Bitwarden/${process.version} Chrome/${process.versions.chrome} Electron/${process.versions.electron} Safari/537.36`; -+ expect(cleanUserAgent(initialWindowsAgent)).toEqual(expectedUserAgent); -+ }); - -- it('cleans linux agent', () => { -- const initialWindowsAgent = `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Bitwarden/${process.version} Chrome/${process.versions.chrome} Electron/${process.versions.electron} Safari/537.36`; -- expect(cleanUserAgent(initialWindowsAgent)).toEqual(expectedUserAgent); -- }); -+ it("cleans linux agent", () => { -+ const initialWindowsAgent = `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Bitwarden/${process.version} Chrome/${process.versions.chrome} Electron/${process.versions.electron} Safari/537.36`; -+ expect(cleanUserAgent(initialWindowsAgent)).toEqual(expectedUserAgent); -+ }); - -- it('does not change version numbers', () => { -- const expected = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36`; -- const initialAgent = `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Bitwarden/1.28.3 Chrome/87.0.4280.141 Electron/11.4.5 Safari/537.36`; -+ it("does not change version numbers", () => { -+ const expected = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36`; -+ const initialAgent = `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Bitwarden/1.28.3 Chrome/87.0.4280.141 Electron/11.4.5 Safari/537.36`; - -- expect(cleanUserAgent(initialAgent)).toEqual(expected); -- }); -+ expect(cleanUserAgent(initialAgent)).toEqual(expected); -+ }); - }); -diff --git a/jslib/spec/helpers.ts b/jslib/spec/helpers.ts -index 058cc4fa..3d7aaa73 100644 ---- a/jslib/spec/helpers.ts -+++ b/jslib/spec/helpers.ts -@@ -1,9 +1,9 @@ - // tslint:disable-next-line --const TSConsoleReporter = require('jasmine-ts-console-reporter'); -+const TSConsoleReporter = require("jasmine-ts-console-reporter"); - jasmine.getEnv().clearReporters(); // Clear default console reporter - jasmine.getEnv().addReporter(new TSConsoleReporter()); - - // Polyfills - // tslint:disable-next-line --const jsdom: any = require('jsdom'); -+const jsdom: any = require("jsdom"); - (global as any).DOMParser = new jsdom.JSDOM().window.DOMParser; -diff --git a/jslib/spec/node/cli/consoleLog.service.spec.ts b/jslib/spec/node/cli/consoleLog.service.spec.ts -index afcdc007..a6d4529c 100644 ---- a/jslib/spec/node/cli/consoleLog.service.spec.ts -+++ b/jslib/spec/node/cli/consoleLog.service.spec.ts -@@ -1,41 +1,42 @@ --import { ConsoleLogService } from 'jslib-node/cli/services/consoleLog.service'; --import { interceptConsole, restoreConsole } from '../../common/services/consoleLog.service.spec'; -+import { ConsoleLogService } from "jslib-node/cli/services/consoleLog.service"; -+import { interceptConsole, restoreConsole } from "../../common/services/consoleLog.service.spec"; - - const originalConsole = console; - let caughtMessage: any = {}; - --describe('CLI Console log service', () => { -- let logService: ConsoleLogService; -- beforeEach(() => { -- caughtMessage = {}; -- interceptConsole(caughtMessage); -- logService = new ConsoleLogService(true); -- }); -+describe("CLI Console log service", () => { -+ let logService: ConsoleLogService; -+ beforeEach(() => { -+ caughtMessage = {}; -+ interceptConsole(caughtMessage); -+ logService = new ConsoleLogService(true); -+ }); - -- afterAll(() => { -- restoreConsole(); -- }); -+ afterAll(() => { -+ restoreConsole(); -+ }); - -- it('should redirect all console to error if BW_RESPONSE env is true', () => { -- process.env.BW_RESPONSE = 'true'; -+ it("should redirect all console to error if BW_RESPONSE env is true", () => { -+ process.env.BW_RESPONSE = "true"; - -- logService.debug('this is a debug message'); -- expect(caughtMessage).toEqual({ error: jasmine.arrayWithExactContents(['this is a debug message']) }); -+ logService.debug("this is a debug message"); -+ expect(caughtMessage).toEqual({ -+ error: jasmine.arrayWithExactContents(["this is a debug message"]), - }); -+ }); - -- it('should not redirect console to error if BW_RESPONSE != true', () => { -- process.env.BW_RESPONSE = 'false'; -- -- logService.debug('debug'); -- logService.info('info'); -- logService.warning('warning'); -- logService.error('error'); -+ it("should not redirect console to error if BW_RESPONSE != true", () => { -+ process.env.BW_RESPONSE = "false"; - -- expect(caughtMessage).toEqual({ -- log: jasmine.arrayWithExactContents(['info']), -- warn: jasmine.arrayWithExactContents(['warning']), -- error: jasmine.arrayWithExactContents(['error']), -- }); -+ logService.debug("debug"); -+ logService.info("info"); -+ logService.warning("warning"); -+ logService.error("error"); - -+ expect(caughtMessage).toEqual({ -+ log: jasmine.arrayWithExactContents(["info"]), -+ warn: jasmine.arrayWithExactContents(["warning"]), -+ error: jasmine.arrayWithExactContents(["error"]), - }); -+ }); - }); -diff --git a/jslib/spec/node/services/nodeCryptoFunction.service.spec.ts b/jslib/spec/node/services/nodeCryptoFunction.service.spec.ts -index 12b00b6e..43849b74 100644 ---- a/jslib/spec/node/services/nodeCryptoFunction.service.spec.ts -+++ b/jslib/spec/node/services/nodeCryptoFunction.service.spec.ts -@@ -1,424 +1,519 @@ --import { NodeCryptoFunctionService } from 'jslib-node/services/nodeCryptoFunction.service'; -- --import { Utils } from 'jslib-common/misc/utils'; --import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey'; -- --const RsaPublicKey = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP' + -- '4xlU2ab/v0crqIfXfIoWF/XXdHGIdrZeilnRXPPJT1B9dTsasttEZNnua/0Rek/cjNDHtzT52irfoZYS7X6HNIfOi54Q+egP' + -- 'RQ1H7iNHVZz3K8Db9GCSKPeC8MbW6gVCzb15esCe1gGzg6wkMuWYDFYPoh/oBqcIqrGah7firqB1nDedzEjw32heP2DAffVN' + -- '084iTDjiWrJNUxBJ2pDD5Z9dT3MzQ2s09ew1yMWK2z37rT3YerC7OgEDmo3WYo3xL3qYJznu3EO2nmrYjiRa40wKSjxsTlUc' + -- 'xDF+F0uMW8oR9EMUHgepdepfAtLsSAQIDAQAB'; --const RsaPrivateKey = 'MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXRVrCX+2hfOQS8Hz' + -- 'YUS2oc/jGVTZpv+/Ryuoh9d8ihYX9dd0cYh2tl6KWdFc88lPUH11Oxqy20Rk2e5r/RF6T9yM0Me3NPnaKt+hlhLtfoc0h86L' + -- 'nhD56A9FDUfuI0dVnPcrwNv0YJIo94LwxtbqBULNvXl6wJ7WAbODrCQy5ZgMVg+iH+gGpwiqsZqHt+KuoHWcN53MSPDfaF4/' + -- 'YMB99U3TziJMOOJask1TEEnakMPln11PczNDazT17DXIxYrbPfutPdh6sLs6AQOajdZijfEvepgnOe7cQ7aeatiOJFrjTApK' + -- 'PGxOVRzEMX4XS4xbyhH0QxQeB6l16l8C0uxIBAgMBAAECggEASaWfeVDA3cVzOPFSpvJm20OTE+R6uGOU+7vh36TX/POq92q' + -- 'Buwbd0h0oMD32FxsXywd2IxtBDUSiFM9699qufTVuM0Q3tZw6lHDTOVG08+tPdr8qSbMtw7PGFxN79fHLBxejjO4IrM9lapj' + -- 'WpxEF+11x7r+wM+0xRZQ8sNFYG46aPfIaty4BGbL0I2DQ2y8I57iBCAy69eht59NLMm27fRWGJIWCuBIjlpfzET1j2HLXUIh' + -- '5bTBNzqaN039WH49HczGE3mQKVEJZc/efk3HaVd0a1Sjzyn0QY+N1jtZN3jTRbuDWA1AknkX1LX/0tUhuS3/7C3ejHxjw4Dk' + -- '1ZLo5/QKBgQDIWvqFn0+IKRSu6Ua2hDsufIHHUNLelbfLUMmFthxabcUn4zlvIscJO00Tq/ezopSRRvbGiqnxjv/mYxucvOU' + -- 'BeZtlus0Q9RTACBtw9TGoNTmQbEunJ2FOSlqbQxkBBAjgGEppRPt30iGj/VjAhCATq2MYOa/X4dVR51BqQAFIEwKBgQDBSIf' + -- 'TFKC/hDk6FKZlgwvupWYJyU9RkyfstPErZFmzoKhPkQ3YORo2oeAYmVUbS9I2iIYpYpYQJHX8jMuCbCz4ONxTCuSIXYQYUcU' + -- 'q4PglCKp31xBAE6TN8SvhfME9/MvuDssnQinAHuF0GDAhF646T3LLS1not6Vszv7brwSoGwKBgQC88v/8cGfi80ssQZeMnVv' + -- 'q1UTXIeQcQnoY5lGHJl3K8mbS3TnXE6c9j417Fdz+rj8KWzBzwWXQB5pSPflWcdZO886Xu/mVGmy9RWgLuVFhXwCwsVEPjNX' + -- '5ramRb0/vY0yzenUCninBsIxFSbIfrPtLUYCc4hpxr+sr2Mg/y6jpvQKBgBezMRRs3xkcuXepuI2R+BCXL1/b02IJTUf1F+1' + -- 'eLLGd7YV0H+J3fgNc7gGWK51hOrF9JBZHBGeOUPlaukmPwiPdtQZpu4QNE3l37VlIpKTF30E6mb+BqR+nht3rUjarnMXgAoE' + -- 'Z18y6/KIjpSMpqC92Nnk/EBM9EYe6Cf4eA9ApAoGAeqEUg46UTlJySkBKURGpIs3v1kkf5I0X8DnOhwb+HPxNaiEdmO7ckm8' + -- '+tPVgppLcG0+tMdLjigFQiDUQk2y3WjyxP5ZvXu7U96jaJRI8PFMoE06WeVYcdIzrID2HvqH+w0UQJFrLJ/0Mn4stFAEzXKZ' + -- 'BokBGnjFnTnKcs7nv/O8='; -- --const Sha1Mac = '4d4c223f95dc577b665ec4ccbcb680b80a397038'; --const Sha256Mac = '6be3caa84922e12aaaaa2f16c40d44433bb081ef323db584eb616333ab4e874f'; --const Sha512Mac = '21910e341fa12106ca35758a2285374509326c9fbe0bd64e7b99c898f841dc948c58ce66d3504d8883c' + -- '5ea7817a0b7c5d4d9b00364ccd214669131fc17fe4aca'; -- --describe('NodeCrypto Function Service', () => { -- describe('pbkdf2', () => { -- const regular256Key = 'pj9prw/OHPleXI6bRdmlaD+saJS4awrMiQsQiDjeu2I='; -- const utf8256Key = 'yqvoFXgMRmHR3QPYr5pyR4uVuoHkltv9aHUP63p8n7I='; -- const unicode256Key = 'ZdeOata6xoRpB4DLp8zHhXz5kLmkWtX5pd+TdRH8w8w='; -- -- const regular512Key = 'liTi/Ke8LPU1Qv+Vl7NGEVt/XMbsBVJ2kQxtVG/Z1/JFHFKQW3ZkI81qVlwTiCpb+cFXzs+57' + -- 'eyhhx5wfKo5Cg=='; -- const utf8512Key = 'df0KdvIBeCzD/kyXptwQohaqUa4e7IyFUyhFQjXCANu5T+scq55hCcE4dG4T/MhAk2exw8j7ixRN' + -- 'zXANiVZpnw=='; -- const unicode512Key = 'FE+AnUJaxv8jh+zUDtZz4mjjcYk0/PZDZm+SLJe3XtxtnpdqqpblX6JjuMZt/dYYNMOrb2+mD' + -- 'L3FiQDTROh1lg=='; -- -- testPbkdf2('sha256', regular256Key, utf8256Key, unicode256Key); -- testPbkdf2('sha512', regular512Key, utf8512Key, unicode512Key); -- }); -- -- describe('hkdf', () => { -- const regular256Key = 'qBUmEYtwTwwGPuw/z6bs/qYXXYNUlocFlyAuuANI8Pw='; -- const utf8256Key = '6DfJwW1R3txgiZKkIFTvVAb7qVlG7lKcmJGJoxR2GBU='; -- const unicode256Key = 'gejGI82xthA+nKtKmIh82kjw+ttHr+ODsUoGdu5sf0A='; -- -- const regular512Key = 'xe5cIG6ZfwGmb1FvsOedM0XKOm21myZkjL/eDeKIqqM='; -- const utf8512Key = 'XQMVBnxVEhlvjSFDQc77j5GDE9aorvbS0vKnjhRg0LY='; -- const unicode512Key = '148GImrTbrjaGAe/iWEpclINM8Ehhko+9lB14+52lqc='; -- -- testHkdf('sha256', regular256Key, utf8256Key, unicode256Key); -- testHkdf('sha512', regular512Key, utf8512Key, unicode512Key); -- }); -- -- describe('hkdfExpand', () => { -- const prk16Byte = 'criAmKtfzxanbgea5/kelQ=='; -- const prk32Byte = 'F5h4KdYQnIVH4rKH0P9CZb1GrR4n16/sJrS0PsQEn0Y='; -- const prk64Byte = 'ssBK0mRG17VHdtsgt8yo4v25CRNpauH+0r2fwY/E9rLyaFBAOMbIeTry+' + -- 'gUJ28p8y+hFh3EI9pcrEWaNvFYonQ=='; -- -- testHkdfExpand('sha256', prk32Byte, 32, 'BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD8='); -- testHkdfExpand('sha256', prk32Byte, 64, 'BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD9BV+' + -- '/queOZenPNkDhmlVyL2WZ3OSU5+7ISNF5NhNfvZA=='); -- testHkdfExpand('sha512', prk64Byte, 32, 'uLWbMWodSBms5uGJ5WTRTesyW+MD7nlpCZvagvIRXlk='); -- testHkdfExpand('sha512', prk64Byte, 64, 'uLWbMWodSBms5uGJ5WTRTesyW+MD7nlpCZvagvIRXlkY5Pv0sB+' + -- 'MqvaopmkC6sD/j89zDwTV9Ib2fpucUydO8w=='); -- -- it('should fail with prk too small', async () => { -- const cryptoFunctionService = new NodeCryptoFunctionService(); -- const f = cryptoFunctionService.hkdfExpand(Utils.fromB64ToArray(prk16Byte), 'info', 32, 'sha256'); -- await expectAsync(f).toBeRejectedWith(new Error('prk is too small.')); -- }); -- -- it('should fail with outputByteSize is too large', async () => { -- const cryptoFunctionService = new NodeCryptoFunctionService(); -- const f = cryptoFunctionService.hkdfExpand(Utils.fromB64ToArray(prk32Byte), 'info', 8161, 'sha256'); -- await expectAsync(f).toBeRejectedWith(new Error('outputByteSize is too large.')); -- }); -- }); -- -- describe('hash', () => { -- const regular1Hash = '2a241604fb921fad12bf877282457268e1dccb70'; -- const utf81Hash = '85672798dc5831e96d6c48655d3d39365a9c88b6'; -- const unicode1Hash = '39c975935054a3efc805a9709b60763a823a6ad4'; -- -- const regular256Hash = '2b8e96031d352a8655d733d7a930b5ffbea69dc25cf65c7bca7dd946278908b2'; -- const utf8256Hash = '25fe8440f5b01ed113b0a0e38e721b126d2f3f77a67518c4a04fcde4e33eeb9d'; -- const unicode256Hash = 'adc1c0c2afd6e92cefdf703f9b6eb2c38e0d6d1a040c83f8505c561fea58852e'; -- -- const regular512Hash = 'c15cf11d43bde333647e3f559ec4193bb2edeaa0e8b902772f514cdf3f785a3f49a6e02a4b87b3' + -- 'b47523271ad45b7e0aebb5cdcc1bc54815d256eb5dcb80da9d'; -- const utf8512Hash = '035c31a877a291af09ed2d3a1a293e69c3e079ea2cecc00211f35e6bce10474ca3ad6e30b59e26118' + -- '37463f20969c5bc95282965a051a88f8cdf2e166549fcdd'; -- const unicode512Hash = '2b16a5561af8ad6fe414cc103fc8036492e1fc6d9aabe1b655497054f760fe0e34c5d100ac773d' + -- '9f3030438284f22dbfa20cb2e9b019f2c98dfe38ce1ef41bae'; -- -- const regularMd5 = '5eceffa53a5fd58c44134211e2c5f522'; -- const utf8Md5 = '3abc9433c09551b939c80aa0aa3174e1'; -- const unicodeMd5 = '85ae134072c8d81257933f7045ba17ca'; -- -- testHash('sha1', regular1Hash, utf81Hash, unicode1Hash); -- testHash('sha256', regular256Hash, utf8256Hash, unicode256Hash); -- testHash('sha512', regular512Hash, utf8512Hash, unicode512Hash); -- testHash('md5', regularMd5, utf8Md5, unicodeMd5); -- }); -- -- describe('hmac', () => { -- testHmac('sha1', Sha1Mac); -- testHmac('sha256', Sha256Mac); -- testHmac('sha512', Sha512Mac); -- }); -- -- describe('compare', () => { -- testCompare(false); -- }); -- -- describe('hmacFast', () => { -- testHmac('sha1', Sha1Mac, true); -- testHmac('sha256', Sha256Mac, true); -- testHmac('sha512', Sha512Mac, true); -- }); -- -- describe('compareFast', () => { -- testCompare(true); -- }); -- -- describe('aesEncrypt', () => { -- it('should successfully encrypt data', async () => { -- const nodeCryptoFunctionService = new NodeCryptoFunctionService(); -- const iv = makeStaticByteArray(16); -- const key = makeStaticByteArray(32); -- const data = Utils.fromUtf8ToArray('EncryptMe!'); -- const encValue = await nodeCryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer); -- expect(Utils.fromBufferToB64(encValue)).toBe('ByUF8vhyX4ddU9gcooznwA=='); -- }); -- -- it('should successfully encrypt and then decrypt data', async () => { -- const nodeCryptoFunctionService = new NodeCryptoFunctionService(); -- const iv = makeStaticByteArray(16); -- const key = makeStaticByteArray(32); -- const value = 'EncryptMe!'; -- const data = Utils.fromUtf8ToArray(value); -- const encValue = await nodeCryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer); -- const decValue = await nodeCryptoFunctionService.aesDecrypt(encValue, iv.buffer, key.buffer); -- expect(Utils.fromBufferToUtf8(decValue)).toBe(value); -- }); -- }); -- -- describe('aesDecryptFast', () => { -- it('should successfully decrypt data', async () => { -- const nodeCryptoFunctionService = new NodeCryptoFunctionService(); -- const iv = Utils.fromBufferToB64(makeStaticByteArray(16).buffer); -- const symKey = new SymmetricCryptoKey(makeStaticByteArray(32).buffer); -- const data = 'ByUF8vhyX4ddU9gcooznwA=='; -- const params = nodeCryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey); -- const decValue = await nodeCryptoFunctionService.aesDecryptFast(params); -- expect(decValue).toBe('EncryptMe!'); -- }); -- }); -- -- describe('aesDecrypt', () => { -- it('should successfully decrypt data', async () => { -- const nodeCryptoFunctionService = new NodeCryptoFunctionService(); -- const iv = makeStaticByteArray(16); -- const key = makeStaticByteArray(32); -- const data = Utils.fromB64ToArray('ByUF8vhyX4ddU9gcooznwA=='); -- const decValue = await nodeCryptoFunctionService.aesDecrypt(data.buffer, iv.buffer, key.buffer); -- expect(Utils.fromBufferToUtf8(decValue)).toBe('EncryptMe!'); -- }); -- }); -- -- describe('rsaEncrypt', () => { -- it('should successfully encrypt and then decrypt data', async () => { -- const nodeCryptoFunctionService = new NodeCryptoFunctionService(); -- const pubKey = Utils.fromB64ToArray(RsaPublicKey); -- const privKey = Utils.fromB64ToArray(RsaPrivateKey); -- const value = 'EncryptMe!'; -- const data = Utils.fromUtf8ToArray(value); -- const encValue = await nodeCryptoFunctionService.rsaEncrypt(data.buffer, pubKey.buffer, 'sha1'); -- const decValue = await nodeCryptoFunctionService.rsaDecrypt(encValue, privKey.buffer, 'sha1'); -- expect(Utils.fromBufferToUtf8(decValue)).toBe(value); -- }); -- }); -- -- describe('rsaDecrypt', () => { -- it('should successfully decrypt data', async () => { -- const nodeCryptoFunctionService = new NodeCryptoFunctionService(); -- const privKey = Utils.fromB64ToArray(RsaPrivateKey); -- const data = Utils.fromB64ToArray('A1/p8BQzN9UrbdYxUY2Va5+kPLyfZXF9JsZrjeEXcaclsnHurdxVAJcnbEqYMP3UXV' + -- '4YAS/mpf+Rxe6/X0WS1boQdA0MAHSgx95hIlAraZYpiMLLiJRKeo2u8YivCdTM9V5vuAEJwf9Tof/qFsFci3sApdbATkorCT' + -- 'zFOIEPF2S1zgperEP23M01mr4dWVdYN18B32YF67xdJHMbFhp5dkQwv9CmscoWq7OE5HIfOb+JAh7BEZb+CmKhM3yWJvoR/D' + -- '/5jcercUtK2o+XrzNrL4UQ7yLZcFz6Bfwb/j6ICYvqd/YJwXNE6dwlL57OfwJyCdw2rRYf0/qI00t9u8Iitw=='); -- const decValue = await nodeCryptoFunctionService.rsaDecrypt(data.buffer, privKey.buffer, 'sha1'); -- expect(Utils.fromBufferToUtf8(decValue)).toBe('EncryptMe!'); -- }); -- }); -- -- describe('rsaExtractPublicKey', () => { -- it('should successfully extract key', async () => { -- const nodeCryptoFunctionService = new NodeCryptoFunctionService(); -- const privKey = Utils.fromB64ToArray(RsaPrivateKey); -- const publicKey = await nodeCryptoFunctionService.rsaExtractPublicKey(privKey.buffer); -- expect(Utils.fromBufferToB64(publicKey)).toBe(RsaPublicKey); -- }); -- }); -- -- describe('rsaGenerateKeyPair', () => { -- testRsaGenerateKeyPair(1024); -- testRsaGenerateKeyPair(2048); -- -- // Generating 4096 bit keys is really slow with Forge lib. -- // Maybe move to something else if we ever want to generate keys of this size. -- // testRsaGenerateKeyPair(4096); -- }); -- -- describe('randomBytes', () => { -- it('should make a value of the correct length', async () => { -- const nodeCryptoFunctionService = new NodeCryptoFunctionService(); -- const randomData = await nodeCryptoFunctionService.randomBytes(16); -- expect(randomData.byteLength).toBe(16); -- }); -- -- it('should not make the same value twice', async () => { -- const nodeCryptoFunctionService = new NodeCryptoFunctionService(); -- const randomData = await nodeCryptoFunctionService.randomBytes(16); -- const randomData2 = await nodeCryptoFunctionService.randomBytes(16); -- expect(randomData.byteLength === randomData2.byteLength && randomData !== randomData2).toBeTruthy(); -- }); -- }); -+import { NodeCryptoFunctionService } from "jslib-node/services/nodeCryptoFunction.service"; -+ -+import { Utils } from "jslib-common/misc/utils"; -+import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey"; -+ -+const RsaPublicKey = -+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP" + -+ "4xlU2ab/v0crqIfXfIoWF/XXdHGIdrZeilnRXPPJT1B9dTsasttEZNnua/0Rek/cjNDHtzT52irfoZYS7X6HNIfOi54Q+egP" + -+ "RQ1H7iNHVZz3K8Db9GCSKPeC8MbW6gVCzb15esCe1gGzg6wkMuWYDFYPoh/oBqcIqrGah7firqB1nDedzEjw32heP2DAffVN" + -+ "084iTDjiWrJNUxBJ2pDD5Z9dT3MzQ2s09ew1yMWK2z37rT3YerC7OgEDmo3WYo3xL3qYJznu3EO2nmrYjiRa40wKSjxsTlUc" + -+ "xDF+F0uMW8oR9EMUHgepdepfAtLsSAQIDAQAB"; -+const RsaPrivateKey = -+ "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXRVrCX+2hfOQS8Hz" + -+ "YUS2oc/jGVTZpv+/Ryuoh9d8ihYX9dd0cYh2tl6KWdFc88lPUH11Oxqy20Rk2e5r/RF6T9yM0Me3NPnaKt+hlhLtfoc0h86L" + -+ "nhD56A9FDUfuI0dVnPcrwNv0YJIo94LwxtbqBULNvXl6wJ7WAbODrCQy5ZgMVg+iH+gGpwiqsZqHt+KuoHWcN53MSPDfaF4/" + -+ "YMB99U3TziJMOOJask1TEEnakMPln11PczNDazT17DXIxYrbPfutPdh6sLs6AQOajdZijfEvepgnOe7cQ7aeatiOJFrjTApK" + -+ "PGxOVRzEMX4XS4xbyhH0QxQeB6l16l8C0uxIBAgMBAAECggEASaWfeVDA3cVzOPFSpvJm20OTE+R6uGOU+7vh36TX/POq92q" + -+ "Buwbd0h0oMD32FxsXywd2IxtBDUSiFM9699qufTVuM0Q3tZw6lHDTOVG08+tPdr8qSbMtw7PGFxN79fHLBxejjO4IrM9lapj" + -+ "WpxEF+11x7r+wM+0xRZQ8sNFYG46aPfIaty4BGbL0I2DQ2y8I57iBCAy69eht59NLMm27fRWGJIWCuBIjlpfzET1j2HLXUIh" + -+ "5bTBNzqaN039WH49HczGE3mQKVEJZc/efk3HaVd0a1Sjzyn0QY+N1jtZN3jTRbuDWA1AknkX1LX/0tUhuS3/7C3ejHxjw4Dk" + -+ "1ZLo5/QKBgQDIWvqFn0+IKRSu6Ua2hDsufIHHUNLelbfLUMmFthxabcUn4zlvIscJO00Tq/ezopSRRvbGiqnxjv/mYxucvOU" + -+ "BeZtlus0Q9RTACBtw9TGoNTmQbEunJ2FOSlqbQxkBBAjgGEppRPt30iGj/VjAhCATq2MYOa/X4dVR51BqQAFIEwKBgQDBSIf" + -+ "TFKC/hDk6FKZlgwvupWYJyU9RkyfstPErZFmzoKhPkQ3YORo2oeAYmVUbS9I2iIYpYpYQJHX8jMuCbCz4ONxTCuSIXYQYUcU" + -+ "q4PglCKp31xBAE6TN8SvhfME9/MvuDssnQinAHuF0GDAhF646T3LLS1not6Vszv7brwSoGwKBgQC88v/8cGfi80ssQZeMnVv" + -+ "q1UTXIeQcQnoY5lGHJl3K8mbS3TnXE6c9j417Fdz+rj8KWzBzwWXQB5pSPflWcdZO886Xu/mVGmy9RWgLuVFhXwCwsVEPjNX" + -+ "5ramRb0/vY0yzenUCninBsIxFSbIfrPtLUYCc4hpxr+sr2Mg/y6jpvQKBgBezMRRs3xkcuXepuI2R+BCXL1/b02IJTUf1F+1" + -+ "eLLGd7YV0H+J3fgNc7gGWK51hOrF9JBZHBGeOUPlaukmPwiPdtQZpu4QNE3l37VlIpKTF30E6mb+BqR+nht3rUjarnMXgAoE" + -+ "Z18y6/KIjpSMpqC92Nnk/EBM9EYe6Cf4eA9ApAoGAeqEUg46UTlJySkBKURGpIs3v1kkf5I0X8DnOhwb+HPxNaiEdmO7ckm8" + -+ "+tPVgppLcG0+tMdLjigFQiDUQk2y3WjyxP5ZvXu7U96jaJRI8PFMoE06WeVYcdIzrID2HvqH+w0UQJFrLJ/0Mn4stFAEzXKZ" + -+ "BokBGnjFnTnKcs7nv/O8="; -+ -+const Sha1Mac = "4d4c223f95dc577b665ec4ccbcb680b80a397038"; -+const Sha256Mac = "6be3caa84922e12aaaaa2f16c40d44433bb081ef323db584eb616333ab4e874f"; -+const Sha512Mac = -+ "21910e341fa12106ca35758a2285374509326c9fbe0bd64e7b99c898f841dc948c58ce66d3504d8883c" + -+ "5ea7817a0b7c5d4d9b00364ccd214669131fc17fe4aca"; -+ -+describe("NodeCrypto Function Service", () => { -+ describe("pbkdf2", () => { -+ const regular256Key = "pj9prw/OHPleXI6bRdmlaD+saJS4awrMiQsQiDjeu2I="; -+ const utf8256Key = "yqvoFXgMRmHR3QPYr5pyR4uVuoHkltv9aHUP63p8n7I="; -+ const unicode256Key = "ZdeOata6xoRpB4DLp8zHhXz5kLmkWtX5pd+TdRH8w8w="; -+ -+ const regular512Key = -+ "liTi/Ke8LPU1Qv+Vl7NGEVt/XMbsBVJ2kQxtVG/Z1/JFHFKQW3ZkI81qVlwTiCpb+cFXzs+57" + -+ "eyhhx5wfKo5Cg=="; -+ const utf8512Key = -+ "df0KdvIBeCzD/kyXptwQohaqUa4e7IyFUyhFQjXCANu5T+scq55hCcE4dG4T/MhAk2exw8j7ixRN" + -+ "zXANiVZpnw=="; -+ const unicode512Key = -+ "FE+AnUJaxv8jh+zUDtZz4mjjcYk0/PZDZm+SLJe3XtxtnpdqqpblX6JjuMZt/dYYNMOrb2+mD" + -+ "L3FiQDTROh1lg=="; -+ -+ testPbkdf2("sha256", regular256Key, utf8256Key, unicode256Key); -+ testPbkdf2("sha512", regular512Key, utf8512Key, unicode512Key); -+ }); -+ -+ describe("hkdf", () => { -+ const regular256Key = "qBUmEYtwTwwGPuw/z6bs/qYXXYNUlocFlyAuuANI8Pw="; -+ const utf8256Key = "6DfJwW1R3txgiZKkIFTvVAb7qVlG7lKcmJGJoxR2GBU="; -+ const unicode256Key = "gejGI82xthA+nKtKmIh82kjw+ttHr+ODsUoGdu5sf0A="; -+ -+ const regular512Key = "xe5cIG6ZfwGmb1FvsOedM0XKOm21myZkjL/eDeKIqqM="; -+ const utf8512Key = "XQMVBnxVEhlvjSFDQc77j5GDE9aorvbS0vKnjhRg0LY="; -+ const unicode512Key = "148GImrTbrjaGAe/iWEpclINM8Ehhko+9lB14+52lqc="; -+ -+ testHkdf("sha256", regular256Key, utf8256Key, unicode256Key); -+ testHkdf("sha512", regular512Key, utf8512Key, unicode512Key); -+ }); -+ -+ describe("hkdfExpand", () => { -+ const prk16Byte = "criAmKtfzxanbgea5/kelQ=="; -+ const prk32Byte = "F5h4KdYQnIVH4rKH0P9CZb1GrR4n16/sJrS0PsQEn0Y="; -+ const prk64Byte = -+ "ssBK0mRG17VHdtsgt8yo4v25CRNpauH+0r2fwY/E9rLyaFBAOMbIeTry+" + -+ "gUJ28p8y+hFh3EI9pcrEWaNvFYonQ=="; -+ -+ testHkdfExpand("sha256", prk32Byte, 32, "BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD8="); -+ testHkdfExpand( -+ "sha256", -+ prk32Byte, -+ 64, -+ "BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD9BV+" + -+ "/queOZenPNkDhmlVyL2WZ3OSU5+7ISNF5NhNfvZA==" -+ ); -+ testHkdfExpand("sha512", prk64Byte, 32, "uLWbMWodSBms5uGJ5WTRTesyW+MD7nlpCZvagvIRXlk="); -+ testHkdfExpand( -+ "sha512", -+ prk64Byte, -+ 64, -+ "uLWbMWodSBms5uGJ5WTRTesyW+MD7nlpCZvagvIRXlkY5Pv0sB+" + -+ "MqvaopmkC6sD/j89zDwTV9Ib2fpucUydO8w==" -+ ); -+ -+ it("should fail with prk too small", async () => { -+ const cryptoFunctionService = new NodeCryptoFunctionService(); -+ const f = cryptoFunctionService.hkdfExpand( -+ Utils.fromB64ToArray(prk16Byte), -+ "info", -+ 32, -+ "sha256" -+ ); -+ await expectAsync(f).toBeRejectedWith(new Error("prk is too small.")); -+ }); -+ -+ it("should fail with outputByteSize is too large", async () => { -+ const cryptoFunctionService = new NodeCryptoFunctionService(); -+ const f = cryptoFunctionService.hkdfExpand( -+ Utils.fromB64ToArray(prk32Byte), -+ "info", -+ 8161, -+ "sha256" -+ ); -+ await expectAsync(f).toBeRejectedWith(new Error("outputByteSize is too large.")); -+ }); -+ }); -+ -+ describe("hash", () => { -+ const regular1Hash = "2a241604fb921fad12bf877282457268e1dccb70"; -+ const utf81Hash = "85672798dc5831e96d6c48655d3d39365a9c88b6"; -+ const unicode1Hash = "39c975935054a3efc805a9709b60763a823a6ad4"; -+ -+ const regular256Hash = "2b8e96031d352a8655d733d7a930b5ffbea69dc25cf65c7bca7dd946278908b2"; -+ const utf8256Hash = "25fe8440f5b01ed113b0a0e38e721b126d2f3f77a67518c4a04fcde4e33eeb9d"; -+ const unicode256Hash = "adc1c0c2afd6e92cefdf703f9b6eb2c38e0d6d1a040c83f8505c561fea58852e"; -+ -+ const regular512Hash = -+ "c15cf11d43bde333647e3f559ec4193bb2edeaa0e8b902772f514cdf3f785a3f49a6e02a4b87b3" + -+ "b47523271ad45b7e0aebb5cdcc1bc54815d256eb5dcb80da9d"; -+ const utf8512Hash = -+ "035c31a877a291af09ed2d3a1a293e69c3e079ea2cecc00211f35e6bce10474ca3ad6e30b59e26118" + -+ "37463f20969c5bc95282965a051a88f8cdf2e166549fcdd"; -+ const unicode512Hash = -+ "2b16a5561af8ad6fe414cc103fc8036492e1fc6d9aabe1b655497054f760fe0e34c5d100ac773d" + -+ "9f3030438284f22dbfa20cb2e9b019f2c98dfe38ce1ef41bae"; -+ -+ const regularMd5 = "5eceffa53a5fd58c44134211e2c5f522"; -+ const utf8Md5 = "3abc9433c09551b939c80aa0aa3174e1"; -+ const unicodeMd5 = "85ae134072c8d81257933f7045ba17ca"; -+ -+ testHash("sha1", regular1Hash, utf81Hash, unicode1Hash); -+ testHash("sha256", regular256Hash, utf8256Hash, unicode256Hash); -+ testHash("sha512", regular512Hash, utf8512Hash, unicode512Hash); -+ testHash("md5", regularMd5, utf8Md5, unicodeMd5); -+ }); -+ -+ describe("hmac", () => { -+ testHmac("sha1", Sha1Mac); -+ testHmac("sha256", Sha256Mac); -+ testHmac("sha512", Sha512Mac); -+ }); -+ -+ describe("compare", () => { -+ testCompare(false); -+ }); -+ -+ describe("hmacFast", () => { -+ testHmac("sha1", Sha1Mac, true); -+ testHmac("sha256", Sha256Mac, true); -+ testHmac("sha512", Sha512Mac, true); -+ }); -+ -+ describe("compareFast", () => { -+ testCompare(true); -+ }); -+ -+ describe("aesEncrypt", () => { -+ it("should successfully encrypt data", async () => { -+ const nodeCryptoFunctionService = new NodeCryptoFunctionService(); -+ const iv = makeStaticByteArray(16); -+ const key = makeStaticByteArray(32); -+ const data = Utils.fromUtf8ToArray("EncryptMe!"); -+ const encValue = await nodeCryptoFunctionService.aesEncrypt( -+ data.buffer, -+ iv.buffer, -+ key.buffer -+ ); -+ expect(Utils.fromBufferToB64(encValue)).toBe("ByUF8vhyX4ddU9gcooznwA=="); -+ }); -+ -+ it("should successfully encrypt and then decrypt data", async () => { -+ const nodeCryptoFunctionService = new NodeCryptoFunctionService(); -+ const iv = makeStaticByteArray(16); -+ const key = makeStaticByteArray(32); -+ const value = "EncryptMe!"; -+ const data = Utils.fromUtf8ToArray(value); -+ const encValue = await nodeCryptoFunctionService.aesEncrypt( -+ data.buffer, -+ iv.buffer, -+ key.buffer -+ ); -+ const decValue = await nodeCryptoFunctionService.aesDecrypt(encValue, iv.buffer, key.buffer); -+ expect(Utils.fromBufferToUtf8(decValue)).toBe(value); -+ }); -+ }); -+ -+ describe("aesDecryptFast", () => { -+ it("should successfully decrypt data", async () => { -+ const nodeCryptoFunctionService = new NodeCryptoFunctionService(); -+ const iv = Utils.fromBufferToB64(makeStaticByteArray(16).buffer); -+ const symKey = new SymmetricCryptoKey(makeStaticByteArray(32).buffer); -+ const data = "ByUF8vhyX4ddU9gcooznwA=="; -+ const params = nodeCryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey); -+ const decValue = await nodeCryptoFunctionService.aesDecryptFast(params); -+ expect(decValue).toBe("EncryptMe!"); -+ }); -+ }); -+ -+ describe("aesDecrypt", () => { -+ it("should successfully decrypt data", async () => { -+ const nodeCryptoFunctionService = new NodeCryptoFunctionService(); -+ const iv = makeStaticByteArray(16); -+ const key = makeStaticByteArray(32); -+ const data = Utils.fromB64ToArray("ByUF8vhyX4ddU9gcooznwA=="); -+ const decValue = await nodeCryptoFunctionService.aesDecrypt( -+ data.buffer, -+ iv.buffer, -+ key.buffer -+ ); -+ expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!"); -+ }); -+ }); -+ -+ describe("rsaEncrypt", () => { -+ it("should successfully encrypt and then decrypt data", async () => { -+ const nodeCryptoFunctionService = new NodeCryptoFunctionService(); -+ const pubKey = Utils.fromB64ToArray(RsaPublicKey); -+ const privKey = Utils.fromB64ToArray(RsaPrivateKey); -+ const value = "EncryptMe!"; -+ const data = Utils.fromUtf8ToArray(value); -+ const encValue = await nodeCryptoFunctionService.rsaEncrypt( -+ data.buffer, -+ pubKey.buffer, -+ "sha1" -+ ); -+ const decValue = await nodeCryptoFunctionService.rsaDecrypt(encValue, privKey.buffer, "sha1"); -+ expect(Utils.fromBufferToUtf8(decValue)).toBe(value); -+ }); -+ }); -+ -+ describe("rsaDecrypt", () => { -+ it("should successfully decrypt data", async () => { -+ const nodeCryptoFunctionService = new NodeCryptoFunctionService(); -+ const privKey = Utils.fromB64ToArray(RsaPrivateKey); -+ const data = Utils.fromB64ToArray( -+ "A1/p8BQzN9UrbdYxUY2Va5+kPLyfZXF9JsZrjeEXcaclsnHurdxVAJcnbEqYMP3UXV" + -+ "4YAS/mpf+Rxe6/X0WS1boQdA0MAHSgx95hIlAraZYpiMLLiJRKeo2u8YivCdTM9V5vuAEJwf9Tof/qFsFci3sApdbATkorCT" + -+ "zFOIEPF2S1zgperEP23M01mr4dWVdYN18B32YF67xdJHMbFhp5dkQwv9CmscoWq7OE5HIfOb+JAh7BEZb+CmKhM3yWJvoR/D" + -+ "/5jcercUtK2o+XrzNrL4UQ7yLZcFz6Bfwb/j6ICYvqd/YJwXNE6dwlL57OfwJyCdw2rRYf0/qI00t9u8Iitw==" -+ ); -+ const decValue = await nodeCryptoFunctionService.rsaDecrypt( -+ data.buffer, -+ privKey.buffer, -+ "sha1" -+ ); -+ expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!"); -+ }); -+ }); -+ -+ describe("rsaExtractPublicKey", () => { -+ it("should successfully extract key", async () => { -+ const nodeCryptoFunctionService = new NodeCryptoFunctionService(); -+ const privKey = Utils.fromB64ToArray(RsaPrivateKey); -+ const publicKey = await nodeCryptoFunctionService.rsaExtractPublicKey(privKey.buffer); -+ expect(Utils.fromBufferToB64(publicKey)).toBe(RsaPublicKey); -+ }); -+ }); -+ -+ describe("rsaGenerateKeyPair", () => { -+ testRsaGenerateKeyPair(1024); -+ testRsaGenerateKeyPair(2048); -+ -+ // Generating 4096 bit keys is really slow with Forge lib. -+ // Maybe move to something else if we ever want to generate keys of this size. -+ // testRsaGenerateKeyPair(4096); -+ }); -+ -+ describe("randomBytes", () => { -+ it("should make a value of the correct length", async () => { -+ const nodeCryptoFunctionService = new NodeCryptoFunctionService(); -+ const randomData = await nodeCryptoFunctionService.randomBytes(16); -+ expect(randomData.byteLength).toBe(16); -+ }); -+ -+ it("should not make the same value twice", async () => { -+ const nodeCryptoFunctionService = new NodeCryptoFunctionService(); -+ const randomData = await nodeCryptoFunctionService.randomBytes(16); -+ const randomData2 = await nodeCryptoFunctionService.randomBytes(16); -+ expect( -+ randomData.byteLength === randomData2.byteLength && randomData !== randomData2 -+ ).toBeTruthy(); -+ }); -+ }); - }); - --function testPbkdf2(algorithm: 'sha256' | 'sha512', regularKey: string, utf8Key: string, unicodeKey: string) { -- const regularEmail = 'user@example.com'; -- const utf8Email = 'üser@example.com'; -- -- const regularPassword = 'password'; -- const utf8Password = 'pǻssword'; -- const unicodePassword = '😀password🙏'; -- -- it('should create valid ' + algorithm + ' key from regular input', async () => { -- const cryptoFunctionService = new NodeCryptoFunctionService(); -- const key = await cryptoFunctionService.pbkdf2(regularPassword, regularEmail, algorithm, 5000); -- expect(Utils.fromBufferToB64(key)).toBe(regularKey); -- }); -- -- it('should create valid ' + algorithm + ' key from utf8 input', async () => { -- const cryptoFunctionService = new NodeCryptoFunctionService(); -- const key = await cryptoFunctionService.pbkdf2(utf8Password, utf8Email, algorithm, 5000); -- expect(Utils.fromBufferToB64(key)).toBe(utf8Key); -- }); -- -- it('should create valid ' + algorithm + ' key from unicode input', async () => { -- const cryptoFunctionService = new NodeCryptoFunctionService(); -- const key = await cryptoFunctionService.pbkdf2(unicodePassword, regularEmail, algorithm, 5000); -- expect(Utils.fromBufferToB64(key)).toBe(unicodeKey); -- }); -- -- it('should create valid ' + algorithm + ' key from array buffer input', async () => { -- const cryptoFunctionService = new NodeCryptoFunctionService(); -- const key = await cryptoFunctionService.pbkdf2(Utils.fromUtf8ToArray(regularPassword).buffer, -- Utils.fromUtf8ToArray(regularEmail).buffer, algorithm, 5000); -- expect(Utils.fromBufferToB64(key)).toBe(regularKey); -- }); -+function testPbkdf2( -+ algorithm: "sha256" | "sha512", -+ regularKey: string, -+ utf8Key: string, -+ unicodeKey: string -+) { -+ const regularEmail = "user@example.com"; -+ const utf8Email = "üser@example.com"; -+ -+ const regularPassword = "password"; -+ const utf8Password = "pǻssword"; -+ const unicodePassword = "😀password🙏"; -+ -+ it("should create valid " + algorithm + " key from regular input", async () => { -+ const cryptoFunctionService = new NodeCryptoFunctionService(); -+ const key = await cryptoFunctionService.pbkdf2(regularPassword, regularEmail, algorithm, 5000); -+ expect(Utils.fromBufferToB64(key)).toBe(regularKey); -+ }); -+ -+ it("should create valid " + algorithm + " key from utf8 input", async () => { -+ const cryptoFunctionService = new NodeCryptoFunctionService(); -+ const key = await cryptoFunctionService.pbkdf2(utf8Password, utf8Email, algorithm, 5000); -+ expect(Utils.fromBufferToB64(key)).toBe(utf8Key); -+ }); -+ -+ it("should create valid " + algorithm + " key from unicode input", async () => { -+ const cryptoFunctionService = new NodeCryptoFunctionService(); -+ const key = await cryptoFunctionService.pbkdf2(unicodePassword, regularEmail, algorithm, 5000); -+ expect(Utils.fromBufferToB64(key)).toBe(unicodeKey); -+ }); -+ -+ it("should create valid " + algorithm + " key from array buffer input", async () => { -+ const cryptoFunctionService = new NodeCryptoFunctionService(); -+ const key = await cryptoFunctionService.pbkdf2( -+ Utils.fromUtf8ToArray(regularPassword).buffer, -+ Utils.fromUtf8ToArray(regularEmail).buffer, -+ algorithm, -+ 5000 -+ ); -+ expect(Utils.fromBufferToB64(key)).toBe(regularKey); -+ }); - } - --function testHkdf(algorithm: 'sha256' | 'sha512', regularKey: string, utf8Key: string, unicodeKey: string) { -- const ikm = Utils.fromB64ToArray('criAmKtfzxanbgea5/kelQ=='); -- -- const regularSalt = 'salt'; -- const utf8Salt = 'üser_salt'; -- const unicodeSalt = '😀salt🙏'; -- -- const regularInfo = 'info'; -- const utf8Info = 'üser_info'; -- const unicodeInfo = '😀info🙏'; -- -- it('should create valid ' + algorithm + ' key from regular input', async () => { -- const cryptoFunctionService = new NodeCryptoFunctionService(); -- const key = await cryptoFunctionService.hkdf(ikm, regularSalt, regularInfo, 32, algorithm); -- expect(Utils.fromBufferToB64(key)).toBe(regularKey); -- }); -- -- it('should create valid ' + algorithm + ' key from utf8 input', async () => { -- const cryptoFunctionService = new NodeCryptoFunctionService(); -- const key = await cryptoFunctionService.hkdf(ikm, utf8Salt, utf8Info, 32, algorithm); -- expect(Utils.fromBufferToB64(key)).toBe(utf8Key); -- }); -- -- it('should create valid ' + algorithm + ' key from unicode input', async () => { -- const cryptoFunctionService = new NodeCryptoFunctionService(); -- const key = await cryptoFunctionService.hkdf(ikm, unicodeSalt, unicodeInfo, 32, algorithm); -- expect(Utils.fromBufferToB64(key)).toBe(unicodeKey); -- }); -- -- it('should create valid ' + algorithm + ' key from array buffer input', async () => { -- const cryptoFunctionService = new NodeCryptoFunctionService(); -- const key = await cryptoFunctionService.hkdf(ikm, Utils.fromUtf8ToArray(regularSalt).buffer, -- Utils.fromUtf8ToArray(regularInfo).buffer, 32, algorithm); -- expect(Utils.fromBufferToB64(key)).toBe(regularKey); -- }); -+function testHkdf( -+ algorithm: "sha256" | "sha512", -+ regularKey: string, -+ utf8Key: string, -+ unicodeKey: string -+) { -+ const ikm = Utils.fromB64ToArray("criAmKtfzxanbgea5/kelQ=="); -+ -+ const regularSalt = "salt"; -+ const utf8Salt = "üser_salt"; -+ const unicodeSalt = "😀salt🙏"; -+ -+ const regularInfo = "info"; -+ const utf8Info = "üser_info"; -+ const unicodeInfo = "😀info🙏"; -+ -+ it("should create valid " + algorithm + " key from regular input", async () => { -+ const cryptoFunctionService = new NodeCryptoFunctionService(); -+ const key = await cryptoFunctionService.hkdf(ikm, regularSalt, regularInfo, 32, algorithm); -+ expect(Utils.fromBufferToB64(key)).toBe(regularKey); -+ }); -+ -+ it("should create valid " + algorithm + " key from utf8 input", async () => { -+ const cryptoFunctionService = new NodeCryptoFunctionService(); -+ const key = await cryptoFunctionService.hkdf(ikm, utf8Salt, utf8Info, 32, algorithm); -+ expect(Utils.fromBufferToB64(key)).toBe(utf8Key); -+ }); -+ -+ it("should create valid " + algorithm + " key from unicode input", async () => { -+ const cryptoFunctionService = new NodeCryptoFunctionService(); -+ const key = await cryptoFunctionService.hkdf(ikm, unicodeSalt, unicodeInfo, 32, algorithm); -+ expect(Utils.fromBufferToB64(key)).toBe(unicodeKey); -+ }); -+ -+ it("should create valid " + algorithm + " key from array buffer input", async () => { -+ const cryptoFunctionService = new NodeCryptoFunctionService(); -+ const key = await cryptoFunctionService.hkdf( -+ ikm, -+ Utils.fromUtf8ToArray(regularSalt).buffer, -+ Utils.fromUtf8ToArray(regularInfo).buffer, -+ 32, -+ algorithm -+ ); -+ expect(Utils.fromBufferToB64(key)).toBe(regularKey); -+ }); - } - --function testHkdfExpand(algorithm: 'sha256' | 'sha512', b64prk: string, outputByteSize: number, -- b64ExpectedOkm: string) { -- const info = 'info'; -- -- it('should create valid ' + algorithm + ' ' + outputByteSize + ' byte okm', async () => { -- const cryptoFunctionService = new NodeCryptoFunctionService(); -- const okm = await cryptoFunctionService.hkdfExpand(Utils.fromB64ToArray(b64prk), info, outputByteSize, -- algorithm); -- expect(Utils.fromBufferToB64(okm)).toBe(b64ExpectedOkm); -- }); -+function testHkdfExpand( -+ algorithm: "sha256" | "sha512", -+ b64prk: string, -+ outputByteSize: number, -+ b64ExpectedOkm: string -+) { -+ const info = "info"; -+ -+ it("should create valid " + algorithm + " " + outputByteSize + " byte okm", async () => { -+ const cryptoFunctionService = new NodeCryptoFunctionService(); -+ const okm = await cryptoFunctionService.hkdfExpand( -+ Utils.fromB64ToArray(b64prk), -+ info, -+ outputByteSize, -+ algorithm -+ ); -+ expect(Utils.fromBufferToB64(okm)).toBe(b64ExpectedOkm); -+ }); - } - --function testHash(algorithm: 'sha1' | 'sha256' | 'sha512' | 'md5', regularHash: string, -- utf8Hash: string, unicodeHash: string) { -- const regularValue = 'HashMe!!'; -- const utf8Value = 'HǻshMe!!'; -- const unicodeValue = '😀HashMe!!!🙏'; -- -- it('should create valid ' + algorithm + ' hash from regular input', async () => { -- const cryptoFunctionService = new NodeCryptoFunctionService(); -- const hash = await cryptoFunctionService.hash(regularValue, algorithm); -- expect(Utils.fromBufferToHex(hash)).toBe(regularHash); -- }); -- -- it('should create valid ' + algorithm + ' hash from utf8 input', async () => { -- const cryptoFunctionService = new NodeCryptoFunctionService(); -- const hash = await cryptoFunctionService.hash(utf8Value, algorithm); -- expect(Utils.fromBufferToHex(hash)).toBe(utf8Hash); -- }); -- -- it('should create valid ' + algorithm + ' hash from unicode input', async () => { -- const cryptoFunctionService = new NodeCryptoFunctionService(); -- const hash = await cryptoFunctionService.hash(unicodeValue, algorithm); -- expect(Utils.fromBufferToHex(hash)).toBe(unicodeHash); -- }); -- -- it('should create valid ' + algorithm + ' hash from array buffer input', async () => { -- const cryptoFunctionService = new NodeCryptoFunctionService(); -- const hash = await cryptoFunctionService.hash(Utils.fromUtf8ToArray(regularValue).buffer, algorithm); -- expect(Utils.fromBufferToHex(hash)).toBe(regularHash); -- }); -+function testHash( -+ algorithm: "sha1" | "sha256" | "sha512" | "md5", -+ regularHash: string, -+ utf8Hash: string, -+ unicodeHash: string -+) { -+ const regularValue = "HashMe!!"; -+ const utf8Value = "HǻshMe!!"; -+ const unicodeValue = "😀HashMe!!!🙏"; -+ -+ it("should create valid " + algorithm + " hash from regular input", async () => { -+ const cryptoFunctionService = new NodeCryptoFunctionService(); -+ const hash = await cryptoFunctionService.hash(regularValue, algorithm); -+ expect(Utils.fromBufferToHex(hash)).toBe(regularHash); -+ }); -+ -+ it("should create valid " + algorithm + " hash from utf8 input", async () => { -+ const cryptoFunctionService = new NodeCryptoFunctionService(); -+ const hash = await cryptoFunctionService.hash(utf8Value, algorithm); -+ expect(Utils.fromBufferToHex(hash)).toBe(utf8Hash); -+ }); -+ -+ it("should create valid " + algorithm + " hash from unicode input", async () => { -+ const cryptoFunctionService = new NodeCryptoFunctionService(); -+ const hash = await cryptoFunctionService.hash(unicodeValue, algorithm); -+ expect(Utils.fromBufferToHex(hash)).toBe(unicodeHash); -+ }); -+ -+ it("should create valid " + algorithm + " hash from array buffer input", async () => { -+ const cryptoFunctionService = new NodeCryptoFunctionService(); -+ const hash = await cryptoFunctionService.hash( -+ Utils.fromUtf8ToArray(regularValue).buffer, -+ algorithm -+ ); -+ expect(Utils.fromBufferToHex(hash)).toBe(regularHash); -+ }); - } - --function testHmac(algorithm: 'sha1' | 'sha256' | 'sha512', mac: string, fast = false) { -- it('should create valid ' + algorithm + ' hmac', async () => { -- const cryptoFunctionService = new NodeCryptoFunctionService(); -- const value = Utils.fromUtf8ToArray('SignMe!!').buffer; -- const key = Utils.fromUtf8ToArray('secretkey').buffer; -- let computedMac: ArrayBuffer = null; -- if (fast) { -- computedMac = await cryptoFunctionService.hmacFast(value, key, algorithm); -- } else { -- computedMac = await cryptoFunctionService.hmac(value, key, algorithm); -- } -- expect(Utils.fromBufferToHex(computedMac)).toBe(mac); -- }); -+function testHmac(algorithm: "sha1" | "sha256" | "sha512", mac: string, fast = false) { -+ it("should create valid " + algorithm + " hmac", async () => { -+ const cryptoFunctionService = new NodeCryptoFunctionService(); -+ const value = Utils.fromUtf8ToArray("SignMe!!").buffer; -+ const key = Utils.fromUtf8ToArray("secretkey").buffer; -+ let computedMac: ArrayBuffer = null; -+ if (fast) { -+ computedMac = await cryptoFunctionService.hmacFast(value, key, algorithm); -+ } else { -+ computedMac = await cryptoFunctionService.hmac(value, key, algorithm); -+ } -+ expect(Utils.fromBufferToHex(computedMac)).toBe(mac); -+ }); - } - - function testCompare(fast = false) { -- it('should successfully compare two of the same values', async () => { -- const cryptoFunctionService = new NodeCryptoFunctionService(); -- const a = new Uint8Array(2); -- a[0] = 1; -- a[1] = 2; -- const equal = fast ? await cryptoFunctionService.compareFast(a.buffer, a.buffer) : -- await cryptoFunctionService.compare(a.buffer, a.buffer); -- expect(equal).toBe(true); -- }); -- -- it('should successfully compare two different values of the same length', async () => { -- const cryptoFunctionService = new NodeCryptoFunctionService(); -- const a = new Uint8Array(2); -- a[0] = 1; -- a[1] = 2; -- const b = new Uint8Array(2); -- b[0] = 3; -- b[1] = 4; -- const equal = fast ? await cryptoFunctionService.compareFast(a.buffer, b.buffer) : -- await cryptoFunctionService.compare(a.buffer, b.buffer); -- expect(equal).toBe(false); -- }); -- -- it('should successfully compare two different values of different lengths', async () => { -- const cryptoFunctionService = new NodeCryptoFunctionService(); -- const a = new Uint8Array(2); -- a[0] = 1; -- a[1] = 2; -- const b = new Uint8Array(2); -- b[0] = 3; -- const equal = fast ? await cryptoFunctionService.compareFast(a.buffer, b.buffer) : -- await cryptoFunctionService.compare(a.buffer, b.buffer); -- expect(equal).toBe(false); -- }); -+ it("should successfully compare two of the same values", async () => { -+ const cryptoFunctionService = new NodeCryptoFunctionService(); -+ const a = new Uint8Array(2); -+ a[0] = 1; -+ a[1] = 2; -+ const equal = fast -+ ? await cryptoFunctionService.compareFast(a.buffer, a.buffer) -+ : await cryptoFunctionService.compare(a.buffer, a.buffer); -+ expect(equal).toBe(true); -+ }); -+ -+ it("should successfully compare two different values of the same length", async () => { -+ const cryptoFunctionService = new NodeCryptoFunctionService(); -+ const a = new Uint8Array(2); -+ a[0] = 1; -+ a[1] = 2; -+ const b = new Uint8Array(2); -+ b[0] = 3; -+ b[1] = 4; -+ const equal = fast -+ ? await cryptoFunctionService.compareFast(a.buffer, b.buffer) -+ : await cryptoFunctionService.compare(a.buffer, b.buffer); -+ expect(equal).toBe(false); -+ }); -+ -+ it("should successfully compare two different values of different lengths", async () => { -+ const cryptoFunctionService = new NodeCryptoFunctionService(); -+ const a = new Uint8Array(2); -+ a[0] = 1; -+ a[1] = 2; -+ const b = new Uint8Array(2); -+ b[0] = 3; -+ const equal = fast -+ ? await cryptoFunctionService.compareFast(a.buffer, b.buffer) -+ : await cryptoFunctionService.compare(a.buffer, b.buffer); -+ expect(equal).toBe(false); -+ }); - } - - function testRsaGenerateKeyPair(length: 1024 | 2048 | 4096) { -- it('should successfully generate a ' + length + ' bit key pair', async () => { -- const cryptoFunctionService = new NodeCryptoFunctionService(); -- const keyPair = await cryptoFunctionService.rsaGenerateKeyPair(length); -- expect(keyPair[0] == null || keyPair[1] == null).toBe(false); -- const publicKey = await cryptoFunctionService.rsaExtractPublicKey(keyPair[1]); -- expect(Utils.fromBufferToB64(keyPair[0])).toBe(Utils.fromBufferToB64(publicKey)); -- }, 30000); -+ it( -+ "should successfully generate a " + length + " bit key pair", -+ async () => { -+ const cryptoFunctionService = new NodeCryptoFunctionService(); -+ const keyPair = await cryptoFunctionService.rsaGenerateKeyPair(length); -+ expect(keyPair[0] == null || keyPair[1] == null).toBe(false); -+ const publicKey = await cryptoFunctionService.rsaExtractPublicKey(keyPair[1]); -+ expect(Utils.fromBufferToB64(keyPair[0])).toBe(Utils.fromBufferToB64(publicKey)); -+ }, -+ 30000 -+ ); - } - - function makeStaticByteArray(length: number) { -- const arr = new Uint8Array(length); -- for (let i = 0; i < length; i++) { -- arr[i] = i; -- } -- return arr; -+ const arr = new Uint8Array(length); -+ for (let i = 0; i < length; i++) { -+ arr[i] = i; -+ } -+ return arr; - } -diff --git a/jslib/spec/support/jasmine.json b/jslib/spec/support/jasmine.json -index 96a0fe21..9a91e2f0 100644 ---- a/jslib/spec/support/jasmine.json -+++ b/jslib/spec/support/jasmine.json -@@ -1,13 +1,7 @@ - { -- "spec_dir": "dist/spec", -- "spec_files": [ -- "common/**/*[sS]pec.js", -- "node/**/*[sS]pec.js", -- "electron/**/*[sS]pec.js" -- ], -- "helpers": [ -- "helpers.js" -- ], -+ "spec_dir": "spec", -+ "spec_files": ["common/**/*[sS]pec.ts", "node/**/*[sS]pec.ts", "electron/**/*[sS]pec.ts"], -+ "helpers": ["helpers.ts"], - "stopSpecOnExpectationFailure": false, - "random": true - } -diff --git a/jslib/spec/support/karma.conf.js b/jslib/spec/support/karma.conf.js -index 471a3b07..3b5baa28 100644 ---- a/jslib/spec/support/karma.conf.js -+++ b/jslib/spec/support/karma.conf.js -@@ -1,98 +1,98 @@ - module.exports = (config) => { -- config.set({ -- // base path that will be used to resolve all patterns (eg. files, exclude) -- basePath: '../../', -+ config.set({ -+ // base path that will be used to resolve all patterns (eg. files, exclude) -+ basePath: "../../", - -- // frameworks to use -- // available frameworks: https://npmjs.org/browse/keyword/karma-adapter -- frameworks: ['jasmine', 'detectBrowsers'], -+ // frameworks to use -+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter -+ frameworks: ["jasmine", "detectBrowsers"], - -- // list of files / patterns to load in the browser -- files: [ -- { pattern: 'spec/utils.ts', watched: false }, -- { pattern: 'spec/common/**/*.ts', watched: false }, -- { pattern: 'spec/web/**/*.ts', watched: false }, -- ], -+ // list of files / patterns to load in the browser -+ files: [ -+ { pattern: "spec/utils.ts", watched: false }, -+ { pattern: "spec/common/**/*.ts", watched: false }, -+ { pattern: "spec/web/**/*.ts", watched: false }, -+ ], - -- // list of files to exclude -- exclude: [ -- ], -+ // list of files to exclude -+ exclude: [], - -- // preprocess matching files before serving them to the browser -- // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor -- preprocessors: { -- 'spec/**/*.ts': 'webpack' -- }, -+ // preprocess matching files before serving them to the browser -+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor -+ preprocessors: { -+ "spec/**/*.ts": "webpack", -+ }, - -- // test results reporter to use -- // possible values: 'dots', 'progress' -- // available reporters: https://npmjs.org/browse/keyword/karma-reporter -- reporters: ['progress', 'kjhtml'], -+ // test results reporter to use -+ // possible values: 'dots', 'progress' -+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter -+ reporters: ["progress", "kjhtml"], - -- // web server port -- port: 9876, -+ // web server port -+ port: 9876, - -- // enable / disable colors in the output (reporters and logs) -- colors: true, -+ // enable / disable colors in the output (reporters and logs) -+ colors: true, - -- // level of logging -- // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG -- logLevel: config.LOG_INFO, -+ // level of logging -+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG -+ logLevel: config.LOG_INFO, - -- // Concurrency level -- // how many browser should be started simultaneous -- concurrency: Infinity, -+ // Concurrency level -+ // how many browser should be started simultaneous -+ concurrency: Infinity, - -- client: { -- clearContext: false // leave Jasmine Spec Runner output visible in browser -- }, -+ client: { -+ clearContext: false, // leave Jasmine Spec Runner output visible in browser -+ }, - -- webpack: { -- resolve: { -- extensions: ['.js', '.ts', '.tsx'], -- }, -- module: { -- rules: [ -- { -- test: /\.tsx?$/, -- loader: 'ts-loader', -- options: { -- compiler: 'ttypescript' -- }, -- }, -- ], -+ webpack: { -+ resolve: { -+ extensions: [".js", ".ts", ".tsx"], -+ }, -+ module: { -+ rules: [ -+ { -+ test: /\.tsx?$/, -+ loader: "ts-loader", -+ options: { -+ compiler: "ttypescript", - }, -- stats: { -- colors: true, -- modules: true, -- reasons: true, -- errorDetails: true, -- }, -- devtool: 'inline-source-map', -- }, -+ }, -+ ], -+ }, -+ stats: { -+ colors: true, -+ modules: true, -+ reasons: true, -+ errorDetails: true, -+ }, -+ devtool: "inline-source-map", -+ }, - -- detectBrowsers: { -- usePhantomJS: false, -- postDetection: (availableBrowsers) => { -- const result = availableBrowsers; -- function removeBrowser(browser) { -- if (availableBrowsers.length > 1 && availableBrowsers.indexOf(browser) > -1) { -- result.splice(result.indexOf(browser), 1); -- } -- } -+ detectBrowsers: { -+ usePhantomJS: false, -+ postDetection: (availableBrowsers) => { -+ const result = availableBrowsers; -+ function removeBrowser(browser) { -+ if (availableBrowsers.length > 1 && availableBrowsers.indexOf(browser) > -1) { -+ result.splice(result.indexOf(browser), 1); -+ } -+ } +diff --git a/jslib/angular/src/components/register.component.ts b/jslib/angular/src/components/register.component.ts +index fd91af29..abcfd62c 100644 +--- a/jslib/angular/src/components/register.component.ts ++++ b/jslib/angular/src/components/register.component.ts +@@ -30,7 +30,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn + formPromise: Promise; + masterPasswordScore: number; + referenceData: ReferenceEventRequest; +- showTerms = true; ++ showTerms = false; + acceptPolicies: boolean = false; + + protected successRoute = 'login'; +@@ -43,7 +43,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn + protected passwordGenerationService: PasswordGenerationService, environmentService: EnvironmentService, + protected logService: LogService) { + super(environmentService, i18nService, platformUtilsService); +- this.showTerms = !platformUtilsService.isSelfHost(); ++ this.showTerms = false; + } -- removeBrowser('IE'); -- removeBrowser('Opera'); -- removeBrowser('SafariTechPreview'); -+ removeBrowser("IE"); -+ removeBrowser("Opera"); -+ removeBrowser("SafariTechPreview"); + async ngOnInit() { +@@ -81,6 +81,12 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn + } -- var githubAction = process.env.GITHUB_WORKFLOW != null && process.env.GITHUB_WORKFLOW !== ''; -- if (githubAction) { -- removeBrowser('Firefox'); -- removeBrowser('Safari'); -- } -+ var githubAction = -+ process.env.GITHUB_WORKFLOW != null && process.env.GITHUB_WORKFLOW !== ""; -+ if (githubAction) { -+ removeBrowser("Firefox"); -+ removeBrowser("Safari"); + async submit() { ++ if (typeof crypto.subtle === 'undefined') { ++ this.platformUtilsService.showToast('error', "This browser requires HTTPS to use the web vault", ++ "Check the Vaultwarden wiki for details on how to enable it"); ++ return; + } - -- return result; -- } -- }, -- }) --} -+ return result; -+ }, -+ }, -+ }); -+}; -diff --git a/jslib/spec/utils.ts b/jslib/spec/utils.ts -index 4df925ed..00cdcd3f 100644 ---- a/jslib/spec/utils.ts -+++ b/jslib/spec/utils.ts -@@ -1,16 +1,19 @@ - function newGuid() { -- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { -- // tslint:disable:no-bitwise -- const r = Math.random() * 16 | 0; -- const v = c === 'x' ? r : (r & 0x3 | 0x8); -- return v.toString(16); -- }); -+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { -+ // tslint:disable:no-bitwise -+ const r = (Math.random() * 16) | 0; -+ const v = c === "x" ? r : (r & 0x3) | 0x8; -+ return v.toString(16); -+ }); - } - --export function GetUniqueString(prefix: string = '') { -- return prefix + '_' + newGuid(); -+export function GetUniqueString(prefix: string = "") { -+ return prefix + "_" + newGuid(); - } - --export function BuildTestObject(def: Partial> | T, constructor?: (new () => T)): T { -- return Object.assign(constructor === null ? {} : new constructor(), def) as T; -+export function BuildTestObject( -+ def: Partial> | T, -+ constructor?: new () => T -+): T { -+ return Object.assign(constructor === null ? {} : new constructor(), def) as T; - } -diff --git a/jslib/spec/web/services/webCryptoFunction.service.spec.ts b/jslib/spec/web/services/webCryptoFunction.service.spec.ts -index 28d1082e..88ea3c0d 100644 ---- a/jslib/spec/web/services/webCryptoFunction.service.spec.ts -+++ b/jslib/spec/web/services/webCryptoFunction.service.spec.ts -@@ -1,484 +1,567 @@ --import Substitute from '@fluffy-spoon/substitute'; -- --import { PlatformUtilsService } from 'jslib-common/abstractions/platformUtils.service'; -- --import { WebCryptoFunctionService } from 'jslib-common/services/webCryptoFunction.service'; -- --import { Utils } from 'jslib-common/misc/utils'; --import { SymmetricCryptoKey } from 'jslib-common/models/domain/symmetricCryptoKey'; -- --const RsaPublicKey = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP' + -- '4xlU2ab/v0crqIfXfIoWF/XXdHGIdrZeilnRXPPJT1B9dTsasttEZNnua/0Rek/cjNDHtzT52irfoZYS7X6HNIfOi54Q+egP' + -- 'RQ1H7iNHVZz3K8Db9GCSKPeC8MbW6gVCzb15esCe1gGzg6wkMuWYDFYPoh/oBqcIqrGah7firqB1nDedzEjw32heP2DAffVN' + -- '084iTDjiWrJNUxBJ2pDD5Z9dT3MzQ2s09ew1yMWK2z37rT3YerC7OgEDmo3WYo3xL3qYJznu3EO2nmrYjiRa40wKSjxsTlUc' + -- 'xDF+F0uMW8oR9EMUHgepdepfAtLsSAQIDAQAB'; --const RsaPrivateKey = 'MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXRVrCX+2hfOQS8Hz' + -- 'YUS2oc/jGVTZpv+/Ryuoh9d8ihYX9dd0cYh2tl6KWdFc88lPUH11Oxqy20Rk2e5r/RF6T9yM0Me3NPnaKt+hlhLtfoc0h86L' + -- 'nhD56A9FDUfuI0dVnPcrwNv0YJIo94LwxtbqBULNvXl6wJ7WAbODrCQy5ZgMVg+iH+gGpwiqsZqHt+KuoHWcN53MSPDfaF4/' + -- 'YMB99U3TziJMOOJask1TEEnakMPln11PczNDazT17DXIxYrbPfutPdh6sLs6AQOajdZijfEvepgnOe7cQ7aeatiOJFrjTApK' + -- 'PGxOVRzEMX4XS4xbyhH0QxQeB6l16l8C0uxIBAgMBAAECggEASaWfeVDA3cVzOPFSpvJm20OTE+R6uGOU+7vh36TX/POq92q' + -- 'Buwbd0h0oMD32FxsXywd2IxtBDUSiFM9699qufTVuM0Q3tZw6lHDTOVG08+tPdr8qSbMtw7PGFxN79fHLBxejjO4IrM9lapj' + -- 'WpxEF+11x7r+wM+0xRZQ8sNFYG46aPfIaty4BGbL0I2DQ2y8I57iBCAy69eht59NLMm27fRWGJIWCuBIjlpfzET1j2HLXUIh' + -- '5bTBNzqaN039WH49HczGE3mQKVEJZc/efk3HaVd0a1Sjzyn0QY+N1jtZN3jTRbuDWA1AknkX1LX/0tUhuS3/7C3ejHxjw4Dk' + -- '1ZLo5/QKBgQDIWvqFn0+IKRSu6Ua2hDsufIHHUNLelbfLUMmFthxabcUn4zlvIscJO00Tq/ezopSRRvbGiqnxjv/mYxucvOU' + -- 'BeZtlus0Q9RTACBtw9TGoNTmQbEunJ2FOSlqbQxkBBAjgGEppRPt30iGj/VjAhCATq2MYOa/X4dVR51BqQAFIEwKBgQDBSIf' + -- 'TFKC/hDk6FKZlgwvupWYJyU9RkyfstPErZFmzoKhPkQ3YORo2oeAYmVUbS9I2iIYpYpYQJHX8jMuCbCz4ONxTCuSIXYQYUcU' + -- 'q4PglCKp31xBAE6TN8SvhfME9/MvuDssnQinAHuF0GDAhF646T3LLS1not6Vszv7brwSoGwKBgQC88v/8cGfi80ssQZeMnVv' + -- 'q1UTXIeQcQnoY5lGHJl3K8mbS3TnXE6c9j417Fdz+rj8KWzBzwWXQB5pSPflWcdZO886Xu/mVGmy9RWgLuVFhXwCwsVEPjNX' + -- '5ramRb0/vY0yzenUCninBsIxFSbIfrPtLUYCc4hpxr+sr2Mg/y6jpvQKBgBezMRRs3xkcuXepuI2R+BCXL1/b02IJTUf1F+1' + -- 'eLLGd7YV0H+J3fgNc7gGWK51hOrF9JBZHBGeOUPlaukmPwiPdtQZpu4QNE3l37VlIpKTF30E6mb+BqR+nht3rUjarnMXgAoE' + -- 'Z18y6/KIjpSMpqC92Nnk/EBM9EYe6Cf4eA9ApAoGAeqEUg46UTlJySkBKURGpIs3v1kkf5I0X8DnOhwb+HPxNaiEdmO7ckm8' + -- '+tPVgppLcG0+tMdLjigFQiDUQk2y3WjyxP5ZvXu7U96jaJRI8PFMoE06WeVYcdIzrID2HvqH+w0UQJFrLJ/0Mn4stFAEzXKZ' + -- 'BokBGnjFnTnKcs7nv/O8='; -- --const Sha1Mac = '4d4c223f95dc577b665ec4ccbcb680b80a397038'; --const Sha256Mac = '6be3caa84922e12aaaaa2f16c40d44433bb081ef323db584eb616333ab4e874f'; --const Sha512Mac = '21910e341fa12106ca35758a2285374509326c9fbe0bd64e7b99c898f841dc948c58ce66d3504d8883c' + -- '5ea7817a0b7c5d4d9b00364ccd214669131fc17fe4aca'; -- --describe('WebCrypto Function Service', () => { -- describe('pbkdf2', () => { -- const regular256Key = 'pj9prw/OHPleXI6bRdmlaD+saJS4awrMiQsQiDjeu2I='; -- const utf8256Key = 'yqvoFXgMRmHR3QPYr5pyR4uVuoHkltv9aHUP63p8n7I='; -- const unicode256Key = 'ZdeOata6xoRpB4DLp8zHhXz5kLmkWtX5pd+TdRH8w8w='; -- -- const regular512Key = 'liTi/Ke8LPU1Qv+Vl7NGEVt/XMbsBVJ2kQxtVG/Z1/JFHFKQW3ZkI81qVlwTiCpb+cFXzs+57' + -- 'eyhhx5wfKo5Cg=='; -- const utf8512Key = 'df0KdvIBeCzD/kyXptwQohaqUa4e7IyFUyhFQjXCANu5T+scq55hCcE4dG4T/MhAk2exw8j7ixRN' + -- 'zXANiVZpnw=='; -- const unicode512Key = 'FE+AnUJaxv8jh+zUDtZz4mjjcYk0/PZDZm+SLJe3XtxtnpdqqpblX6JjuMZt/dYYNMOrb2+mD' + -- 'L3FiQDTROh1lg=='; -- -- testPbkdf2('sha256', regular256Key, utf8256Key, unicode256Key); -- testPbkdf2('sha512', regular512Key, utf8512Key, unicode512Key); -+import Substitute from "@fluffy-spoon/substitute"; -+ -+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service"; -+ -+import { WebCryptoFunctionService } from "jslib-common/services/webCryptoFunction.service"; -+ -+import { Utils } from "jslib-common/misc/utils"; -+import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey"; -+ -+const RsaPublicKey = -+ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP" + -+ "4xlU2ab/v0crqIfXfIoWF/XXdHGIdrZeilnRXPPJT1B9dTsasttEZNnua/0Rek/cjNDHtzT52irfoZYS7X6HNIfOi54Q+egP" + -+ "RQ1H7iNHVZz3K8Db9GCSKPeC8MbW6gVCzb15esCe1gGzg6wkMuWYDFYPoh/oBqcIqrGah7firqB1nDedzEjw32heP2DAffVN" + -+ "084iTDjiWrJNUxBJ2pDD5Z9dT3MzQ2s09ew1yMWK2z37rT3YerC7OgEDmo3WYo3xL3qYJznu3EO2nmrYjiRa40wKSjxsTlUc" + -+ "xDF+F0uMW8oR9EMUHgepdepfAtLsSAQIDAQAB"; -+const RsaPrivateKey = -+ "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXRVrCX+2hfOQS8Hz" + -+ "YUS2oc/jGVTZpv+/Ryuoh9d8ihYX9dd0cYh2tl6KWdFc88lPUH11Oxqy20Rk2e5r/RF6T9yM0Me3NPnaKt+hlhLtfoc0h86L" + -+ "nhD56A9FDUfuI0dVnPcrwNv0YJIo94LwxtbqBULNvXl6wJ7WAbODrCQy5ZgMVg+iH+gGpwiqsZqHt+KuoHWcN53MSPDfaF4/" + -+ "YMB99U3TziJMOOJask1TEEnakMPln11PczNDazT17DXIxYrbPfutPdh6sLs6AQOajdZijfEvepgnOe7cQ7aeatiOJFrjTApK" + -+ "PGxOVRzEMX4XS4xbyhH0QxQeB6l16l8C0uxIBAgMBAAECggEASaWfeVDA3cVzOPFSpvJm20OTE+R6uGOU+7vh36TX/POq92q" + -+ "Buwbd0h0oMD32FxsXywd2IxtBDUSiFM9699qufTVuM0Q3tZw6lHDTOVG08+tPdr8qSbMtw7PGFxN79fHLBxejjO4IrM9lapj" + -+ "WpxEF+11x7r+wM+0xRZQ8sNFYG46aPfIaty4BGbL0I2DQ2y8I57iBCAy69eht59NLMm27fRWGJIWCuBIjlpfzET1j2HLXUIh" + -+ "5bTBNzqaN039WH49HczGE3mQKVEJZc/efk3HaVd0a1Sjzyn0QY+N1jtZN3jTRbuDWA1AknkX1LX/0tUhuS3/7C3ejHxjw4Dk" + -+ "1ZLo5/QKBgQDIWvqFn0+IKRSu6Ua2hDsufIHHUNLelbfLUMmFthxabcUn4zlvIscJO00Tq/ezopSRRvbGiqnxjv/mYxucvOU" + -+ "BeZtlus0Q9RTACBtw9TGoNTmQbEunJ2FOSlqbQxkBBAjgGEppRPt30iGj/VjAhCATq2MYOa/X4dVR51BqQAFIEwKBgQDBSIf" + -+ "TFKC/hDk6FKZlgwvupWYJyU9RkyfstPErZFmzoKhPkQ3YORo2oeAYmVUbS9I2iIYpYpYQJHX8jMuCbCz4ONxTCuSIXYQYUcU" + -+ "q4PglCKp31xBAE6TN8SvhfME9/MvuDssnQinAHuF0GDAhF646T3LLS1not6Vszv7brwSoGwKBgQC88v/8cGfi80ssQZeMnVv" + -+ "q1UTXIeQcQnoY5lGHJl3K8mbS3TnXE6c9j417Fdz+rj8KWzBzwWXQB5pSPflWcdZO886Xu/mVGmy9RWgLuVFhXwCwsVEPjNX" + -+ "5ramRb0/vY0yzenUCninBsIxFSbIfrPtLUYCc4hpxr+sr2Mg/y6jpvQKBgBezMRRs3xkcuXepuI2R+BCXL1/b02IJTUf1F+1" + -+ "eLLGd7YV0H+J3fgNc7gGWK51hOrF9JBZHBGeOUPlaukmPwiPdtQZpu4QNE3l37VlIpKTF30E6mb+BqR+nht3rUjarnMXgAoE" + -+ "Z18y6/KIjpSMpqC92Nnk/EBM9EYe6Cf4eA9ApAoGAeqEUg46UTlJySkBKURGpIs3v1kkf5I0X8DnOhwb+HPxNaiEdmO7ckm8" + -+ "+tPVgppLcG0+tMdLjigFQiDUQk2y3WjyxP5ZvXu7U96jaJRI8PFMoE06WeVYcdIzrID2HvqH+w0UQJFrLJ/0Mn4stFAEzXKZ" + -+ "BokBGnjFnTnKcs7nv/O8="; -+ -+const Sha1Mac = "4d4c223f95dc577b665ec4ccbcb680b80a397038"; -+const Sha256Mac = "6be3caa84922e12aaaaa2f16c40d44433bb081ef323db584eb616333ab4e874f"; -+const Sha512Mac = -+ "21910e341fa12106ca35758a2285374509326c9fbe0bd64e7b99c898f841dc948c58ce66d3504d8883c" + -+ "5ea7817a0b7c5d4d9b00364ccd214669131fc17fe4aca"; -+ -+describe("WebCrypto Function Service", () => { -+ describe("pbkdf2", () => { -+ const regular256Key = "pj9prw/OHPleXI6bRdmlaD+saJS4awrMiQsQiDjeu2I="; -+ const utf8256Key = "yqvoFXgMRmHR3QPYr5pyR4uVuoHkltv9aHUP63p8n7I="; -+ const unicode256Key = "ZdeOata6xoRpB4DLp8zHhXz5kLmkWtX5pd+TdRH8w8w="; -+ -+ const regular512Key = -+ "liTi/Ke8LPU1Qv+Vl7NGEVt/XMbsBVJ2kQxtVG/Z1/JFHFKQW3ZkI81qVlwTiCpb+cFXzs+57" + -+ "eyhhx5wfKo5Cg=="; -+ const utf8512Key = -+ "df0KdvIBeCzD/kyXptwQohaqUa4e7IyFUyhFQjXCANu5T+scq55hCcE4dG4T/MhAk2exw8j7ixRN" + -+ "zXANiVZpnw=="; -+ const unicode512Key = -+ "FE+AnUJaxv8jh+zUDtZz4mjjcYk0/PZDZm+SLJe3XtxtnpdqqpblX6JjuMZt/dYYNMOrb2+mD" + -+ "L3FiQDTROh1lg=="; -+ -+ testPbkdf2("sha256", regular256Key, utf8256Key, unicode256Key); -+ testPbkdf2("sha512", regular512Key, utf8512Key, unicode512Key); -+ }); -+ -+ describe("hkdf", () => { -+ const regular256Key = "qBUmEYtwTwwGPuw/z6bs/qYXXYNUlocFlyAuuANI8Pw="; -+ const utf8256Key = "6DfJwW1R3txgiZKkIFTvVAb7qVlG7lKcmJGJoxR2GBU="; -+ const unicode256Key = "gejGI82xthA+nKtKmIh82kjw+ttHr+ODsUoGdu5sf0A="; -+ -+ const regular512Key = "xe5cIG6ZfwGmb1FvsOedM0XKOm21myZkjL/eDeKIqqM="; -+ const utf8512Key = "XQMVBnxVEhlvjSFDQc77j5GDE9aorvbS0vKnjhRg0LY="; -+ const unicode512Key = "148GImrTbrjaGAe/iWEpclINM8Ehhko+9lB14+52lqc="; -+ -+ testHkdf("sha256", regular256Key, utf8256Key, unicode256Key); -+ testHkdf("sha512", regular512Key, utf8512Key, unicode512Key); -+ }); -+ -+ describe("hkdfExpand", () => { -+ const prk16Byte = "criAmKtfzxanbgea5/kelQ=="; -+ const prk32Byte = "F5h4KdYQnIVH4rKH0P9CZb1GrR4n16/sJrS0PsQEn0Y="; -+ const prk64Byte = -+ "ssBK0mRG17VHdtsgt8yo4v25CRNpauH+0r2fwY/E9rLyaFBAOMbIeTry+" + -+ "gUJ28p8y+hFh3EI9pcrEWaNvFYonQ=="; -+ -+ testHkdfExpand("sha256", prk32Byte, 32, "BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD8="); -+ testHkdfExpand( -+ "sha256", -+ prk32Byte, -+ 64, -+ "BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD9BV+" + -+ "/queOZenPNkDhmlVyL2WZ3OSU5+7ISNF5NhNfvZA==" -+ ); -+ testHkdfExpand("sha512", prk64Byte, 32, "uLWbMWodSBms5uGJ5WTRTesyW+MD7nlpCZvagvIRXlk="); -+ testHkdfExpand( -+ "sha512", -+ prk64Byte, -+ 64, -+ "uLWbMWodSBms5uGJ5WTRTesyW+MD7nlpCZvagvIRXlkY5Pv0sB+" + -+ "MqvaopmkC6sD/j89zDwTV9Ib2fpucUydO8w==" -+ ); -+ -+ it("should fail with prk too small", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const f = cryptoFunctionService.hkdfExpand( -+ Utils.fromB64ToArray(prk16Byte), -+ "info", -+ 32, -+ "sha256" -+ ); -+ await expectAsync(f).toBeRejectedWith(new Error("prk is too small.")); - }); - -- describe('hkdf', () => { -- const regular256Key = 'qBUmEYtwTwwGPuw/z6bs/qYXXYNUlocFlyAuuANI8Pw='; -- const utf8256Key = '6DfJwW1R3txgiZKkIFTvVAb7qVlG7lKcmJGJoxR2GBU='; -- const unicode256Key = 'gejGI82xthA+nKtKmIh82kjw+ttHr+ODsUoGdu5sf0A='; -- -- const regular512Key = 'xe5cIG6ZfwGmb1FvsOedM0XKOm21myZkjL/eDeKIqqM='; -- const utf8512Key = 'XQMVBnxVEhlvjSFDQc77j5GDE9aorvbS0vKnjhRg0LY='; -- const unicode512Key = '148GImrTbrjaGAe/iWEpclINM8Ehhko+9lB14+52lqc='; -- -- testHkdf('sha256', regular256Key, utf8256Key, unicode256Key); -- testHkdf('sha512', regular512Key, utf8512Key, unicode512Key); -+ it("should fail with outputByteSize is too large", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const f = cryptoFunctionService.hkdfExpand( -+ Utils.fromB64ToArray(prk32Byte), -+ "info", -+ 8161, -+ "sha256" -+ ); -+ await expectAsync(f).toBeRejectedWith(new Error("outputByteSize is too large.")); - }); -- -- describe('hkdfExpand', () => { -- const prk16Byte = 'criAmKtfzxanbgea5/kelQ=='; -- const prk32Byte = 'F5h4KdYQnIVH4rKH0P9CZb1GrR4n16/sJrS0PsQEn0Y='; -- const prk64Byte = 'ssBK0mRG17VHdtsgt8yo4v25CRNpauH+0r2fwY/E9rLyaFBAOMbIeTry+' + -- 'gUJ28p8y+hFh3EI9pcrEWaNvFYonQ=='; -- -- testHkdfExpand('sha256', prk32Byte, 32, 'BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD8='); -- testHkdfExpand('sha256', prk32Byte, 64, 'BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD9BV+' + -- '/queOZenPNkDhmlVyL2WZ3OSU5+7ISNF5NhNfvZA=='); -- testHkdfExpand('sha512', prk64Byte, 32, 'uLWbMWodSBms5uGJ5WTRTesyW+MD7nlpCZvagvIRXlk='); -- testHkdfExpand('sha512', prk64Byte, 64, 'uLWbMWodSBms5uGJ5WTRTesyW+MD7nlpCZvagvIRXlkY5Pv0sB+' + -- 'MqvaopmkC6sD/j89zDwTV9Ib2fpucUydO8w=='); -- -- it('should fail with prk too small', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const f = cryptoFunctionService.hkdfExpand(Utils.fromB64ToArray(prk16Byte), 'info', 32, 'sha256'); -- await expectAsync(f).toBeRejectedWith(new Error('prk is too small.')); -- }); -- -- it('should fail with outputByteSize is too large', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const f = cryptoFunctionService.hkdfExpand(Utils.fromB64ToArray(prk32Byte), 'info', 8161, 'sha256'); -- await expectAsync(f).toBeRejectedWith(new Error('outputByteSize is too large.')); -- }); -+ }); -+ -+ describe("hash", () => { -+ const regular1Hash = "2a241604fb921fad12bf877282457268e1dccb70"; -+ const utf81Hash = "85672798dc5831e96d6c48655d3d39365a9c88b6"; -+ const unicode1Hash = "39c975935054a3efc805a9709b60763a823a6ad4"; -+ -+ const regular256Hash = "2b8e96031d352a8655d733d7a930b5ffbea69dc25cf65c7bca7dd946278908b2"; -+ const utf8256Hash = "25fe8440f5b01ed113b0a0e38e721b126d2f3f77a67518c4a04fcde4e33eeb9d"; -+ const unicode256Hash = "adc1c0c2afd6e92cefdf703f9b6eb2c38e0d6d1a040c83f8505c561fea58852e"; -+ -+ const regular512Hash = -+ "c15cf11d43bde333647e3f559ec4193bb2edeaa0e8b902772f514cdf3f785a3f49a6e02a4b87b3" + -+ "b47523271ad45b7e0aebb5cdcc1bc54815d256eb5dcb80da9d"; -+ const utf8512Hash = -+ "035c31a877a291af09ed2d3a1a293e69c3e079ea2cecc00211f35e6bce10474ca3ad6e30b59e26118" + -+ "37463f20969c5bc95282965a051a88f8cdf2e166549fcdd"; -+ const unicode512Hash = -+ "2b16a5561af8ad6fe414cc103fc8036492e1fc6d9aabe1b655497054f760fe0e34c5d100ac773d" + -+ "9f3030438284f22dbfa20cb2e9b019f2c98dfe38ce1ef41bae"; -+ -+ const regularMd5 = "5eceffa53a5fd58c44134211e2c5f522"; -+ const utf8Md5 = "3abc9433c09551b939c80aa0aa3174e1"; -+ const unicodeMd5 = "85ae134072c8d81257933f7045ba17ca"; -+ -+ testHash("sha1", regular1Hash, utf81Hash, unicode1Hash); -+ testHash("sha256", regular256Hash, utf8256Hash, unicode256Hash); -+ testHash("sha512", regular512Hash, utf8512Hash, unicode512Hash); -+ testHash("md5", regularMd5, utf8Md5, unicodeMd5); -+ }); -+ -+ describe("hmac", () => { -+ testHmac("sha1", Sha1Mac); -+ testHmac("sha256", Sha256Mac); -+ testHmac("sha512", Sha512Mac); -+ }); -+ -+ describe("compare", () => { -+ it("should successfully compare two of the same values", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const a = new Uint8Array(2); -+ a[0] = 1; -+ a[1] = 2; -+ const equal = await cryptoFunctionService.compare(a.buffer, a.buffer); -+ expect(equal).toBe(true); - }); - -- describe('hash', () => { -- const regular1Hash = '2a241604fb921fad12bf877282457268e1dccb70'; -- const utf81Hash = '85672798dc5831e96d6c48655d3d39365a9c88b6'; -- const unicode1Hash = '39c975935054a3efc805a9709b60763a823a6ad4'; -- -- const regular256Hash = '2b8e96031d352a8655d733d7a930b5ffbea69dc25cf65c7bca7dd946278908b2'; -- const utf8256Hash = '25fe8440f5b01ed113b0a0e38e721b126d2f3f77a67518c4a04fcde4e33eeb9d'; -- const unicode256Hash = 'adc1c0c2afd6e92cefdf703f9b6eb2c38e0d6d1a040c83f8505c561fea58852e'; -- -- const regular512Hash = 'c15cf11d43bde333647e3f559ec4193bb2edeaa0e8b902772f514cdf3f785a3f49a6e02a4b87b3' + -- 'b47523271ad45b7e0aebb5cdcc1bc54815d256eb5dcb80da9d'; -- const utf8512Hash = '035c31a877a291af09ed2d3a1a293e69c3e079ea2cecc00211f35e6bce10474ca3ad6e30b59e26118' + -- '37463f20969c5bc95282965a051a88f8cdf2e166549fcdd'; -- const unicode512Hash = '2b16a5561af8ad6fe414cc103fc8036492e1fc6d9aabe1b655497054f760fe0e34c5d100ac773d' + -- '9f3030438284f22dbfa20cb2e9b019f2c98dfe38ce1ef41bae'; -- -- const regularMd5 = '5eceffa53a5fd58c44134211e2c5f522'; -- const utf8Md5 = '3abc9433c09551b939c80aa0aa3174e1'; -- const unicodeMd5 = '85ae134072c8d81257933f7045ba17ca'; -- -- testHash('sha1', regular1Hash, utf81Hash, unicode1Hash); -- testHash('sha256', regular256Hash, utf8256Hash, unicode256Hash); -- testHash('sha512', regular512Hash, utf8512Hash, unicode512Hash); -- testHash('md5', regularMd5, utf8Md5, unicodeMd5); -+ it("should successfully compare two different values of the same length", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const a = new Uint8Array(2); -+ a[0] = 1; -+ a[1] = 2; -+ const b = new Uint8Array(2); -+ b[0] = 3; -+ b[1] = 4; -+ const equal = await cryptoFunctionService.compare(a.buffer, b.buffer); -+ expect(equal).toBe(false); - }); - -- describe('hmac', () => { -- testHmac('sha1', Sha1Mac); -- testHmac('sha256', Sha256Mac); -- testHmac('sha512', Sha512Mac); -+ it("should successfully compare two different values of different lengths", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const a = new Uint8Array(2); -+ a[0] = 1; -+ a[1] = 2; -+ const b = new Uint8Array(2); -+ b[0] = 3; -+ const equal = await cryptoFunctionService.compare(a.buffer, b.buffer); -+ expect(equal).toBe(false); - }); -- -- describe('compare', () => { -- it('should successfully compare two of the same values', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const a = new Uint8Array(2); -- a[0] = 1; -- a[1] = 2; -- const equal = await cryptoFunctionService.compare(a.buffer, a.buffer); -- expect(equal).toBe(true); -- }); -- -- it('should successfully compare two different values of the same length', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const a = new Uint8Array(2); -- a[0] = 1; -- a[1] = 2; -- const b = new Uint8Array(2); -- b[0] = 3; -- b[1] = 4; -- const equal = await cryptoFunctionService.compare(a.buffer, b.buffer); -- expect(equal).toBe(false); -- }); -- -- it('should successfully compare two different values of different lengths', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const a = new Uint8Array(2); -- a[0] = 1; -- a[1] = 2; -- const b = new Uint8Array(2); -- b[0] = 3; -- const equal = await cryptoFunctionService.compare(a.buffer, b.buffer); -- expect(equal).toBe(false); -- }); -+ }); -+ -+ describe("hmacFast", () => { -+ testHmacFast("sha1", Sha1Mac); -+ testHmacFast("sha256", Sha256Mac); -+ testHmacFast("sha512", Sha512Mac); -+ }); -+ -+ describe("compareFast", () => { -+ it("should successfully compare two of the same values", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const a = new Uint8Array(2); -+ a[0] = 1; -+ a[1] = 2; -+ const aByteString = Utils.fromBufferToByteString(a.buffer); -+ const equal = await cryptoFunctionService.compareFast(aByteString, aByteString); -+ expect(equal).toBe(true); - }); - -- describe('hmacFast', () => { -- testHmacFast('sha1', Sha1Mac); -- testHmacFast('sha256', Sha256Mac); -- testHmacFast('sha512', Sha512Mac); -+ it("should successfully compare two different values of the same length", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const a = new Uint8Array(2); -+ a[0] = 1; -+ a[1] = 2; -+ const aByteString = Utils.fromBufferToByteString(a.buffer); -+ const b = new Uint8Array(2); -+ b[0] = 3; -+ b[1] = 4; -+ const bByteString = Utils.fromBufferToByteString(b.buffer); -+ const equal = await cryptoFunctionService.compareFast(aByteString, bByteString); -+ expect(equal).toBe(false); - }); - -- describe('compareFast', () => { -- it('should successfully compare two of the same values', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const a = new Uint8Array(2); -- a[0] = 1; -- a[1] = 2; -- const aByteString = Utils.fromBufferToByteString(a.buffer); -- const equal = await cryptoFunctionService.compareFast(aByteString, aByteString); -- expect(equal).toBe(true); -- }); -- -- it('should successfully compare two different values of the same length', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const a = new Uint8Array(2); -- a[0] = 1; -- a[1] = 2; -- const aByteString = Utils.fromBufferToByteString(a.buffer); -- const b = new Uint8Array(2); -- b[0] = 3; -- b[1] = 4; -- const bByteString = Utils.fromBufferToByteString(b.buffer); -- const equal = await cryptoFunctionService.compareFast(aByteString, bByteString); -- expect(equal).toBe(false); -- }); -- -- it('should successfully compare two different values of different lengths', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const a = new Uint8Array(2); -- a[0] = 1; -- a[1] = 2; -- const aByteString = Utils.fromBufferToByteString(a.buffer); -- const b = new Uint8Array(2); -- b[0] = 3; -- const bByteString = Utils.fromBufferToByteString(b.buffer); -- const equal = await cryptoFunctionService.compareFast(aByteString, bByteString); -- expect(equal).toBe(false); -- }); -+ it("should successfully compare two different values of different lengths", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const a = new Uint8Array(2); -+ a[0] = 1; -+ a[1] = 2; -+ const aByteString = Utils.fromBufferToByteString(a.buffer); -+ const b = new Uint8Array(2); -+ b[0] = 3; -+ const bByteString = Utils.fromBufferToByteString(b.buffer); -+ const equal = await cryptoFunctionService.compareFast(aByteString, bByteString); -+ expect(equal).toBe(false); - }); -- -- describe('aesEncrypt', () => { -- it('should successfully encrypt data', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const iv = makeStaticByteArray(16); -- const key = makeStaticByteArray(32); -- const data = Utils.fromUtf8ToArray('EncryptMe!'); -- const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer); -- expect(Utils.fromBufferToB64(encValue)).toBe('ByUF8vhyX4ddU9gcooznwA=='); -- }); -- -- it('should successfully encrypt and then decrypt data fast', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const iv = makeStaticByteArray(16); -- const key = makeStaticByteArray(32); -- const value = 'EncryptMe!'; -- const data = Utils.fromUtf8ToArray(value); -- const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer); -- const encData = Utils.fromBufferToB64(encValue); -- const b64Iv = Utils.fromBufferToB64(iv.buffer); -- const symKey = new SymmetricCryptoKey(key.buffer); -- const params = cryptoFunctionService.aesDecryptFastParameters(encData, b64Iv, null, symKey); -- const decValue = await cryptoFunctionService.aesDecryptFast(params); -- expect(decValue).toBe(value); -- }); -- -- it('should successfully encrypt and then decrypt data', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const iv = makeStaticByteArray(16); -- const key = makeStaticByteArray(32); -- const value = 'EncryptMe!'; -- const data = Utils.fromUtf8ToArray(value); -- const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer); -- const decValue = await cryptoFunctionService.aesDecrypt(encValue, iv.buffer, key.buffer); -- expect(Utils.fromBufferToUtf8(decValue)).toBe(value); -- }); -+ }); + -+ describe("aesEncrypt", () => { -+ it("should successfully encrypt data", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const iv = makeStaticByteArray(16); -+ const key = makeStaticByteArray(32); -+ const data = Utils.fromUtf8ToArray("EncryptMe!"); -+ const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer); -+ expect(Utils.fromBufferToB64(encValue)).toBe("ByUF8vhyX4ddU9gcooznwA=="); - }); + if (!this.acceptPolicies && this.showTerms) { + this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), + this.i18nService.t('acceptPoliciesError')); +diff --git a/jslib/angular/src/components/sso.component.ts b/jslib/angular/src/components/sso.component.ts +index 1ab8e2f4..7e74fbd7 100644 +--- a/jslib/angular/src/components/sso.component.ts ++++ b/jslib/angular/src/components/sso.component.ts +@@ -23,6 +23,8 @@ import { Utils } from 'jslib-common/misc/utils'; -- describe('aesDecryptFast', () => { -- it('should successfully decrypt data', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const iv = Utils.fromBufferToB64(makeStaticByteArray(16).buffer); -- const symKey = new SymmetricCryptoKey(makeStaticByteArray(32).buffer); -- const data = 'ByUF8vhyX4ddU9gcooznwA=='; -- const params = cryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey); -- const decValue = await cryptoFunctionService.aesDecryptFast(params); -- expect(decValue).toBe('EncryptMe!'); -- }); -+ it("should successfully encrypt and then decrypt data fast", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const iv = makeStaticByteArray(16); -+ const key = makeStaticByteArray(32); -+ const value = "EncryptMe!"; -+ const data = Utils.fromUtf8ToArray(value); -+ const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer); -+ const encData = Utils.fromBufferToB64(encValue); -+ const b64Iv = Utils.fromBufferToB64(iv.buffer); -+ const symKey = new SymmetricCryptoKey(key.buffer); -+ const params = cryptoFunctionService.aesDecryptFastParameters(encData, b64Iv, null, symKey); -+ const decValue = await cryptoFunctionService.aesDecryptFast(params); -+ expect(decValue).toBe(value); - }); + import { AuthResult } from 'jslib-common/models/domain/authResult'; -- describe('aesDecrypt', () => { -- it('should successfully decrypt data', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const iv = makeStaticByteArray(16); -- const key = makeStaticByteArray(32); -- const data = Utils.fromB64ToArray('ByUF8vhyX4ddU9gcooznwA=='); -- const decValue = await cryptoFunctionService.aesDecrypt(data.buffer, iv.buffer, key.buffer); -- expect(Utils.fromBufferToUtf8(decValue)).toBe('EncryptMe!'); -- }); -+ it("should successfully encrypt and then decrypt data", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const iv = makeStaticByteArray(16); -+ const key = makeStaticByteArray(32); -+ const value = "EncryptMe!"; -+ const data = Utils.fromUtf8ToArray(value); -+ const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer); -+ const decValue = await cryptoFunctionService.aesDecrypt(encValue, iv.buffer, key.buffer); -+ expect(Utils.fromBufferToUtf8(decValue)).toBe(value); - }); -- -- describe('rsaEncrypt', () => { -- it('should successfully encrypt and then decrypt data', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const pubKey = Utils.fromB64ToArray(RsaPublicKey); -- const privKey = Utils.fromB64ToArray(RsaPrivateKey); -- const value = 'EncryptMe!'; -- const data = Utils.fromUtf8ToArray(value); -- const encValue = await cryptoFunctionService.rsaEncrypt(data.buffer, pubKey.buffer, 'sha1'); -- const decValue = await cryptoFunctionService.rsaDecrypt(encValue, privKey.buffer, 'sha1'); -- expect(Utils.fromBufferToUtf8(decValue)).toBe(value); -- }); -+ }); -+ -+ describe("aesDecryptFast", () => { -+ it("should successfully decrypt data", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const iv = Utils.fromBufferToB64(makeStaticByteArray(16).buffer); -+ const symKey = new SymmetricCryptoKey(makeStaticByteArray(32).buffer); -+ const data = "ByUF8vhyX4ddU9gcooznwA=="; -+ const params = cryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey); -+ const decValue = await cryptoFunctionService.aesDecryptFast(params); -+ expect(decValue).toBe("EncryptMe!"); - }); -- -- describe('rsaDecrypt', () => { -- it('should successfully decrypt data', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const privKey = Utils.fromB64ToArray(RsaPrivateKey); -- const data = Utils.fromB64ToArray('A1/p8BQzN9UrbdYxUY2Va5+kPLyfZXF9JsZrjeEXcaclsnHurdxVAJcnbEqYMP3UXV' + -- '4YAS/mpf+Rxe6/X0WS1boQdA0MAHSgx95hIlAraZYpiMLLiJRKeo2u8YivCdTM9V5vuAEJwf9Tof/qFsFci3sApdbATkorCT' + -- 'zFOIEPF2S1zgperEP23M01mr4dWVdYN18B32YF67xdJHMbFhp5dkQwv9CmscoWq7OE5HIfOb+JAh7BEZb+CmKhM3yWJvoR/D' + -- '/5jcercUtK2o+XrzNrL4UQ7yLZcFz6Bfwb/j6ICYvqd/YJwXNE6dwlL57OfwJyCdw2rRYf0/qI00t9u8Iitw=='); -- const decValue = await cryptoFunctionService.rsaDecrypt(data.buffer, privKey.buffer, 'sha1'); -- expect(Utils.fromBufferToUtf8(decValue)).toBe('EncryptMe!'); -- }); -+ }); -+ -+ describe("aesDecrypt", () => { -+ it("should successfully decrypt data", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const iv = makeStaticByteArray(16); -+ const key = makeStaticByteArray(32); -+ const data = Utils.fromB64ToArray("ByUF8vhyX4ddU9gcooznwA=="); -+ const decValue = await cryptoFunctionService.aesDecrypt(data.buffer, iv.buffer, key.buffer); -+ expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!"); - }); -- -- describe('rsaExtractPublicKey', () => { -- it('should successfully extract key', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const privKey = Utils.fromB64ToArray(RsaPrivateKey); -- const publicKey = await cryptoFunctionService.rsaExtractPublicKey(privKey.buffer); -- expect(Utils.fromBufferToB64(publicKey)).toBe(RsaPublicKey); -- }); -+ }); -+ -+ describe("rsaEncrypt", () => { -+ it("should successfully encrypt and then decrypt data", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const pubKey = Utils.fromB64ToArray(RsaPublicKey); -+ const privKey = Utils.fromB64ToArray(RsaPrivateKey); -+ const value = "EncryptMe!"; -+ const data = Utils.fromUtf8ToArray(value); -+ const encValue = await cryptoFunctionService.rsaEncrypt(data.buffer, pubKey.buffer, "sha1"); -+ const decValue = await cryptoFunctionService.rsaDecrypt(encValue, privKey.buffer, "sha1"); -+ expect(Utils.fromBufferToUtf8(decValue)).toBe(value); - }); -- -- describe('rsaGenerateKeyPair', () => { -- testRsaGenerateKeyPair(1024); -- testRsaGenerateKeyPair(2048); -- -- // Generating 4096 bit keys can be slow. Commenting it out to save CI. -- // testRsaGenerateKeyPair(4096); -+ }); -+ -+ describe("rsaDecrypt", () => { -+ it("should successfully decrypt data", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const privKey = Utils.fromB64ToArray(RsaPrivateKey); -+ const data = Utils.fromB64ToArray( -+ "A1/p8BQzN9UrbdYxUY2Va5+kPLyfZXF9JsZrjeEXcaclsnHurdxVAJcnbEqYMP3UXV" + -+ "4YAS/mpf+Rxe6/X0WS1boQdA0MAHSgx95hIlAraZYpiMLLiJRKeo2u8YivCdTM9V5vuAEJwf9Tof/qFsFci3sApdbATkorCT" + -+ "zFOIEPF2S1zgperEP23M01mr4dWVdYN18B32YF67xdJHMbFhp5dkQwv9CmscoWq7OE5HIfOb+JAh7BEZb+CmKhM3yWJvoR/D" + -+ "/5jcercUtK2o+XrzNrL4UQ7yLZcFz6Bfwb/j6ICYvqd/YJwXNE6dwlL57OfwJyCdw2rRYf0/qI00t9u8Iitw==" -+ ); -+ const decValue = await cryptoFunctionService.rsaDecrypt(data.buffer, privKey.buffer, "sha1"); -+ expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!"); - }); -- -- describe('randomBytes', () => { -- it('should make a value of the correct length', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const randomData = await cryptoFunctionService.randomBytes(16); -- expect(randomData.byteLength).toBe(16); -- }); -- -- it('should not make the same value twice', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const randomData = await cryptoFunctionService.randomBytes(16); -- const randomData2 = await cryptoFunctionService.randomBytes(16); -- expect(randomData.byteLength === randomData2.byteLength && randomData !== randomData2).toBeTruthy(); -- }); -+ }); ++import { switchMap } from 'rxjs/operators'; + -+ describe("rsaExtractPublicKey", () => { -+ it("should successfully extract key", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const privKey = Utils.fromB64ToArray(RsaPrivateKey); -+ const publicKey = await cryptoFunctionService.rsaExtractPublicKey(privKey.buffer); -+ expect(Utils.fromBufferToB64(publicKey)).toBe(RsaPublicKey); - }); --}); -+ }); - --function testPbkdf2(algorithm: 'sha256' | 'sha512', regularKey: string, -- utf8Key: string, unicodeKey: string) { -- const regularEmail = 'user@example.com'; -- const utf8Email = 'üser@example.com'; -+ describe("rsaGenerateKeyPair", () => { -+ testRsaGenerateKeyPair(1024); -+ testRsaGenerateKeyPair(2048); - -- const regularPassword = 'password'; -- const utf8Password = 'pǻssword'; -- const unicodePassword = '😀password🙏'; -+ // Generating 4096 bit keys can be slow. Commenting it out to save CI. -+ // testRsaGenerateKeyPair(4096); -+ }); + @Directive() + export class SsoComponent { + identifier: string; +@@ -54,13 +56,19 @@ export class SsoComponent { -- it('should create valid ' + algorithm + ' key from regular input', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const key = await cryptoFunctionService.pbkdf2(regularPassword, regularEmail, algorithm, 5000); -- expect(Utils.fromBufferToB64(key)).toBe(regularKey); -+ describe("randomBytes", () => { -+ it("should make a value of the correct length", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const randomData = await cryptoFunctionService.randomBytes(16); -+ expect(randomData.byteLength).toBe(16); - }); + async ngOnInit() { + this.route.queryParams.pipe(first()).subscribe(async qParams => { +- if (qParams.code != null && qParams.state != null) { ++ // I have no idea why the qParams is empty here - I've hacked in an alternative very messily, but it works. ++ const workingParams = (new URL(window.location.href)).searchParams; ++ const workingSwap = { ++ code: workingParams.get('code'), ++ state: workingParams.get('state'), ++ }; ++ if (workingSwap.code != null && workingSwap.state != null) { + const codeVerifier = await this.storageService.get(ConstantsService.ssoCodeVerifierKey); + const state = await this.storageService.get(ConstantsService.ssoStateKey); + await this.storageService.remove(ConstantsService.ssoCodeVerifierKey); + await this.storageService.remove(ConstantsService.ssoStateKey); +- if (qParams.code != null && codeVerifier != null && state != null && this.checkState(state, qParams.state)) { +- await this.logIn(qParams.code, codeVerifier, this.getOrgIdentifierFromState(qParams.state)); ++ if (workingSwap.code != null && codeVerifier != null && state != null && this.checkState(state, workingSwap.state)) { ++ await this.logIn(workingSwap.code, codeVerifier, this.getOrgIdentifierFromState(workingSwap.state)); + } + } else if (qParams.clientId != null && qParams.redirectUri != null && qParams.state != null && + qParams.codeChallenge != null) { +@@ -125,7 +133,7 @@ export class SsoComponent { + let authorizeUrl = this.environmentService.getIdentityUrl() + '/connect/authorize?' + + 'client_id=' + this.clientId + '&redirect_uri=' + encodeURIComponent(this.redirectUri) + '&' + + 'response_type=code&scope=api offline_access&' + +- 'state=' + state + '&code_challenge=' + codeChallenge + '&' + ++ 'state=' + encodeURIComponent(state) + '&code_challenge=' + codeChallenge + '&' + + 'code_challenge_method=S256&response_mode=query&' + + 'domain_hint=' + encodeURIComponent(this.identifier); -- it('should create valid ' + algorithm + ' key from utf8 input', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const key = await cryptoFunctionService.pbkdf2(utf8Password, utf8Email, algorithm, 5000); -- expect(Utils.fromBufferToB64(key)).toBe(utf8Key); -- }); -- -- it('should create valid ' + algorithm + ' key from unicode input', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const key = await cryptoFunctionService.pbkdf2(unicodePassword, regularEmail, algorithm, 5000); -- expect(Utils.fromBufferToB64(key)).toBe(unicodeKey); -+ it("should not make the same value twice", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const randomData = await cryptoFunctionService.randomBytes(16); -+ const randomData2 = await cryptoFunctionService.randomBytes(16); -+ expect( -+ randomData.byteLength === randomData2.byteLength && randomData !== randomData2 -+ ).toBeTruthy(); - }); -+ }); -+}); +diff --git a/jslib/common/src/abstractions/api.service.ts b/jslib/common/src/abstractions/api.service.ts +index 1c6aa0ef..aab45eeb 100644 +--- a/jslib/common/src/abstractions/api.service.ts ++++ b/jslib/common/src/abstractions/api.service.ts +@@ -38,6 +38,7 @@ import { OrganizationSsoRequest } from '../models/request/organization/organizat + import { OrganizationCreateRequest } from '../models/request/organizationCreateRequest'; + import { OrganizationImportRequest } from '../models/request/organizationImportRequest'; + import { OrganizationKeysRequest } from '../models/request/organizationKeysRequest'; ++import { OrganizationSsoUpdateRequest } from '../models/request/organizationSsoUpdateRequest'; + import { OrganizationSubscriptionUpdateRequest } from '../models/request/organizationSubscriptionUpdateRequest'; + import { OrganizationTaxInfoUpdateRequest } from '../models/request/organizationTaxInfoUpdateRequest'; + import { OrganizationUpdateRequest } from '../models/request/organizationUpdateRequest'; +@@ -148,6 +149,7 @@ import { SendAccessResponse } from '../models/response/sendAccessResponse'; + import { SendFileDownloadDataResponse } from '../models/response/sendFileDownloadDataResponse'; + import { SendFileUploadDataResponse } from '../models/response/sendFileUploadDataResponse'; + import { SendResponse } from '../models/response/sendResponse'; ++import { SsoConfigResponse } from '../models/response/ssoConfigResponse'; + import { SubscriptionResponse } from '../models/response/subscriptionResponse'; + import { SyncResponse } from '../models/response/syncResponse'; + import { TaxInfoResponse } from '../models/response/taxInfoResponse'; +@@ -386,6 +388,8 @@ export abstract class ApiService { + getOrganizationSso: (id: string) => Promise; + postOrganization: (request: OrganizationCreateRequest) => Promise; + putOrganization: (id: string, request: OrganizationUpdateRequest) => Promise; ++ getSsoConfig: (id: string) => Promise; ++ putOrganizationSso: (id: string, request: OrganizationSsoUpdateRequest) => Promise; + putOrganizationTaxInfo: (id: string, request: OrganizationTaxInfoUpdateRequest) => Promise; + postLeaveOrganization: (id: string) => Promise; + postOrganizationLicense: (data: FormData) => Promise; +diff --git a/jslib/common/src/models/request/organizationSsoUpdateRequest.ts b/jslib/common/src/models/request/organizationSsoUpdateRequest.ts +new file mode 100644 +index 00000000..7075aecc +--- /dev/null ++++ b/jslib/common/src/models/request/organizationSsoUpdateRequest.ts +@@ -0,0 +1,8 @@ ++export class OrganizationSsoUpdateRequest { ++ useSso: boolean; ++ callbackPath: string; ++ signedOutCallbackPath: string; ++ authority: string; ++ clientId: string; ++ clientSecret: string; ++} +diff --git a/jslib/common/src/models/request/tokenRequest.ts b/jslib/common/src/models/request/tokenRequest.ts +index 41797eb0..26206356 100644 +--- a/jslib/common/src/models/request/tokenRequest.ts ++++ b/jslib/common/src/models/request/tokenRequest.ts +@@ -14,9 +14,10 @@ export class TokenRequest implements CaptchaProtectedRequest { + clientId: string; + clientSecret: string; + device?: DeviceRequest; ++ orgId?: string -- it('should create valid ' + algorithm + ' key from array buffer input', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const key = await cryptoFunctionService.pbkdf2(Utils.fromUtf8ToArray(regularPassword).buffer, -- Utils.fromUtf8ToArray(regularEmail).buffer, algorithm, 5000); -- expect(Utils.fromBufferToB64(key)).toBe(regularKey); -- }); -+function testPbkdf2( -+ algorithm: "sha256" | "sha512", -+ regularKey: string, -+ utf8Key: string, -+ unicodeKey: string -+) { -+ const regularEmail = "user@example.com"; -+ const utf8Email = "üser@example.com"; -+ -+ const regularPassword = "password"; -+ const utf8Password = "pǻssword"; -+ const unicodePassword = "😀password🙏"; -+ -+ it("should create valid " + algorithm + " key from regular input", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const key = await cryptoFunctionService.pbkdf2(regularPassword, regularEmail, algorithm, 5000); -+ expect(Utils.fromBufferToB64(key)).toBe(regularKey); -+ }); -+ -+ it("should create valid " + algorithm + " key from utf8 input", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const key = await cryptoFunctionService.pbkdf2(utf8Password, utf8Email, algorithm, 5000); -+ expect(Utils.fromBufferToB64(key)).toBe(utf8Key); -+ }); -+ -+ it("should create valid " + algorithm + " key from unicode input", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const key = await cryptoFunctionService.pbkdf2(unicodePassword, regularEmail, algorithm, 5000); -+ expect(Utils.fromBufferToB64(key)).toBe(unicodeKey); -+ }); -+ -+ it("should create valid " + algorithm + " key from array buffer input", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const key = await cryptoFunctionService.pbkdf2( -+ Utils.fromUtf8ToArray(regularPassword).buffer, -+ Utils.fromUtf8ToArray(regularEmail).buffer, -+ algorithm, -+ 5000 -+ ); -+ expect(Utils.fromBufferToB64(key)).toBe(regularKey); -+ }); - } + constructor(credentials: string[], codes: string[], clientIdClientSecret: string[], public provider: TwoFactorProviderType, +- public token: string, public remember: boolean, public captchaResponse: string, device?: DeviceRequest) { ++ public token: string, public remember: boolean, public captchaResponse: string, device?: DeviceRequest, orgId?: string) { + if (credentials != null && credentials.length > 1) { + this.email = credentials[0]; + this.masterPasswordHash = credentials[1]; +@@ -28,6 +29,9 @@ export class TokenRequest implements CaptchaProtectedRequest { + this.clientId = clientIdClientSecret[0]; + this.clientSecret = clientIdClientSecret[1]; + } ++ if (orgId && orgId !== '') { ++ this.orgId = orgId; ++ } + this.device = device != null ? device : null; + } --function testHkdf(algorithm: 'sha256' | 'sha512', regularKey: string, utf8Key: string, unicodeKey: string) { -- const ikm = Utils.fromB64ToArray('criAmKtfzxanbgea5/kelQ=='); -- -- const regularSalt = 'salt'; -- const utf8Salt = 'üser_salt'; -- const unicodeSalt = '😀salt🙏'; -- -- const regularInfo = 'info'; -- const utf8Info = 'üser_info'; -- const unicodeInfo = '😀info🙏'; -- -- it('should create valid ' + algorithm + ' key from regular input', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const key = await cryptoFunctionService.hkdf(ikm, regularSalt, regularInfo, 32, algorithm); -- expect(Utils.fromBufferToB64(key)).toBe(regularKey); -- }); -- -- it('should create valid ' + algorithm + ' key from utf8 input', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const key = await cryptoFunctionService.hkdf(ikm, utf8Salt, utf8Info, 32, algorithm); -- expect(Utils.fromBufferToB64(key)).toBe(utf8Key); -- }); -- -- it('should create valid ' + algorithm + ' key from unicode input', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const key = await cryptoFunctionService.hkdf(ikm, unicodeSalt, unicodeInfo, 32, algorithm); -- expect(Utils.fromBufferToB64(key)).toBe(unicodeKey); -- }); -- -- it('should create valid ' + algorithm + ' key from array buffer input', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const key = await cryptoFunctionService.hkdf(ikm, Utils.fromUtf8ToArray(regularSalt).buffer, -- Utils.fromUtf8ToArray(regularInfo).buffer, 32, algorithm); -- expect(Utils.fromBufferToB64(key)).toBe(regularKey); -- }); -+function testHkdf( -+ algorithm: "sha256" | "sha512", -+ regularKey: string, -+ utf8Key: string, -+ unicodeKey: string -+) { -+ const ikm = Utils.fromB64ToArray("criAmKtfzxanbgea5/kelQ=="); -+ -+ const regularSalt = "salt"; -+ const utf8Salt = "üser_salt"; -+ const unicodeSalt = "😀salt🙏"; -+ -+ const regularInfo = "info"; -+ const utf8Info = "üser_info"; -+ const unicodeInfo = "😀info🙏"; -+ -+ it("should create valid " + algorithm + " key from regular input", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const key = await cryptoFunctionService.hkdf(ikm, regularSalt, regularInfo, 32, algorithm); -+ expect(Utils.fromBufferToB64(key)).toBe(regularKey); -+ }); -+ -+ it("should create valid " + algorithm + " key from utf8 input", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const key = await cryptoFunctionService.hkdf(ikm, utf8Salt, utf8Info, 32, algorithm); -+ expect(Utils.fromBufferToB64(key)).toBe(utf8Key); -+ }); -+ -+ it("should create valid " + algorithm + " key from unicode input", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const key = await cryptoFunctionService.hkdf(ikm, unicodeSalt, unicodeInfo, 32, algorithm); -+ expect(Utils.fromBufferToB64(key)).toBe(unicodeKey); -+ }); +@@ -50,6 +54,7 @@ export class TokenRequest implements CaptchaProtectedRequest { + obj.code = this.code; + obj.code_verifier = this.codeVerifier; + obj.redirect_uri = this.redirectUri; ++ obj.org_identifier = this.orgId; + } else { + throw new Error('must provide credentials or codes'); + } +diff --git a/jslib/common/src/models/response/ssoConfigResponse.ts b/jslib/common/src/models/response/ssoConfigResponse.ts +new file mode 100644 +index 00000000..9c72dd33 +--- /dev/null ++++ b/jslib/common/src/models/response/ssoConfigResponse.ts +@@ -0,0 +1,22 @@ ++import { BaseResponse } from './baseResponse'; + -+ it("should create valid " + algorithm + " key from array buffer input", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const key = await cryptoFunctionService.hkdf( -+ ikm, -+ Utils.fromUtf8ToArray(regularSalt).buffer, -+ Utils.fromUtf8ToArray(regularInfo).buffer, -+ 32, -+ algorithm -+ ); -+ expect(Utils.fromBufferToB64(key)).toBe(regularKey); -+ }); - } - --function testHkdfExpand(algorithm: 'sha256' | 'sha512', b64prk: string, outputByteSize: number, -- b64ExpectedOkm: string) { -- const info = 'info'; -- -- it('should create valid ' + algorithm + ' ' + outputByteSize + ' byte okm', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const okm = await cryptoFunctionService.hkdfExpand(Utils.fromB64ToArray(b64prk), info, outputByteSize, -- algorithm); -- expect(Utils.fromBufferToB64(okm)).toBe(b64ExpectedOkm); -- }); -+function testHkdfExpand( -+ algorithm: "sha256" | "sha512", -+ b64prk: string, -+ outputByteSize: number, -+ b64ExpectedOkm: string -+) { -+ const info = "info"; ++export class SsoConfigResponse extends BaseResponse { ++ id: string; ++ useSso: boolean; ++ callbackPath: string; ++ signedOutCallbackPath: string; ++ authority: string; ++ clientId: string; ++ clientSecret: string; + -+ it("should create valid " + algorithm + " " + outputByteSize + " byte okm", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const okm = await cryptoFunctionService.hkdfExpand( -+ Utils.fromB64ToArray(b64prk), -+ info, -+ outputByteSize, -+ algorithm -+ ); -+ expect(Utils.fromBufferToB64(okm)).toBe(b64ExpectedOkm); -+ }); - } ++ constructor(response: any) { ++ super(response); ++ this.id = this.getResponseProperty('Id'); ++ this.useSso = this.getResponseProperty('UseSso'); ++ this.callbackPath = this.getResponseProperty('CallbackPath'); ++ this.signedOutCallbackPath = this.getResponseProperty('SignedOutCallbackPath'); ++ this.authority = this.getResponseProperty('Authority'); ++ this.clientId = this.getResponseProperty('ClientId'); ++ this.clientSecret = this.getResponseProperty('ClientSecret'); ++ } ++} +diff --git a/jslib/common/src/services/api.service.ts b/jslib/common/src/services/api.service.ts +index 46fdc139..16140f6c 100644 +--- a/jslib/common/src/services/api.service.ts ++++ b/jslib/common/src/services/api.service.ts +@@ -39,6 +39,7 @@ import { OrganizationSsoRequest } from '../models/request/organization/organizat + import { OrganizationCreateRequest } from '../models/request/organizationCreateRequest'; + import { OrganizationImportRequest } from '../models/request/organizationImportRequest'; + import { OrganizationKeysRequest } from '../models/request/organizationKeysRequest'; ++import { OrganizationSsoUpdateRequest } from '../models/request/organizationSsoUpdateRequest'; + import { OrganizationSubscriptionUpdateRequest } from '../models/request/organizationSubscriptionUpdateRequest'; + import { OrganizationTaxInfoUpdateRequest } from '../models/request/organizationTaxInfoUpdateRequest'; + import { OrganizationUpdateRequest } from '../models/request/organizationUpdateRequest'; +@@ -154,6 +155,7 @@ import { SendAccessResponse } from '../models/response/sendAccessResponse'; + import { SendFileDownloadDataResponse } from '../models/response/sendFileDownloadDataResponse'; + import { SendFileUploadDataResponse } from '../models/response/sendFileUploadDataResponse'; + import { SendResponse } from '../models/response/sendResponse'; ++import { SsoConfigResponse } from '../models/response/ssoConfigResponse'; + import { SubscriptionResponse } from '../models/response/subscriptionResponse'; + import { SyncResponse } from '../models/response/syncResponse'; + import { TaxInfoResponse } from '../models/response/taxInfoResponse'; +@@ -1187,6 +1189,16 @@ export class ApiService implements ApiServiceAbstraction { + return new OrganizationResponse(r); + } --function testHash(algorithm: 'sha1' | 'sha256' | 'sha512' | 'md5', regularHash: string, -- utf8Hash: string, unicodeHash: string) { -- const regularValue = 'HashMe!!'; -- const utf8Value = 'HǻshMe!!'; -- const unicodeValue = '😀HashMe!!!🙏'; -- -- it('should create valid ' + algorithm + ' hash from regular input', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const hash = await cryptoFunctionService.hash(regularValue, algorithm); -- expect(Utils.fromBufferToHex(hash)).toBe(regularHash); -- }); -- -- it('should create valid ' + algorithm + ' hash from utf8 input', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const hash = await cryptoFunctionService.hash(utf8Value, algorithm); -- expect(Utils.fromBufferToHex(hash)).toBe(utf8Hash); -- }); -- -- it('should create valid ' + algorithm + ' hash from unicode input', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const hash = await cryptoFunctionService.hash(unicodeValue, algorithm); -- expect(Utils.fromBufferToHex(hash)).toBe(unicodeHash); -- }); -- -- it('should create valid ' + algorithm + ' hash from array buffer input', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const hash = await cryptoFunctionService.hash(Utils.fromUtf8ToArray(regularValue).buffer, algorithm); -- expect(Utils.fromBufferToHex(hash)).toBe(regularHash); -- }); -+function testHash( -+ algorithm: "sha1" | "sha256" | "sha512" | "md5", -+ regularHash: string, -+ utf8Hash: string, -+ unicodeHash: string -+) { -+ const regularValue = "HashMe!!"; -+ const utf8Value = "HǻshMe!!"; -+ const unicodeValue = "😀HashMe!!!🙏"; -+ -+ it("should create valid " + algorithm + " hash from regular input", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const hash = await cryptoFunctionService.hash(regularValue, algorithm); -+ expect(Utils.fromBufferToHex(hash)).toBe(regularHash); -+ }); -+ -+ it("should create valid " + algorithm + " hash from utf8 input", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const hash = await cryptoFunctionService.hash(utf8Value, algorithm); -+ expect(Utils.fromBufferToHex(hash)).toBe(utf8Hash); -+ }); -+ -+ it("should create valid " + algorithm + " hash from unicode input", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const hash = await cryptoFunctionService.hash(unicodeValue, algorithm); -+ expect(Utils.fromBufferToHex(hash)).toBe(unicodeHash); -+ }); ++ async getSsoConfig(id: string): Promise { ++ const r = await this.send('GET', '/organizations/' + id + '/sso', null, true, true); ++ return new SsoConfigResponse(r); ++ } + -+ it("should create valid " + algorithm + " hash from array buffer input", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const hash = await cryptoFunctionService.hash( -+ Utils.fromUtf8ToArray(regularValue).buffer, -+ algorithm -+ ); -+ expect(Utils.fromBufferToHex(hash)).toBe(regularHash); -+ }); - } - --function testHmac(algorithm: 'sha1' | 'sha256' | 'sha512', mac: string) { -- it('should create valid ' + algorithm + ' hmac', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const computedMac = await cryptoFunctionService.hmac(Utils.fromUtf8ToArray('SignMe!!').buffer, -- Utils.fromUtf8ToArray('secretkey').buffer, algorithm); -- expect(Utils.fromBufferToHex(computedMac)).toBe(mac); -- }); -+function testHmac(algorithm: "sha1" | "sha256" | "sha512", mac: string) { -+ it("should create valid " + algorithm + " hmac", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const computedMac = await cryptoFunctionService.hmac( -+ Utils.fromUtf8ToArray("SignMe!!").buffer, -+ Utils.fromUtf8ToArray("secretkey").buffer, -+ algorithm -+ ); -+ expect(Utils.fromBufferToHex(computedMac)).toBe(mac); -+ }); - } - --function testHmacFast(algorithm: 'sha1' | 'sha256' | 'sha512', mac: string) { -- it('should create valid ' + algorithm + ' hmac', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const keyByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray('secretkey').buffer); -- const dataByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray('SignMe!!').buffer); -- const computedMac = await cryptoFunctionService.hmacFast(dataByteString, keyByteString, algorithm); -- expect(Utils.fromBufferToHex(Utils.fromByteStringToArray(computedMac).buffer)).toBe(mac); -- }); -+function testHmacFast(algorithm: "sha1" | "sha256" | "sha512", mac: string) { -+ it("should create valid " + algorithm + " hmac", async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const keyByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray("secretkey").buffer); -+ const dataByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray("SignMe!!").buffer); -+ const computedMac = await cryptoFunctionService.hmacFast( -+ dataByteString, -+ keyByteString, -+ algorithm -+ ); -+ expect(Utils.fromBufferToHex(Utils.fromByteStringToArray(computedMac).buffer)).toBe(mac); -+ }); - } - - function testRsaGenerateKeyPair(length: 1024 | 2048 | 4096) { -- it('should successfully generate a ' + length + ' bit key pair', async () => { -- const cryptoFunctionService = getWebCryptoFunctionService(); -- const keyPair = await cryptoFunctionService.rsaGenerateKeyPair(length); -- expect(keyPair[0] == null || keyPair[1] == null).toBe(false); -- const publicKey = await cryptoFunctionService.rsaExtractPublicKey(keyPair[1]); -- expect(Utils.fromBufferToB64(keyPair[0])).toBe(Utils.fromBufferToB64(publicKey)); -- }, 30000); -+ it( -+ "should successfully generate a " + length + " bit key pair", -+ async () => { -+ const cryptoFunctionService = getWebCryptoFunctionService(); -+ const keyPair = await cryptoFunctionService.rsaGenerateKeyPair(length); -+ expect(keyPair[0] == null || keyPair[1] == null).toBe(false); -+ const publicKey = await cryptoFunctionService.rsaExtractPublicKey(keyPair[1]); -+ expect(Utils.fromBufferToB64(keyPair[0])).toBe(Utils.fromBufferToB64(publicKey)); -+ }, -+ 30000 -+ ); - } - - function getWebCryptoFunctionService() { -- const platformUtilsMock = Substitute.for(); -- platformUtilsMock.isEdge().mimicks(() => navigator.userAgent.indexOf(' Edg/') !== -1); -- platformUtilsMock.isIE().mimicks(() => navigator.userAgent.indexOf(' Edg/') === -1 && -- navigator.userAgent.indexOf(' Trident/') !== -1); -- -- return new WebCryptoFunctionService(window, platformUtilsMock); -+ const platformUtilsMock = Substitute.for(); -+ platformUtilsMock.isEdge().mimicks(() => navigator.userAgent.indexOf(" Edg/") !== -1); -+ platformUtilsMock -+ .isIE() -+ .mimicks( -+ () => -+ navigator.userAgent.indexOf(" Edg/") === -1 && -+ navigator.userAgent.indexOf(" Trident/") !== -1 -+ ); ++ async putOrganizationSso(id: string, request: OrganizationSsoUpdateRequest): Promise { ++ const r = await this.send('PUT', '/organizations/' + id + '/sso', request, true, false); ++ return new SsoConfigResponse(r); ++ } + -+ return new WebCryptoFunctionService(window, platformUtilsMock); - } + async putOrganizationTaxInfo(id: string, request: OrganizationTaxInfoUpdateRequest): Promise { + return this.send('PUT', '/organizations/' + id + '/tax', request, true, false); + } +diff --git a/jslib/common/src/services/auth.service.ts b/jslib/common/src/services/auth.service.ts +index e4f670d7..d96f78cd 100644 +--- a/jslib/common/src/services/auth.service.ts ++++ b/jslib/common/src/services/auth.service.ts +@@ -310,13 +310,13 @@ export class AuthService implements AuthServiceAbstraction { + let request: TokenRequest; + if (twoFactorToken != null && twoFactorProvider != null) { + request = new TokenRequest(emailPassword, codeCodeVerifier, clientIdClientSecret, twoFactorProvider, +- twoFactorToken, remember, captchaToken, deviceRequest); ++ twoFactorToken, remember, captchaToken, deviceRequest, orgId); + } else if (storedTwoFactorToken != null) { + request = new TokenRequest(emailPassword, codeCodeVerifier, clientIdClientSecret, +- TwoFactorProviderType.Remember, storedTwoFactorToken, false, captchaToken, deviceRequest); ++ TwoFactorProviderType.Remember, storedTwoFactorToken, false, captchaToken, deviceRequest, orgId); + } else { + request = new TokenRequest(emailPassword, codeCodeVerifier, clientIdClientSecret, null, +- null, false, captchaToken, deviceRequest); ++ null, false, captchaToken, deviceRequest, orgId); + } - function makeStaticByteArray(length: number) { -- const arr = new Uint8Array(length); -- for (let i = 0; i < length; i++) { -- arr[i] = i; -- } -- return arr; -+ const arr = new Uint8Array(length); -+ for (let i = 0; i < length; i++) { -+ arr[i] = i; -+ } -+ return arr; - } -diff --git a/jslib/tsconfig.json b/jslib/tsconfig.json -index e6d3af86..5e56a682 100644 ---- a/jslib/tsconfig.json -+++ b/jslib/tsconfig.json -@@ -15,18 +15,10 @@ - "outDir": "dist", - "baseUrl": ".", - "paths": { -- "jslib-common/*": [ -- "common/src/*" -- ], -- "jslib-angular/*": [ -- "angular/src/*" -- ], -- "jslib-electron/*": [ -- "electron/src/*" -- ], -- "jslib-node/*": [ -- "node/src/*" -- ] -+ "jslib-common/*": ["common/src/*"], -+ "jslib-angular/*": ["angular/src/*"], -+ "jslib-electron/*": ["electron/src/*"], -+ "jslib-node/*": ["node/src/*"] - }, - "plugins": [ - { -@@ -34,11 +26,6 @@ - } - ] - }, -- "include": [ -- "spec" -- ], -- "exclude": [ -- "node_modules", -- "dist" -- ] -+ "include": ["spec"], -+ "exclude": ["node_modules", "dist"] - } -diff --git a/jslib/tslint.json b/jslib/tslint.json -index ff350764..4df19690 100644 ---- a/jslib/tslint.json -+++ b/jslib/tslint.json -@@ -2,17 +2,17 @@ - "extends": "tslint:recommended", - "rules": { - "interface-name": [false], -- "align": [ true, "statements", "members" ], -+ "align": [true, "statements", "members"], - "ban-types": { - "options": [ -- [ "Object", "Avoid using the `Object` type. Did you mean `object`?" ], -- [ "Boolean", "Avoid using the `Boolean` type. Did you mean `boolean`?" ], -- [ "Number", "Avoid using the `Number` type. Did you mean `number`?" ], -- [ "String", "Avoid using the `String` type. Did you mean `string`?" ], -- [ "Symbol", "Avoid using the `Symbol` type. Did you mean `symbol`?" ] -+ ["Object", "Avoid using the `Object` type. Did you mean `object`?"], -+ ["Boolean", "Avoid using the `Boolean` type. Did you mean `boolean`?"], -+ ["Number", "Avoid using the `Number` type. Did you mean `number`?"], -+ ["String", "Avoid using the `String` type. Did you mean `string`?"], -+ ["Symbol", "Avoid using the `Symbol` type. Did you mean `symbol`?"] - ] - }, -- "member-access": [ true, "no-public" ], -+ "member-access": [true, "no-public"], - "member-ordering": [ - true, - { -@@ -35,12 +35,11 @@ - ] - } - ], -- "no-empty": [ true ], -+ "no-empty": [true], - "object-literal-sort-keys": false, -- "object-literal-shorthand": [ true, "never" ], -+ "object-literal-shorthand": [true, "never"], - "ordered-imports": true, - "prefer-for-of": false, -- "quotemark": [ true, "single" ], - "whitespace": [ - true, - "check-branch", -@@ -52,14 +51,7 @@ - "check-type" - ], - "max-classes-per-file": false, -- "arrow-parens": [ -- true, -- "ban-single-arg-parens" -- ], -- "semicolon": [ -- true, -- "always" -- ], -+ "arrow-parens": [true], - "trailing-comma": [ - true, - { + const response = await this.apiService.postIdentityToken(request); diff --git a/src/404.html b/src/404.html index eba36375..cb8883ec 100644 --- a/src/404.html @@ -103764,6 +311,21 @@ index 8581e239..24ae6788 100644 {{'getHelp' | i18n}} +diff --git a/src/app/organizations/manage/manage.component.html b/src/app/organizations/manage/manage.component.html +index 1cb4384b..826407f2 100644 +--- a/src/app/organizations/manage/manage.component.html ++++ b/src/app/organizations/manage/manage.component.html +@@ -20,10 +20,6 @@ + *ngIf="organization.canManagePolicies && accessPolicies"> + {{'policies' | i18n}} + +- +- {{'singleSignOn' | i18n}} +- + + {{'eventLogs' | i18n}} diff --git a/src/app/organizations/settings/settings.component.html b/src/app/organizations/settings/settings.component.html index 2dac5ac1..21ce9848 100644 --- a/src/app/organizations/settings/settings.component.html @@ -103778,6 +340,139 @@ index 2dac5ac1..21ce9848 100644 {{'subscription' | i18n}} +diff --git a/src/app/organizations/settings/sso.component.html b/src/app/organizations/settings/sso.component.html +new file mode 100644 +index 00000000..02ec6f3f +--- /dev/null ++++ b/src/app/organizations/settings/sso.component.html +@@ -0,0 +1,51 @@ ++ ++
++ ++ {{'loading' | i18n}} ++
++
++
++
++
++ ++ ++
++

OpenId Connect Configuration

++
++ ++ ++
++
++ ++ ++
++
++ ++ ++
++
++ ++ ++
++
++ ++ ++
++
++
++ ++
++
++ ++ {{'loading' | i18n}} ++
+diff --git a/src/app/organizations/settings/sso.component.ts b/src/app/organizations/settings/sso.component.ts +new file mode 100644 +index 00000000..f00c36ba +--- /dev/null ++++ b/src/app/organizations/settings/sso.component.ts +@@ -0,0 +1,70 @@ ++import { ++ Component, ++ ComponentFactoryResolver, ++ ViewChild, ++ ViewContainerRef, ++} from '@angular/core'; ++ ++import { ActivatedRoute } from '@angular/router'; ++ ++import { ToasterService } from 'angular2-toaster'; ++ ++import { ApiService } from 'jslib-common/abstractions/api.service'; ++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 { SyncService } from 'jslib-common/abstractions/sync.service'; ++ ++import { OrganizationSsoUpdateRequest } from 'jslib-common/models/request/organizationSsoUpdateRequest'; ++ ++import { SsoConfigResponse } from 'jslib-common/models/response/ssoConfigResponse'; ++ ++@Component({ ++ selector: 'app-org-sso', ++ templateUrl: 'sso.component.html', ++}) ++export class SsoComponent { ++ selfHosted = false; ++ loading = true; ++ loaded = false; ++ ssoConfig: SsoConfigResponse; ++ formPromise: Promise; ++ ++ private organizationId: string; ++ ++ constructor(private componentFactoryResolver: ComponentFactoryResolver, ++ private apiService: ApiService, private i18nService: I18nService, ++ private toasterService: ToasterService, private route: ActivatedRoute, ++ private syncService: SyncService, private platformUtilsService: PlatformUtilsService, ++ private cryptoService: CryptoService) { } ++ ++ async ngOnInit() { ++ this.selfHosted = this.platformUtilsService.isSelfHost(); ++ this.route.parent.parent.params.subscribe(async params => { ++ this.organizationId = params.organizationId; ++ try { ++ this.ssoConfig = await this.apiService.getSsoConfig(this.organizationId); ++ } catch { } ++ }); ++ this.loading = false; ++ this.loaded = true; ++ } ++ ++ async submit() { ++ try { ++ const request = new OrganizationSsoUpdateRequest(); ++ request.useSso = this.ssoConfig.useSso; ++ request.callbackPath = this.ssoConfig.callbackPath; ++ request.signedOutCallbackPath = this.ssoConfig.signedOutCallbackPath; ++ request.authority = this.ssoConfig.authority; ++ request.clientId = this.ssoConfig.clientId; ++ request.clientSecret = this.ssoConfig.clientSecret; ++ ++ this.formPromise = this.apiService.putOrganizationSso(this.organizationId, request).then(() => { ++ return this.syncService.fullSync(true); ++ }); ++ await this.formPromise; ++ this.toasterService.popAsync('success', null, this.i18nService.t('organizationUpdated')); ++ } catch { } ++ } ++} diff --git a/src/app/organizations/vault/vault.component.ts b/src/app/organizations/vault/vault.component.ts index 715453fd..b7c2a7b2 100644 --- a/src/app/organizations/vault/vault.component.ts @@ -103809,7 +504,6 @@ index 84a056e4..88f631c2 100644 children: [ { path: '', pathMatch: 'full', redirectTo: 'account' }, { path: 'account', component: OrgAccountComponent, data: { titleId: 'myOrganization' } }, -+ // TODO the diff from older version was misleading here + { path: 'sso', component: OrgSsoComponent, data: { titleId: 'sso' } }, { path: 'two-factor', component: OrgTwoFactorSetupComponent, data: { titleId: 'twoStepLogin' } }, { From 79bf741bd7fe492301aa51f20f3fff98192500e8 Mon Sep 17 00:00:00 2001 From: Stuart Heap Date: Wed, 23 Feb 2022 16:58:02 +0100 Subject: [PATCH 21/21] don't panic --- src/api/identity.rs | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/api/identity.rs b/src/api/identity.rs index d4ffdfedc8..f041216c5a 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -632,29 +632,35 @@ fn _check_is_some(value: &Option, msg: &str) -> EmptyResult { #[allow(unreachable_code)] fn prevalidate(domainHint: String, conn: DbConn) -> JsonResult { let empty_result = json!({}); - let organization = Organization::find_by_identifier(&domainHint, &conn).unwrap(); - let sso_config = SsoConfig::find_by_org(&organization.uuid, &conn); - match sso_config { - Some(sso_config) => { - if !sso_config.use_sso { - return err_code!("SSO Not allowed for organization", Status::BadRequest.code); + match Organization::find_by_identifier(&domainHint, &conn) { + Some(organization) => { + let sso_config = SsoConfig::find_by_org(&organization.uuid, &conn); + match sso_config { + Some(sso_config) => { + if !sso_config.use_sso { + return err_code!("SSO Not allowed for organization", Status::BadRequest.code); + } + if sso_config.authority.is_none() + || sso_config.client_id.is_none() + || sso_config.client_secret.is_none() { + return err_code!("Organization is incorrectly configured for SSO", Status::BadRequest.code); + } + }, + None => { + return err_code!("Unable to find sso config", Status::BadRequest.code); + }, } - if sso_config.authority.is_none() - || sso_config.client_id.is_none() - || sso_config.client_secret.is_none() { - return err_code!("Organization is incorrectly configured for SSO", Status::BadRequest.code); + + if domainHint == "" { + return err_code!("No Organization Identifier Provided", Status::BadRequest.code); } + + Ok(Json(empty_result)) }, None => { - return err_code!("Unable to find sso config", Status::BadRequest.code); - }, - } - - if domainHint == "" { - return err_code!("No Organization Identifier Provided", Status::BadRequest.code); + return err_code!("No matching organization found", Status::BadRequest.code); + } } - - Ok(Json(empty_result)) } use openidconnect::core::{