Browse Source

Merge branch 'main' into group_support

pull/2667/head
Maximilian Fijak 3 years ago
committed by GitHub
parent
commit
ef35960a18
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 255
      Cargo.lock
  2. 24
      Cargo.toml
  3. 4
      docker/Dockerfile.j2
  4. 12
      docker/amd64/Dockerfile
  5. 12
      docker/amd64/Dockerfile.alpine
  6. 12
      docker/amd64/Dockerfile.buildx
  7. 12
      docker/amd64/Dockerfile.buildx.alpine
  8. 12
      docker/arm64/Dockerfile
  9. 12
      docker/arm64/Dockerfile.alpine
  10. 12
      docker/arm64/Dockerfile.buildx
  11. 12
      docker/arm64/Dockerfile.buildx.alpine
  12. 12
      docker/armv6/Dockerfile
  13. 12
      docker/armv6/Dockerfile.alpine
  14. 12
      docker/armv6/Dockerfile.buildx
  15. 12
      docker/armv6/Dockerfile.buildx.alpine
  16. 12
      docker/armv7/Dockerfile
  17. 12
      docker/armv7/Dockerfile.alpine
  18. 12
      docker/armv7/Dockerfile.buildx
  19. 12
      docker/armv7/Dockerfile.buildx.alpine
  20. 67
      src/api/admin.rs
  21. 2
      src/api/core/ciphers.rs
  22. 6
      src/api/core/emergency_access.rs
  23. 23
      src/api/core/mod.rs
  24. 367
      src/api/core/organizations.rs
  25. 5
      src/api/core/sends.rs
  26. 27
      src/api/core/two_factor/mod.rs
  27. 4
      src/api/web.rs
  28. 1
      src/db/models/mod.rs
  29. 150
      src/db/models/org_policy.rs
  30. 118
      src/db/models/organization.rs
  31. 10
      src/db/models/user.rs
  32. 1223
      src/static/scripts/bootstrap.css
  33. 190
      src/static/scripts/jquery-3.6.1.slim.js
  34. 4
      src/static/templates/admin/diagnostics.hbs
  35. 2
      src/static/templates/admin/organizations.hbs
  36. 2
      src/static/templates/admin/settings.hbs
  37. 2
      src/static/templates/admin/users.hbs
  38. 4
      src/util.rs

255
Cargo.lock

@ -54,9 +54,9 @@ dependencies = [
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.18" version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -76,6 +76,15 @@ dependencies = [
"alloc-no-stdlib", "alloc-no-stdlib",
] ]
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "ansi_term" name = "ansi_term"
version = "0.12.1" version = "0.12.1"
@ -228,9 +237,9 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.10.0" version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
@ -246,16 +255,16 @@ checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
[[package]] [[package]]
name = "cached" name = "cached"
version = "0.38.0" version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27e6092f8c7ba6e65a46f6f26d7d7997201d3a6f0e69ff5d2440b930d7c0513a" checksum = "f3e27085975166ffaacbd04527132e1cf5906fa612991f9b4fea08e787da2961"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"async_once", "async_once",
"cached_proc_macro", "cached_proc_macro",
"cached_proc_macro_types", "cached_proc_macro_types",
"futures", "futures",
"hashbrown 0.12.3", "hashbrown",
"instant", "instant",
"lazy_static", "lazy_static",
"once_cell", "once_cell",
@ -295,10 +304,11 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.20" version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6127248204b9aba09a362f6c930ef6a78f2c1b2215f8a7b398c06e1083f17af0" checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
dependencies = [ dependencies = [
"iana-time-zone",
"js-sys", "js-sys",
"num-integer", "num-integer",
"num-traits", "num-traits",
@ -353,7 +363,7 @@ dependencies = [
"rand", "rand",
"sha2", "sha2",
"subtle", "subtle",
"time 0.3.12", "time 0.3.14",
"version_check", "version_check",
] ]
@ -369,7 +379,7 @@ dependencies = [
"publicsuffix", "publicsuffix",
"serde", "serde",
"serde_json", "serde_json",
"time 0.3.12", "time 0.3.14",
"url 2.2.2", "url 2.2.2",
] ]
@ -391,9 +401,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.2" version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -449,9 +459,9 @@ dependencies = [
[[package]] [[package]]
name = "ctrlc" name = "ctrlc"
version = "3.2.2" version = "3.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b37feaa84e6861e00a1f5e5aa8da3ee56d605c9992d33e082786754828e20865" checksum = "1d91974fbbe88ec1df0c24a4f00f99583667a7e2e6272b2b92d294d81e462173"
dependencies = [ dependencies = [
"nix", "nix",
"winapi", "winapi",
@ -494,13 +504,14 @@ dependencies = [
[[package]] [[package]]
name = "dashmap" name = "dashmap"
version = "5.3.4" version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3495912c9c1ccf2e18976439f4443f3fee0fd61f424ff99fde6a66b15ecb448f" checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"hashbrown 0.12.3", "hashbrown",
"lock_api", "lock_api",
"once_cell",
"parking_lot_core", "parking_lot_core",
] ]
@ -632,9 +643,9 @@ dependencies = [
[[package]] [[package]]
name = "either" name = "either"
version = "1.7.0" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]] [[package]]
name = "email-encoding" name = "email-encoding"
@ -648,9 +659,9 @@ dependencies = [
[[package]] [[package]]
name = "email_address" name = "email_address"
version = "0.2.1" version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8684b7c9cb4857dfa1e5b9629ef584ba618c9b93bae60f58cb23f4f271d0468e" checksum = "b1b32a7a2580c4473f10f66b512c34bdd7d33c5e3473227ca833abdb5afe4809"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
@ -758,9 +769,9 @@ dependencies = [
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@ -773,9 +784,9 @@ dependencies = [
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
@ -783,15 +794,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
[[package]] [[package]]
name = "futures-executor" name = "futures-executor"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-task", "futures-task",
@ -800,15 +811,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68"
[[package]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -817,15 +828,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56"
[[package]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1"
[[package]] [[package]]
name = "futures-timer" name = "futures-timer"
@ -835,9 +846,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@ -926,9 +937,9 @@ dependencies = [
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.3.13" version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
@ -964,12 +975,6 @@ dependencies = [
"walkdir", "walkdir",
] ]
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.12.3" version = "0.12.3"
@ -1053,9 +1058,9 @@ dependencies = [
[[package]] [[package]]
name = "httparse" name = "httparse"
version = "1.7.1" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]] [[package]]
name = "httpdate" name = "httpdate"
@ -1100,6 +1105,20 @@ dependencies = [
"tokio-native-tls", "tokio-native-tls",
] ]
[[package]]
name = "iana-time-zone"
version = "0.1.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"js-sys",
"once_cell",
"wasm-bindgen",
"winapi",
]
[[package]] [[package]]
name = "ident_case" name = "ident_case"
version = "1.0.1" version = "1.0.1"
@ -1135,7 +1154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"hashbrown 0.12.3", "hashbrown",
"serde", "serde",
] ]
@ -1254,9 +1273,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.127" version = "0.2.132"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
[[package]] [[package]]
name = "libmimalloc-sys" name = "libmimalloc-sys"
@ -1286,9 +1305,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.7" version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"scopeguard", "scopeguard",
@ -1407,9 +1426,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.5.3" version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
dependencies = [ dependencies = [
"adler", "adler",
] ]
@ -1475,10 +1494,11 @@ dependencies = [
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.24.2" version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb"
dependencies = [ dependencies = [
"autocfg",
"bitflags", "bitflags",
"cfg-if", "cfg-if",
"libc", "libc",
@ -1577,9 +1597,9 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.13.0" version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
[[package]] [[package]]
name = "opaque-debug" name = "opaque-debug"
@ -1676,9 +1696,9 @@ dependencies = [
[[package]] [[package]]
name = "paste" name = "paste"
version = "1.0.8" version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22" checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1"
[[package]] [[package]]
name = "pear" name = "pear"
@ -1726,9 +1746,9 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]] [[package]]
name = "pest" name = "pest"
version = "2.2.1" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69486e2b8c2d2aeb9762db7b4e00b0331156393555cff467f4163ff06821eef8" checksum = "4b0560d531d1febc25a3c9398a62a71256c0178f2e3443baedd9ad4bb8c9deb4"
dependencies = [ dependencies = [
"thiserror", "thiserror",
"ucd-trie", "ucd-trie",
@ -1736,9 +1756,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_derive" name = "pest_derive"
version = "2.2.1" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b13570633aff33c6d22ce47dd566b10a3b9122c2fe9d8e7501895905be532b91" checksum = "905708f7f674518498c1f8d644481440f476d39ca6ecae83319bba7c6c12da91"
dependencies = [ dependencies = [
"pest", "pest",
"pest_generator", "pest_generator",
@ -1746,9 +1766,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_generator" name = "pest_generator"
version = "2.2.1" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3c567e5702efdc79fb18859ea74c3eb36e14c43da7b8c1f098a4ed6514ec7a0" checksum = "5803d8284a629cc999094ecd630f55e91b561a1d1ba75e233b00ae13b91a69ad"
dependencies = [ dependencies = [
"pest", "pest",
"pest_meta", "pest_meta",
@ -1759,9 +1779,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_meta" name = "pest_meta"
version = "2.2.1" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eb32be5ee3bbdafa8c7a18b0a8a8d962b66cfa2ceee4037f49267a50ee821fe" checksum = "1538eb784f07615c6d9a8ab061089c6c54a344c5b4301db51990ca1c241e8c04"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"pest", "pest",
@ -1770,18 +1790,18 @@ dependencies = [
[[package]] [[package]]
name = "phf" name = "phf"
version = "0.11.0" version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4724fa946c8d1e7cd881bd3dbee63ce32fc1e9e191e35786b3dc1320a3f68131" checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c"
dependencies = [ dependencies = [
"phf_shared", "phf_shared",
] ]
[[package]] [[package]]
name = "phf_codegen" name = "phf_codegen"
version = "0.11.0" version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32ba0c43d7a1b6492b2924a62290cfd83987828af037b0743b38e6ab092aee58" checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770"
dependencies = [ dependencies = [
"phf_generator", "phf_generator",
"phf_shared", "phf_shared",
@ -1789,9 +1809,9 @@ dependencies = [
[[package]] [[package]]
name = "phf_generator" name = "phf_generator"
version = "0.11.0" version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b450720b6f75cfbfabc195814bd3765f337a4f9a83186f8537297cac12f6705" checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf"
dependencies = [ dependencies = [
"phf_shared", "phf_shared",
"rand", "rand",
@ -1799,9 +1819,9 @@ dependencies = [
[[package]] [[package]]
name = "phf_shared" name = "phf_shared"
version = "0.11.0" version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd5609d4b2df87167f908a32e1b146ce309c16cf35df76bc11f440b756048e4" checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676"
dependencies = [ dependencies = [
"siphasher", "siphasher",
"uncased", "uncased",
@ -1888,18 +1908,16 @@ dependencies = [
[[package]] [[package]]
name = "psl-types" name = "psl-types"
version = "2.0.10" version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8eda7c62d9ecaafdf8b62374c006de0adf61666ae96a96ba74a37134aa4e470" checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
[[package]] [[package]]
name = "publicsuffix" name = "publicsuffix"
version = "2.1.1" version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "292972edad6bbecc137ab84c5e36421a4a6c979ea31d3cc73540dd04315b33e1" checksum = "aeeedb0b429dc462f30ad27ef3de97058b060016f47790c066757be38ef792b4"
dependencies = [ dependencies = [
"byteorder",
"hashbrown 0.11.2",
"idna 0.2.3", "idna 0.2.3",
"psl-types", "psl-types",
] ]
@ -1984,9 +2002,9 @@ dependencies = [
[[package]] [[package]]
name = "raw-cpuid" name = "raw-cpuid"
version = "10.4.0" version = "10.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c49596760fce12ca21550ac21dc5a9617b2ea4b6e0aa7d8dab8ff2824fc2bba" checksum = "6aa2540135b6a94f74c7bc90ad4b794f822026a894f3d7bcd185c100d13d4ad6"
dependencies = [ dependencies = [
"bitflags", "bitflags",
] ]
@ -2186,7 +2204,7 @@ dependencies = [
"serde_json", "serde_json",
"state", "state",
"tempfile", "tempfile",
"time 0.3.12", "time 0.3.14",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"tokio-util", "tokio-util",
@ -2235,7 +2253,7 @@ dependencies = [
"smallvec", "smallvec",
"stable-pattern", "stable-pattern",
"state", "state",
"time 0.3.12", "time 0.3.14",
"tokio", "tokio",
"tokio-rustls", "tokio-rustls",
"uncased", "uncased",
@ -2332,9 +2350,9 @@ dependencies = [
[[package]] [[package]]
name = "security-framework" name = "security-framework"
version = "2.6.1" version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"core-foundation", "core-foundation",
@ -2355,9 +2373,9 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.142" version = "1.0.144"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2" checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@ -2374,9 +2392,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.142" version = "1.0.144"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34b5b8d809babe02f538c2cfec6f2c1ed10804c0e5a6a041a049a4f5588ccc2e" checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2385,9 +2403,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.83" version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -2419,9 +2437,9 @@ dependencies = [
[[package]] [[package]]
name = "sha1" name = "sha1"
version = "0.10.1" version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" checksum = "006769ba83e921b3085caa8334186b00cf92b4cb1a6cf4632fbccc8eff5c7549"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures",
@ -2430,9 +2448,9 @@ dependencies = [
[[package]] [[package]]
name = "sha2" name = "sha2"
version = "0.10.2" version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" checksum = "cf9db03534dff993187064c4e0c05a5708d2a9728ace9a8959b77bedf415dac5"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures",
@ -2466,7 +2484,7 @@ dependencies = [
"num-bigint", "num-bigint",
"num-traits", "num-traits",
"thiserror", "thiserror",
"time 0.3.12", "time 0.3.14",
] ]
[[package]] [[package]]
@ -2492,9 +2510,9 @@ checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.4.4" version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
dependencies = [ dependencies = [
"libc", "libc",
"winapi", "winapi",
@ -2563,7 +2581,7 @@ dependencies = [
"hostname", "hostname",
"libc", "libc",
"log", "log",
"time 0.3.12", "time 0.3.14",
] ]
[[package]] [[package]]
@ -2582,18 +2600,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.32" version = "1.0.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.32" version = "1.0.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2630,12 +2648,11 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.12" version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74b7cc93fc23ba97fde84f7eea56c55d1ba183f495c6715defdfc7b9cb8c870f" checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b"
dependencies = [ dependencies = [
"itoa", "itoa",
"js-sys",
"libc", "libc",
"num_threads", "num_threads",
"time-macros", "time-macros",
@ -2664,9 +2681,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.20.1" version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" checksum = "89797afd69d206ccd11fb0ea560a44bbb87731d020670e79416d442919257d42"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"bytes", "bytes",
@ -2932,18 +2949,18 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
[[package]] [[package]]
name = "ubyte" name = "ubyte"
version = "0.10.2" version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a58e29f263341a29bb79e14ad7fda5f63b1c7e48929bad4c685d7876b1d04e94" checksum = "c81f0dae7d286ad0d9366d7679a77934cfc3cf3a8d67e82669794412b2368fe6"
dependencies = [ dependencies = [
"serde", "serde",
] ]
[[package]] [[package]]
name = "ucd-trie" name = "ucd-trie"
version = "0.1.4" version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
[[package]] [[package]]
name = "uncased" name = "uncased"
@ -3088,7 +3105,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"syslog", "syslog",
"time 0.3.12", "time 0.3.14",
"tokio", "tokio",
"tokio-tungstenite", "tokio-tungstenite",
"totp-lite", "totp-lite",

24
Cargo.toml

@ -42,10 +42,10 @@ tracing = { version = "0.1.36", features = ["log"] } # Needed to have lettre and
backtrace = "0.3.66" # Logging panics to logfile instead stderr only backtrace = "0.3.66" # Logging panics to logfile instead stderr only
# A `dotenv` implementation for Rust # A `dotenv` implementation for Rust
dotenvy = { version = "0.15.1", default-features = false } dotenvy = { version = "=0.15.1", default-features = false }
# Lazy initialization # Lazy initialization
once_cell = "1.13.0" once_cell = "1.14.0"
# Numerical libraries # Numerical libraries
num-traits = "0.2.15" num-traits = "0.2.15"
@ -57,15 +57,15 @@ rocket = { version = "0.5.0-rc.2", features = ["tls", "json"], default-features
# WebSockets libraries # WebSockets libraries
tokio-tungstenite = "0.17.2" tokio-tungstenite = "0.17.2"
rmpv = "1.0.0" # MessagePack library rmpv = "1.0.0" # MessagePack library
dashmap = "5.3.4" # Concurrent hashmap implementation dashmap = "5.4.0"
# Async futures # Async futures
futures = "0.3.21" futures = "0.3.24"
tokio = { version = "1.20.1", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time"] } tokio = { version = "1.21.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time"] }
# A generic serialization/deserialization framework # A generic serialization/deserialization framework
serde = { version = "1.0.142", features = ["derive"] } serde = { version = "1.0.144", features = ["derive"] }
serde_json = "1.0.83" serde_json = "1.0.85"
# A safe, extensible ORM and Query builder # A safe, extensible ORM and Query builder
diesel = { version = "1.4.8", features = ["chrono", "r2d2"] } diesel = { version = "1.4.8", features = ["chrono", "r2d2"] }
@ -82,9 +82,9 @@ ring = "0.16.20"
uuid = { version = "1.1.2", features = ["v4"] } uuid = { version = "1.1.2", features = ["v4"] }
# Date and time libraries # Date and time libraries
chrono = { version = "0.4.20", features = ["clock", "serde"], default-features = false } chrono = { version = "0.4.22", features = ["clock", "serde"], default-features = false }
chrono-tz = "0.6.3" chrono-tz = "0.6.3"
time = "0.3.12" time = "0.3.14"
# Job scheduler # Job scheduler
job_scheduler_ng = "2.0.1" job_scheduler_ng = "2.0.1"
@ -122,7 +122,7 @@ html5gum = "0.5.2"
regex = { version = "1.6.0", features = ["std", "perf", "unicode-perl"], default-features = false } regex = { version = "1.6.0", features = ["std", "perf", "unicode-perl"], default-features = false }
data-url = "0.1.1" data-url = "0.1.1"
bytes = "1.2.1" bytes = "1.2.1"
cached = "0.38.0" cached = "0.39.0"
# Used for custom short lived cookie jar during favicon extraction # Used for custom short lived cookie jar during favicon extraction
cookie = "0.16.0" cookie = "0.16.0"
@ -135,11 +135,11 @@ openssl = "0.10.41"
pico-args = "0.5.0" pico-args = "0.5.0"
# Macro ident concatenation # Macro ident concatenation
paste = "1.0.8" paste = "1.0.9"
governor = "0.4.2" governor = "0.4.2"
# Capture CTRL+C # Capture CTRL+C
ctrlc = { version = "3.2.2", features = ["termination"] } ctrlc = { version = "3.2.3", features = ["termination"] }
# Allow overriding the default memory allocator # Allow overriding the default memory allocator
# Mainly used for the musl builds, since the default musl malloc is very slow # Mainly used for the musl builds, since the default musl malloc is very slow

4
docker/Dockerfile.j2

@ -59,8 +59,8 @@
# https://docs.docker.com/develop/develop-images/multistage-build/ # https://docs.docker.com/develop/develop-images/multistage-build/
# https://whitfin.io/speeding-up-rust-docker-builds/ # https://whitfin.io/speeding-up-rust-docker-builds/
####################### VAULT BUILD IMAGE ####################### ####################### VAULT BUILD IMAGE #######################
{% set vault_version = "v2022.6.2" %} {% set vault_version = "v2022.9.0" %}
{% set vault_image_digest = "sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70" %} {% set vault_image_digest = "sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc" %}
# The web-vault digest specifies a particular web-vault build on Docker Hub. # The web-vault digest specifies a particular web-vault build on Docker Hub.
# Using the digest instead of the tag name provides better security, # Using the digest instead of the tag name provides better security,
# as the digest of an image is immutable, whereas a tag name can later # as the digest of an image is immutable, whereas a tag name can later

12
docker/amd64/Dockerfile

@ -16,15 +16,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull vaultwarden/web-vault:v2022.6.2 # $ docker pull vaultwarden/web-vault:v2022.9.0
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.9.0
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70] # [vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc
# [vaultwarden/web-vault:v2022.6.2] # [vaultwarden/web-vault:v2022.9.0]
# #
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault FROM vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM rust:1.61-bullseye as build FROM rust:1.61-bullseye as build

12
docker/amd64/Dockerfile.alpine

@ -16,15 +16,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull vaultwarden/web-vault:v2022.6.2 # $ docker pull vaultwarden/web-vault:v2022.9.0
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.9.0
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70] # [vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc
# [vaultwarden/web-vault:v2022.6.2] # [vaultwarden/web-vault:v2022.9.0]
# #
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault FROM vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:x86_64-musl-stable-1.61.0 as build FROM blackdex/rust-musl:x86_64-musl-stable-1.61.0 as build

12
docker/amd64/Dockerfile.buildx

@ -16,15 +16,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull vaultwarden/web-vault:v2022.6.2 # $ docker pull vaultwarden/web-vault:v2022.9.0
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.9.0
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70] # [vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc
# [vaultwarden/web-vault:v2022.6.2] # [vaultwarden/web-vault:v2022.9.0]
# #
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault FROM vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM rust:1.61-bullseye as build FROM rust:1.61-bullseye as build

12
docker/amd64/Dockerfile.buildx.alpine

@ -16,15 +16,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull vaultwarden/web-vault:v2022.6.2 # $ docker pull vaultwarden/web-vault:v2022.9.0
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.9.0
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70] # [vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc
# [vaultwarden/web-vault:v2022.6.2] # [vaultwarden/web-vault:v2022.9.0]
# #
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault FROM vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:x86_64-musl-stable-1.61.0 as build FROM blackdex/rust-musl:x86_64-musl-stable-1.61.0 as build

12
docker/arm64/Dockerfile

@ -16,15 +16,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull vaultwarden/web-vault:v2022.6.2 # $ docker pull vaultwarden/web-vault:v2022.9.0
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.9.0
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70] # [vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc
# [vaultwarden/web-vault:v2022.6.2] # [vaultwarden/web-vault:v2022.9.0]
# #
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault FROM vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM rust:1.61-bullseye as build FROM rust:1.61-bullseye as build

12
docker/arm64/Dockerfile.alpine

@ -16,15 +16,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull vaultwarden/web-vault:v2022.6.2 # $ docker pull vaultwarden/web-vault:v2022.9.0
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.9.0
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70] # [vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc
# [vaultwarden/web-vault:v2022.6.2] # [vaultwarden/web-vault:v2022.9.0]
# #
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault FROM vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:aarch64-musl-stable-1.61.0 as build FROM blackdex/rust-musl:aarch64-musl-stable-1.61.0 as build

12
docker/arm64/Dockerfile.buildx

@ -16,15 +16,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull vaultwarden/web-vault:v2022.6.2 # $ docker pull vaultwarden/web-vault:v2022.9.0
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.9.0
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70] # [vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc
# [vaultwarden/web-vault:v2022.6.2] # [vaultwarden/web-vault:v2022.9.0]
# #
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault FROM vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM rust:1.61-bullseye as build FROM rust:1.61-bullseye as build

12
docker/arm64/Dockerfile.buildx.alpine

@ -16,15 +16,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull vaultwarden/web-vault:v2022.6.2 # $ docker pull vaultwarden/web-vault:v2022.9.0
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.9.0
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70] # [vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc
# [vaultwarden/web-vault:v2022.6.2] # [vaultwarden/web-vault:v2022.9.0]
# #
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault FROM vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:aarch64-musl-stable-1.61.0 as build FROM blackdex/rust-musl:aarch64-musl-stable-1.61.0 as build

12
docker/armv6/Dockerfile

@ -16,15 +16,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull vaultwarden/web-vault:v2022.6.2 # $ docker pull vaultwarden/web-vault:v2022.9.0
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.9.0
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70] # [vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc
# [vaultwarden/web-vault:v2022.6.2] # [vaultwarden/web-vault:v2022.9.0]
# #
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault FROM vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM rust:1.61-bullseye as build FROM rust:1.61-bullseye as build

12
docker/armv6/Dockerfile.alpine

@ -16,15 +16,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull vaultwarden/web-vault:v2022.6.2 # $ docker pull vaultwarden/web-vault:v2022.9.0
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.9.0
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70] # [vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc
# [vaultwarden/web-vault:v2022.6.2] # [vaultwarden/web-vault:v2022.9.0]
# #
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault FROM vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:arm-musleabi-stable-1.61.0 as build FROM blackdex/rust-musl:arm-musleabi-stable-1.61.0 as build

12
docker/armv6/Dockerfile.buildx

@ -16,15 +16,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull vaultwarden/web-vault:v2022.6.2 # $ docker pull vaultwarden/web-vault:v2022.9.0
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.9.0
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70] # [vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc
# [vaultwarden/web-vault:v2022.6.2] # [vaultwarden/web-vault:v2022.9.0]
# #
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault FROM vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM rust:1.61-bullseye as build FROM rust:1.61-bullseye as build

12
docker/armv6/Dockerfile.buildx.alpine

@ -16,15 +16,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull vaultwarden/web-vault:v2022.6.2 # $ docker pull vaultwarden/web-vault:v2022.9.0
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.9.0
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70] # [vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc
# [vaultwarden/web-vault:v2022.6.2] # [vaultwarden/web-vault:v2022.9.0]
# #
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault FROM vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:arm-musleabi-stable-1.61.0 as build FROM blackdex/rust-musl:arm-musleabi-stable-1.61.0 as build

12
docker/armv7/Dockerfile

@ -16,15 +16,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull vaultwarden/web-vault:v2022.6.2 # $ docker pull vaultwarden/web-vault:v2022.9.0
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.9.0
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70] # [vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc
# [vaultwarden/web-vault:v2022.6.2] # [vaultwarden/web-vault:v2022.9.0]
# #
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault FROM vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM rust:1.61-bullseye as build FROM rust:1.61-bullseye as build

12
docker/armv7/Dockerfile.alpine

@ -16,15 +16,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull vaultwarden/web-vault:v2022.6.2 # $ docker pull vaultwarden/web-vault:v2022.9.0
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.9.0
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70] # [vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc
# [vaultwarden/web-vault:v2022.6.2] # [vaultwarden/web-vault:v2022.9.0]
# #
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault FROM vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:armv7-musleabihf-stable-1.61.0 as build FROM blackdex/rust-musl:armv7-musleabihf-stable-1.61.0 as build

12
docker/armv7/Dockerfile.buildx

@ -16,15 +16,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull vaultwarden/web-vault:v2022.6.2 # $ docker pull vaultwarden/web-vault:v2022.9.0
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.9.0
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70] # [vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc
# [vaultwarden/web-vault:v2022.6.2] # [vaultwarden/web-vault:v2022.9.0]
# #
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault FROM vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM rust:1.61-bullseye as build FROM rust:1.61-bullseye as build

12
docker/armv7/Dockerfile.buildx.alpine

@ -16,15 +16,15 @@
# - From https://hub.docker.com/r/vaultwarden/web-vault/tags, # - From https://hub.docker.com/r/vaultwarden/web-vault/tags,
# click the tag name to view the digest of the image it currently points to. # click the tag name to view the digest of the image it currently points to.
# - From the command line: # - From the command line:
# $ docker pull vaultwarden/web-vault:v2022.6.2 # $ docker pull vaultwarden/web-vault:v2022.9.0
# $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.6.2 # $ docker image inspect --format "{{.RepoDigests}}" vaultwarden/web-vault:v2022.9.0
# [vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70] # [vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc]
# #
# - Conversely, to get the tag name from the digest: # - Conversely, to get the tag name from the digest:
# $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 # $ docker image inspect --format "{{.RepoTags}}" vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc
# [vaultwarden/web-vault:v2022.6.2] # [vaultwarden/web-vault:v2022.9.0]
# #
FROM vaultwarden/web-vault@sha256:1dfda41cbddeac5bc59540261fff8defcac37170b5ba02d29c12fa1215498f70 as vault FROM vaultwarden/web-vault@sha256:99d21235a64084c9115f4aa3da1298881b8bbf146c0d48d6530b4d685a6a6fbc as vault
########################## BUILD IMAGE ########################## ########################## BUILD IMAGE ##########################
FROM blackdex/rust-musl:armv7-musleabihf-stable-1.61.0 as build FROM blackdex/rust-musl:armv7-musleabihf-stable-1.61.0 as build

67
src/api/admin.rs

@ -7,8 +7,8 @@ use rocket::serde::json::Json;
use rocket::{ use rocket::{
form::Form, form::Form,
http::{Cookie, CookieJar, SameSite, Status}, http::{Cookie, CookieJar, SameSite, Status},
request::{self, FlashMessage, FromRequest, Outcome, Request}, request::{self, FromRequest, Outcome, Request},
response::{content::RawHtml as Html, Flash, Redirect}, response::{content::RawHtml as Html, Redirect},
Route, Route,
}; };
@ -141,10 +141,24 @@ fn admin_url(referer: Referer) -> String {
} }
} }
#[derive(Responder)]
enum AdminResponse {
#[response(status = 200)]
Ok(ApiResult<Html<String>>),
#[response(status = 401)]
Unauthorized(ApiResult<Html<String>>),
#[response(status = 429)]
TooManyRequests(ApiResult<Html<String>>),
}
#[get("/", rank = 2)] #[get("/", rank = 2)]
fn admin_login(flash: Option<FlashMessage<'_>>) -> ApiResult<Html<String>> { fn admin_login() -> ApiResult<Html<String>> {
render_admin_login(None)
}
fn render_admin_login(msg: Option<&str>) -> ApiResult<Html<String>> {
// If there is an error, show it // If there is an error, show it
let msg = flash.map(|msg| format!("{}: {}", msg.kind(), msg.message())); let msg = msg.map(|msg| format!("Error: {msg}"));
let json = json!({ let json = json!({
"page_content": "admin/login", "page_content": "admin/login",
"version": VERSION, "version": VERSION,
@ -163,22 +177,17 @@ struct LoginForm {
} }
#[post("/", data = "<data>")] #[post("/", data = "<data>")]
fn post_admin_login( fn post_admin_login(data: Form<LoginForm>, cookies: &CookieJar<'_>, ip: ClientIp) -> AdminResponse {
data: Form<LoginForm>,
cookies: &CookieJar<'_>,
ip: ClientIp,
referer: Referer,
) -> Result<Redirect, Flash<Redirect>> {
let data = data.into_inner(); let data = data.into_inner();
if crate::ratelimit::check_limit_admin(&ip.ip).is_err() { if crate::ratelimit::check_limit_admin(&ip.ip).is_err() {
return Err(Flash::error(Redirect::to(admin_url(referer)), "Too many requests, try again later.")); return AdminResponse::TooManyRequests(render_admin_login(Some("Too many requests, try again later.")));
} }
// If the token is invalid, redirect to login page // If the token is invalid, redirect to login page
if !_validate_token(&data.token) { if !_validate_token(&data.token) {
error!("Invalid admin token. IP: {}", ip.ip); error!("Invalid admin token. IP: {}", ip.ip);
Err(Flash::error(Redirect::to(admin_url(referer)), "Invalid admin token, please try again.")) AdminResponse::Unauthorized(render_admin_login(Some("Invalid admin token, please try again.")))
} else { } else {
// If the token received is valid, generate JWT and save it as a cookie // If the token received is valid, generate JWT and save it as a cookie
let claims = generate_admin_claims(); let claims = generate_admin_claims();
@ -192,7 +201,7 @@ fn post_admin_login(
.finish(); .finish();
cookies.add(cookie); cookies.add(cookie);
Ok(Redirect::to(admin_url(referer))) AdminResponse::Ok(render_admin_page())
} }
} }
@ -244,12 +253,16 @@ impl AdminTemplateData {
} }
} }
#[get("/", rank = 1)] fn render_admin_page() -> ApiResult<Html<String>> {
fn admin_page(_token: AdminToken) -> ApiResult<Html<String>> {
let text = AdminTemplateData::new().render()?; let text = AdminTemplateData::new().render()?;
Ok(Html(text)) Ok(Html(text))
} }
#[get("/", rank = 1)]
fn admin_page(_token: AdminToken) -> ApiResult<Html<String>> {
render_admin_page()
}
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[allow(non_snake_case)] #[allow(non_snake_case)]
struct InviteData { struct InviteData {
@ -303,7 +316,7 @@ async fn test_smtp(data: Json<InviteData>, _token: AdminToken) -> EmptyResult {
#[get("/logout")] #[get("/logout")]
fn logout(cookies: &CookieJar<'_>, referer: Referer) -> Redirect { fn logout(cookies: &CookieJar<'_>, referer: Referer) -> Redirect {
cookies.remove(Cookie::build(COOKIE_NAME, "").path(admin_path()).finish()); cookies.remove(Cookie::build(COOKIE_NAME, "").path(admin_path()).finish());
Redirect::to(admin_url(referer)) Redirect::temporary(admin_url(referer))
} }
#[get("/users")] #[get("/users")]
@ -418,15 +431,26 @@ async fn update_user_org_type(data: Json<UserOrgTypeData>, _token: AdminToken, c
}; };
if user_to_edit.atype == UserOrgType::Owner && new_type != UserOrgType::Owner { if user_to_edit.atype == UserOrgType::Owner && new_type != UserOrgType::Owner {
// Removing owner permmission, check that there are at least another owner // Removing owner permmission, check that there is at least one other confirmed owner
let num_owners = if UserOrganization::count_confirmed_by_org_and_type(&data.org_uuid, UserOrgType::Owner, &conn).await <= 1 {
UserOrganization::find_by_org_and_type(&data.org_uuid, UserOrgType::Owner as i32, &conn).await.len();
if num_owners <= 1 {
err!("Can't change the type of the last owner") err!("Can't change the type of the last owner")
} }
} }
// This check is also done at api::organizations::{accept_invite(), _confirm_invite, _activate_user(), edit_user()}, update_user_org_type
// It returns different error messages per function.
if new_type < UserOrgType::Admin {
match OrgPolicy::is_user_allowed(&user_to_edit.user_uuid, &user_to_edit.org_uuid, true, &conn).await {
Ok(_) => {}
Err(OrgPolicyErr::TwoFactorMissing) => {
err!("You cannot modify this user to this type because it has no two-step login method activated");
}
Err(OrgPolicyErr::SingleOrgEnforced) => {
err!("You cannot modify this user to this type because it is a member of an organization which forbids it");
}
}
}
user_to_edit.atype = new_type; user_to_edit.atype = new_type;
user_to_edit.save(&conn).await user_to_edit.save(&conn).await
} }
@ -498,7 +522,6 @@ use cached::proc_macro::cached;
async fn get_release_info(has_http_access: bool, running_within_docker: bool) -> (String, String, String) { async fn get_release_info(has_http_access: bool, running_within_docker: bool) -> (String, String, String) {
// If the HTTP Check failed, do not even attempt to check for new versions since we were not able to connect with github.com anyway. // If the HTTP Check failed, do not even attempt to check for new versions since we were not able to connect with github.com anyway.
if has_http_access { if has_http_access {
info!("Running get_release_info!!");
( (
match get_github_api::<GitRelease>("https://api.github.com/repos/dani-garcia/vaultwarden/releases/latest") match get_github_api::<GitRelease>("https://api.github.com/repos/dani-garcia/vaultwarden/releases/latest")
.await .await

2
src/api/core/ciphers.rs

@ -328,7 +328,7 @@ async fn enforce_personal_ownership_policy(data: Option<&CipherData>, headers: &
if data.is_none() || data.unwrap().OrganizationId.is_none() { if data.is_none() || data.unwrap().OrganizationId.is_none() {
let user_uuid = &headers.user.uuid; let user_uuid = &headers.user.uuid;
let policy_type = OrgPolicyType::PersonalOwnership; let policy_type = OrgPolicyType::PersonalOwnership;
if OrgPolicy::is_applicable_to_user(user_uuid, policy_type, conn).await { if OrgPolicy::is_applicable_to_user(user_uuid, policy_type, None, conn).await {
err!("Due to an Enterprise Policy, you are restricted from saving items to your personal vault.") err!("Due to an Enterprise Policy, you are restricted from saving items to your personal vault.")
} }
} }

6
src/api/core/emergency_access.rs

@ -258,7 +258,7 @@ async fn send_invite(data: JsonUpcase<EmergencyAccessInviteData>, headers: Heade
match User::find_by_mail(&email, &conn).await { match User::find_by_mail(&email, &conn).await {
Some(user) => { Some(user) => {
match accept_invite_process(user.uuid, new_emergency_access.uuid, Some(email), conn.borrow()).await { match accept_invite_process(user.uuid, new_emergency_access.uuid, Some(email), conn.borrow()).await {
Ok(v) => (v), Ok(v) => v,
Err(e) => err!(e.to_string()), Err(e) => err!(e.to_string()),
} }
} }
@ -317,7 +317,7 @@ async fn resend_invite(emer_id: String, headers: Headers, conn: DbConn) -> Empty
match accept_invite_process(grantee_user.uuid, emergency_access.uuid, emergency_access.email, conn.borrow()) match accept_invite_process(grantee_user.uuid, emergency_access.uuid, emergency_access.email, conn.borrow())
.await .await
{ {
Ok(v) => (v), Ok(v) => v,
Err(e) => err!(e.to_string()), Err(e) => err!(e.to_string()),
} }
} }
@ -363,7 +363,7 @@ async fn accept_invite(emer_id: String, data: JsonUpcase<AcceptData>, conn: DbCo
&& (claims.grantor_email.is_some() && grantor_user.email == claims.grantor_email.unwrap()) && (claims.grantor_email.is_some() && grantor_user.email == claims.grantor_email.unwrap())
{ {
match accept_invite_process(grantee_user.uuid.clone(), emer_id, Some(grantee_user.email.clone()), &conn).await { match accept_invite_process(grantee_user.uuid.clone(), emer_id, Some(grantee_user.email.clone()), &conn).await {
Ok(v) => (v), Ok(v) => v,
Err(e) => err!(e.to_string()), Err(e) => err!(e.to_string()),
} }

23
src/api/core/mod.rs

@ -16,7 +16,7 @@ pub fn routes() -> Vec<Route> {
let mut device_token_routes = routes![clear_device_token, put_device_token]; let mut device_token_routes = routes![clear_device_token, put_device_token];
let mut eq_domains_routes = routes![get_eq_domains, post_eq_domains, put_eq_domains]; let mut eq_domains_routes = routes![get_eq_domains, post_eq_domains, put_eq_domains];
let mut hibp_routes = routes![hibp_breach]; let mut hibp_routes = routes![hibp_breach];
let mut meta_routes = routes![alive, now, version]; let mut meta_routes = routes![alive, now, version, config];
let mut routes = Vec::new(); let mut routes = Vec::new();
routes.append(&mut accounts::routes()); routes.append(&mut accounts::routes());
@ -200,3 +200,24 @@ pub fn now() -> Json<String> {
fn version() -> Json<&'static str> { fn version() -> Json<&'static str> {
Json(crate::VERSION.unwrap_or_default()) Json(crate::VERSION.unwrap_or_default())
} }
#[get("/config")]
fn config() -> Json<Value> {
let domain = crate::CONFIG.domain();
Json(json!({
"version": crate::VERSION,
"gitHash": option_env!("GIT_REV"),
"server": {
"name": "Vaultwarden",
"url": "https://github.com/dani-garcia/vaultwarden"
},
"environment": {
"vault": domain,
"api": format!("{domain}/api"),
"identity": format!("{domain}/identity"),
"admin": format!("{domain}/admin"),
"notifications": format!("{domain}/notifications"),
"sso": "",
},
}))
}

367
src/api/core/organizations.rs

@ -61,6 +61,14 @@ pub fn routes() -> Vec<Route> {
import, import,
post_org_keys, post_org_keys,
bulk_public_keys, bulk_public_keys,
deactivate_organization_user,
bulk_deactivate_organization_user,
revoke_organization_user,
bulk_revoke_organization_user,
activate_organization_user,
bulk_activate_organization_user,
restore_organization_user,
bulk_restore_organization_user
get_groups, get_groups,
post_groups, post_groups,
get_group, get_group,
@ -131,7 +139,7 @@ async fn create_organization(headers: Headers, data: JsonUpcase<OrgData>, conn:
if !CONFIG.is_org_creation_allowed(&headers.user.email) { if !CONFIG.is_org_creation_allowed(&headers.user.email) {
err!("User not allowed to create organizations") err!("User not allowed to create organizations")
} }
if OrgPolicy::is_applicable_to_user(&headers.user.uuid, OrgPolicyType::SingleOrg, &conn).await { if OrgPolicy::is_applicable_to_user(&headers.user.uuid, OrgPolicyType::SingleOrg, None, &conn).await {
err!( err!(
"You may not create an organization. You belong to an organization which has a policy that prohibits you from being a member of any other organization." "You may not create an organization. You belong to an organization which has a policy that prohibits you from being a member of any other organization."
) )
@ -196,13 +204,10 @@ async fn leave_organization(org_id: String, headers: Headers, conn: DbConn) -> E
match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn).await { match UserOrganization::find_by_user_and_org(&headers.user.uuid, &org_id, &conn).await {
None => err!("User not part of organization"), None => err!("User not part of organization"),
Some(user_org) => { Some(user_org) => {
if user_org.atype == UserOrgType::Owner { if user_org.atype == UserOrgType::Owner
let num_owners = && UserOrganization::count_confirmed_by_org_and_type(&org_id, UserOrgType::Owner, &conn).await <= 1
UserOrganization::find_by_org_and_type(&org_id, UserOrgType::Owner as i32, &conn).await.len(); {
err!("The last owner can't leave")
if num_owners <= 1 {
err!("The last owner can't leave")
}
} }
user_org.delete(&conn).await user_org.delete(&conn).await
@ -797,17 +802,16 @@ struct AcceptData {
Token: String, Token: String,
} }
#[post("/organizations/<_org_id>/users/<_org_user_id>/accept", data = "<data>")] #[post("/organizations/<org_id>/users/<_org_user_id>/accept", data = "<data>")]
async fn accept_invite( async fn accept_invite(
_org_id: String, org_id: String,
_org_user_id: String, _org_user_id: String,
data: JsonUpcase<AcceptData>, data: JsonUpcase<AcceptData>,
conn: DbConn, conn: DbConn,
) -> EmptyResult { ) -> EmptyResult {
// The web-vault passes org_id and org_user_id in the URL, but we are just reading them from the JWT instead // The web-vault passes org_id and org_user_id in the URL, but we are just reading them from the JWT instead
let data: AcceptData = data.into_inner().data; let data: AcceptData = data.into_inner().data;
let token = &data.Token; let claims = decode_invite(&data.Token)?;
let claims = decode_invite(token)?;
match User::find_by_mail(&claims.email, &conn).await { match User::find_by_mail(&claims.email, &conn).await {
Some(_) => { Some(_) => {
@ -823,46 +827,20 @@ async fn accept_invite(
err!("User already accepted the invitation") err!("User already accepted the invitation")
} }
let user_twofactor_disabled = TwoFactor::find_by_user(&user_org.user_uuid, &conn).await.is_empty(); // This check is also done at accept_invite(), _confirm_invite, _activate_user(), edit_user(), admin::update_user_org_type
// It returns different error messages per function.
let policy = OrgPolicyType::TwoFactorAuthentication as i32; if user_org.atype < UserOrgType::Admin {
let org_twofactor_policy_enabled = match OrgPolicy::is_user_allowed(&user_org.user_uuid, &org_id, false, &conn).await {
match OrgPolicy::find_by_org_and_type(&user_org.org_uuid, policy, &conn).await { Ok(_) => {}
Some(p) => p.enabled, Err(OrgPolicyErr::TwoFactorMissing) => {
None => false, err!("You cannot join this organization until you enable two-step login on your user account");
}; }
Err(OrgPolicyErr::SingleOrgEnforced) => {
if org_twofactor_policy_enabled && user_twofactor_disabled { err!("You cannot join this organization because you are a member of an organization which forbids it");
err!("You cannot join this organization until you enable two-step login on your user account.") }
}
// Enforce Single Organization Policy of organization user is trying to join
let single_org_policy_enabled =
match OrgPolicy::find_by_org_and_type(&user_org.org_uuid, OrgPolicyType::SingleOrg as i32, &conn)
.await
{
Some(p) => p.enabled,
None => false,
};
if single_org_policy_enabled && user_org.atype < UserOrgType::Admin {
let is_member_of_another_org = UserOrganization::find_any_state_by_user(&user_org.user_uuid, &conn)
.await
.into_iter()
.filter(|uo| uo.org_uuid != user_org.org_uuid)
.count()
> 1;
if is_member_of_another_org {
err!("You may not join this organization until you leave or remove all other organizations.")
} }
} }
// Enforce Single Organization Policy of other organizations user is a member of
if OrgPolicy::is_applicable_to_user(&user_org.user_uuid, OrgPolicyType::SingleOrg, &conn).await {
err!(
"You cannot join this organization because you are a member of an organization which forbids it"
)
}
user_org.status = UserOrgStatus::Accepted as i32; user_org.status = UserOrgStatus::Accepted as i32;
user_org.save(&conn).await?; user_org.save(&conn).await?;
} }
@ -966,6 +944,20 @@ async fn _confirm_invite(
err!("User in invalid state") err!("User in invalid state")
} }
// This check is also done at accept_invite(), _confirm_invite, _activate_user(), edit_user(), admin::update_user_org_type
// It returns different error messages per function.
if user_to_confirm.atype < UserOrgType::Admin {
match OrgPolicy::is_user_allowed(&user_to_confirm.user_uuid, org_id, true, conn).await {
Ok(_) => {}
Err(OrgPolicyErr::TwoFactorMissing) => {
err!("You cannot confirm this user because it has no two-step login method activated");
}
Err(OrgPolicyErr::SingleOrgEnforced) => {
err!("You cannot confirm this user because it is a member of an organization which forbids it");
}
}
}
user_to_confirm.status = UserOrgStatus::Confirmed as i32; user_to_confirm.status = UserOrgStatus::Confirmed as i32;
user_to_confirm.akey = key.to_string(); user_to_confirm.akey = key.to_string();
@ -1045,14 +1037,26 @@ async fn edit_user(
} }
if user_to_edit.atype == UserOrgType::Owner && new_type != UserOrgType::Owner { if user_to_edit.atype == UserOrgType::Owner && new_type != UserOrgType::Owner {
// Removing owner permmission, check that there are at least another owner // Removing owner permmission, check that there is at least one other confirmed owner
let num_owners = UserOrganization::find_by_org_and_type(&org_id, UserOrgType::Owner as i32, &conn).await.len(); if UserOrganization::count_confirmed_by_org_and_type(&org_id, UserOrgType::Owner, &conn).await <= 1 {
if num_owners <= 1 {
err!("Can't delete the last owner") err!("Can't delete the last owner")
} }
} }
// This check is also done at accept_invite(), _confirm_invite, _activate_user(), edit_user(), admin::update_user_org_type
// It returns different error messages per function.
if new_type < UserOrgType::Admin {
match OrgPolicy::is_user_allowed(&user_to_edit.user_uuid, &org_id, true, &conn).await {
Ok(_) => {}
Err(OrgPolicyErr::TwoFactorMissing) => {
err!("You cannot modify this user to this type because it has no two-step login method activated");
}
Err(OrgPolicyErr::SingleOrgEnforced) => {
err!("You cannot modify this user to this type because it is a member of an organization which forbids it");
}
}
}
user_to_edit.access_all = data.AccessAll; user_to_edit.access_all = data.AccessAll;
user_to_edit.atype = new_type as i32; user_to_edit.atype = new_type as i32;
@ -1131,10 +1135,8 @@ async fn _delete_user(org_id: &str, org_user_id: &str, headers: &AdminHeaders, c
} }
if user_to_delete.atype == UserOrgType::Owner { if user_to_delete.atype == UserOrgType::Owner {
// Removing owner, check that there are at least another owner // Removing owner, check that there is at least one other confirmed owner
let num_owners = UserOrganization::find_by_org_and_type(org_id, UserOrgType::Owner as i32, conn).await.len(); if UserOrganization::count_confirmed_by_org_and_type(org_id, UserOrgType::Owner, conn).await <= 1 {
if num_owners <= 1 {
err!("Can't delete the last owner") err!("Can't delete the last owner")
} }
} }
@ -1303,7 +1305,7 @@ async fn get_policy(org_id: String, pol_type: i32, _headers: AdminHeaders, conn:
None => err!("Invalid or unsupported policy type"), None => err!("Invalid or unsupported policy type"),
}; };
let policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type, &conn).await { let policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type_enum, &conn).await {
Some(p) => p, Some(p) => p,
None => OrgPolicy::new(org_id, pol_type_enum, "{}".to_string()), None => OrgPolicy::new(org_id, pol_type_enum, "{}".to_string()),
}; };
@ -1331,15 +1333,16 @@ async fn put_policy(
let pol_type_enum = match OrgPolicyType::from_i32(pol_type) { let pol_type_enum = match OrgPolicyType::from_i32(pol_type) {
Some(pt) => pt, Some(pt) => pt,
None => err!("Invalid policy type"), None => err!("Invalid or unsupported policy type"),
}; };
// If enabling the TwoFactorAuthentication policy, remove this org's members that do have 2FA // When enabling the TwoFactorAuthentication policy, remove this org's members that do have 2FA
if pol_type_enum == OrgPolicyType::TwoFactorAuthentication && data.enabled { if pol_type_enum == OrgPolicyType::TwoFactorAuthentication && data.enabled {
for member in UserOrganization::find_by_org(&org_id, &conn).await.into_iter() { for member in UserOrganization::find_by_org(&org_id, &conn).await.into_iter() {
let user_twofactor_disabled = TwoFactor::find_by_user(&member.user_uuid, &conn).await.is_empty(); let user_twofactor_disabled = TwoFactor::find_by_user(&member.user_uuid, &conn).await.is_empty();
// Policy only applies to non-Owner/non-Admin members who have accepted joining the org // Policy only applies to non-Owner/non-Admin members who have accepted joining the org
// Invited users still need to accept the invite and will get an error when they try to accept the invite.
if user_twofactor_disabled if user_twofactor_disabled
&& member.atype < UserOrgType::Admin && member.atype < UserOrgType::Admin
&& member.status != UserOrgStatus::Invited as i32 && member.status != UserOrgStatus::Invited as i32
@ -1355,33 +1358,29 @@ async fn put_policy(
} }
} }
// If enabling the SingleOrg policy, remove this org's members that are members of other orgs // When enabling the SingleOrg policy, remove this org's members that are members of other orgs
if pol_type_enum == OrgPolicyType::SingleOrg && data.enabled { if pol_type_enum == OrgPolicyType::SingleOrg && data.enabled {
for member in UserOrganization::find_by_org(&org_id, &conn).await.into_iter() { for member in UserOrganization::find_by_org(&org_id, &conn).await.into_iter() {
// Policy only applies to non-Owner/non-Admin members who have accepted joining the org // Policy only applies to non-Owner/non-Admin members who have accepted joining the org
if member.atype < UserOrgType::Admin && member.status != UserOrgStatus::Invited as i32 { // Exclude invited and revoked users when checking for this policy.
let is_member_of_another_org = UserOrganization::find_any_state_by_user(&member.user_uuid, &conn) // Those users will not be allowed to accept or be activated because of the policy checks done there.
.await // We check if the count is larger then 1, because it includes this organization also.
.into_iter() if member.atype < UserOrgType::Admin
// Other UserOrganization's where they have accepted being a member of && member.status != UserOrgStatus::Invited as i32
.filter(|uo| uo.uuid != member.uuid && uo.status != UserOrgStatus::Invited as i32) && UserOrganization::count_accepted_and_confirmed_by_user(&member.user_uuid, &conn).await > 1
.count() {
> 1; if CONFIG.mail_enabled() {
let org = Organization::find_by_uuid(&member.org_uuid, &conn).await.unwrap();
if is_member_of_another_org { let user = User::find_by_uuid(&member.user_uuid, &conn).await.unwrap();
if CONFIG.mail_enabled() {
let org = Organization::find_by_uuid(&member.org_uuid, &conn).await.unwrap(); mail::send_single_org_removed_from_org(&user.email, &org.name).await?;
let user = User::find_by_uuid(&member.user_uuid, &conn).await.unwrap();
mail::send_single_org_removed_from_org(&user.email, &org.name).await?;
}
member.delete(&conn).await?;
} }
member.delete(&conn).await?;
} }
} }
} }
let mut policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type, &conn).await { let mut policy = match OrgPolicy::find_by_org_and_type(&org_id, pol_type_enum, &conn).await {
Some(p) => p, Some(p) => p,
None => OrgPolicy::new(org_id, pol_type_enum, "{}".to_string()), None => OrgPolicy::new(org_id, pol_type_enum, "{}".to_string()),
}; };
@ -1404,7 +1403,7 @@ fn get_organization_tax(org_id: String, _headers: Headers) -> Json<Value> {
} }
#[get("/plans")] #[get("/plans")]
fn get_plans(_headers: Headers) -> Json<Value> { fn get_plans() -> Json<Value> {
// Respond with a minimal json just enough to allow the creation of an new organization. // Respond with a minimal json just enough to allow the creation of an new organization.
Json(json!({ Json(json!({
"Object": "list", "Object": "list",
@ -1521,7 +1520,7 @@ async fn import(org_id: String, data: JsonUpcase<OrgImportData>, headers: Header
// If this flag is enabled, any user that isn't provided in the Users list will be removed (by default they will be kept unless they have Deleted == true) // If this flag is enabled, any user that isn't provided in the Users list will be removed (by default they will be kept unless they have Deleted == true)
if data.OverwriteExisting { if data.OverwriteExisting {
for user_org in UserOrganization::find_by_org_and_type(&org_id, UserOrgType::User as i32, &conn).await { for user_org in UserOrganization::find_by_org_and_type(&org_id, UserOrgType::User, &conn).await {
if let Some(user_email) = User::find_by_uuid(&user_org.user_uuid, &conn).await.map(|u| u.email) { if let Some(user_email) = User::find_by_uuid(&user_org.user_uuid, &conn).await.map(|u| u.email) {
if !data.Users.iter().any(|u| u.Email == user_email) { if !data.Users.iter().any(|u| u.Email == user_email) {
user_org.delete(&conn).await?; user_org.delete(&conn).await?;
@ -1533,6 +1532,212 @@ async fn import(org_id: String, data: JsonUpcase<OrgImportData>, headers: Header
Ok(()) Ok(())
} }
// Pre web-vault v2022.9.x endpoint
#[put("/organizations/<org_id>/users/<org_user_id>/deactivate")]
async fn deactivate_organization_user(
org_id: String,
org_user_id: String,
headers: AdminHeaders,
conn: DbConn,
) -> EmptyResult {
_revoke_organization_user(&org_id, &org_user_id, &headers, &conn).await
}
// Pre web-vault v2022.9.x endpoint
#[put("/organizations/<org_id>/users/deactivate", data = "<data>")]
async fn bulk_deactivate_organization_user(
org_id: String,
data: JsonUpcase<Value>,
headers: AdminHeaders,
conn: DbConn,
) -> Json<Value> {
bulk_revoke_organization_user(org_id, data, headers, conn).await
}
#[put("/organizations/<org_id>/users/<org_user_id>/revoke")]
async fn revoke_organization_user(
org_id: String,
org_user_id: String,
headers: AdminHeaders,
conn: DbConn,
) -> EmptyResult {
_revoke_organization_user(&org_id, &org_user_id, &headers, &conn).await
}
#[put("/organizations/<org_id>/users/revoke", data = "<data>")]
async fn bulk_revoke_organization_user(
org_id: String,
data: JsonUpcase<Value>,
headers: AdminHeaders,
conn: DbConn,
) -> Json<Value> {
let data = data.into_inner().data;
let mut bulk_response = Vec::new();
match data["Ids"].as_array() {
Some(org_users) => {
for org_user_id in org_users {
let org_user_id = org_user_id.as_str().unwrap_or_default();
let err_msg = match _revoke_organization_user(&org_id, org_user_id, &headers, &conn).await {
Ok(_) => String::from(""),
Err(e) => format!("{:?}", e),
};
bulk_response.push(json!(
{
"Object": "OrganizationUserBulkResponseModel",
"Id": org_user_id,
"Error": err_msg
}
));
}
}
None => error!("No users to revoke"),
}
Json(json!({
"Data": bulk_response,
"Object": "list",
"ContinuationToken": null
}))
}
async fn _revoke_organization_user(
org_id: &str,
org_user_id: &str,
headers: &AdminHeaders,
conn: &DbConn,
) -> EmptyResult {
match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn).await {
Some(mut user_org) if user_org.status > UserOrgStatus::Revoked as i32 => {
if user_org.user_uuid == headers.user.uuid {
err!("You cannot revoke yourself")
}
if user_org.atype == UserOrgType::Owner && headers.org_user_type != UserOrgType::Owner {
err!("Only owners can revoke other owners")
}
if user_org.atype == UserOrgType::Owner
&& UserOrganization::count_confirmed_by_org_and_type(org_id, UserOrgType::Owner, conn).await <= 1
{
err!("Organization must have at least one confirmed owner")
}
user_org.revoke();
user_org.save(conn).await?;
}
Some(_) => err!("User is already revoked"),
None => err!("User not found in organization"),
}
Ok(())
}
// Pre web-vault v2022.9.x endpoint
#[put("/organizations/<org_id>/users/<org_user_id>/activate")]
async fn activate_organization_user(
org_id: String,
org_user_id: String,
headers: AdminHeaders,
conn: DbConn,
) -> EmptyResult {
_restore_organization_user(&org_id, &org_user_id, &headers, &conn).await
}
// Pre web-vault v2022.9.x endpoint
#[put("/organizations/<org_id>/users/activate", data = "<data>")]
async fn bulk_activate_organization_user(
org_id: String,
data: JsonUpcase<Value>,
headers: AdminHeaders,
conn: DbConn,
) -> Json<Value> {
bulk_restore_organization_user(org_id, data, headers, conn).await
}
#[put("/organizations/<org_id>/users/<org_user_id>/restore")]
async fn restore_organization_user(
org_id: String,
org_user_id: String,
headers: AdminHeaders,
conn: DbConn,
) -> EmptyResult {
_restore_organization_user(&org_id, &org_user_id, &headers, &conn).await
}
#[put("/organizations/<org_id>/users/restore", data = "<data>")]
async fn bulk_restore_organization_user(
org_id: String,
data: JsonUpcase<Value>,
headers: AdminHeaders,
conn: DbConn,
) -> Json<Value> {
let data = data.into_inner().data;
let mut bulk_response = Vec::new();
match data["Ids"].as_array() {
Some(org_users) => {
for org_user_id in org_users {
let org_user_id = org_user_id.as_str().unwrap_or_default();
let err_msg = match _restore_organization_user(&org_id, org_user_id, &headers, &conn).await {
Ok(_) => String::from(""),
Err(e) => format!("{:?}", e),
};
bulk_response.push(json!(
{
"Object": "OrganizationUserBulkResponseModel",
"Id": org_user_id,
"Error": err_msg
}
));
}
}
None => error!("No users to restore"),
}
Json(json!({
"Data": bulk_response,
"Object": "list",
"ContinuationToken": null
}))
}
async fn _restore_organization_user(
org_id: &str,
org_user_id: &str,
headers: &AdminHeaders,
conn: &DbConn,
) -> EmptyResult {
match UserOrganization::find_by_uuid_and_org(org_user_id, org_id, conn).await {
Some(mut user_org) if user_org.status < UserOrgStatus::Accepted as i32 => {
if user_org.user_uuid == headers.user.uuid {
err!("You cannot restore yourself")
}
if user_org.atype == UserOrgType::Owner && headers.org_user_type != UserOrgType::Owner {
err!("Only owners can restore other owners")
}
// This check is also done at accept_invite(), _confirm_invite, _activate_user(), edit_user(), admin::update_user_org_type
// It returns different error messages per function.
if user_org.atype < UserOrgType::Admin {
match OrgPolicy::is_user_allowed(&user_org.user_uuid, org_id, false, conn).await {
Ok(_) => {}
Err(OrgPolicyErr::TwoFactorMissing) => {
err!("You cannot restore this user because it has no two-step login method activated");
}
Err(OrgPolicyErr::SingleOrgEnforced) => {
err!("You cannot restore this user because it is a member of an organization which forbids it");
}
}
}
user_org.restore();
user_org.save(conn).await?;
}
Some(_) => err!("User is already active"),
None => err!("User not found in organization"),
}
Ok(())
#[get("/organizations/<org_id>/groups")] #[get("/organizations/<org_id>/groups")]
async fn get_groups(org_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult { async fn get_groups(org_id: String, _headers: AdminHeaders, conn: DbConn) -> JsonResult {
let groups = Group::find_by_organization(&org_id, &conn).await.iter().map(Group::to_json).collect::<Value>(); let groups = Group::find_by_organization(&org_id, &conn).await.iter().map(Group::to_json).collect::<Value>();

5
src/api/core/sends.rs

@ -70,8 +70,9 @@ struct SendData {
/// controls this policy globally. /// controls this policy globally.
async fn enforce_disable_send_policy(headers: &Headers, conn: &DbConn) -> EmptyResult { async fn enforce_disable_send_policy(headers: &Headers, conn: &DbConn) -> EmptyResult {
let user_uuid = &headers.user.uuid; let user_uuid = &headers.user.uuid;
let policy_type = OrgPolicyType::DisableSend; if !CONFIG.sends_allowed()
if !CONFIG.sends_allowed() || OrgPolicy::is_applicable_to_user(user_uuid, policy_type, conn).await { || OrgPolicy::is_applicable_to_user(user_uuid, OrgPolicyType::DisableSend, None, conn).await
{
err!("Due to an Enterprise Policy, you are only able to delete an existing Send.") err!("Due to an Enterprise Policy, you are only able to delete an existing Send.")
} }
Ok(()) Ok(())

27
src/api/core/two_factor/mod.rs

@ -19,7 +19,14 @@ pub mod webauthn;
pub mod yubikey; pub mod yubikey;
pub fn routes() -> Vec<Route> { pub fn routes() -> Vec<Route> {
let mut routes = routes![get_twofactor, get_recover, recover, disable_twofactor, disable_twofactor_put,]; let mut routes = routes![
get_twofactor,
get_recover,
recover,
disable_twofactor,
disable_twofactor_put,
get_device_verification_settings,
];
routes.append(&mut authenticator::routes()); routes.append(&mut authenticator::routes());
routes.append(&mut duo::routes()); routes.append(&mut duo::routes());
@ -188,3 +195,21 @@ pub async fn send_incomplete_2fa_notifications(pool: DbPool) {
login.delete(&conn).await.expect("Error deleting incomplete 2FA record"); login.delete(&conn).await.expect("Error deleting incomplete 2FA record");
} }
} }
// This function currently is just a dummy and the actual part is not implemented yet.
// This also prevents 404 errors.
//
// See the following Bitwarden PR's regarding this feature.
// https://github.com/bitwarden/clients/pull/2843
// https://github.com/bitwarden/clients/pull/2839
// https://github.com/bitwarden/server/pull/2016
//
// The HTML part is hidden via the CSS patches done via the bw_web_build repo
#[get("/two-factor/get-device-verification-settings")]
fn get_device_verification_settings(_headers: Headers, _conn: DbConn) -> Json<Value> {
Json(json!({
"isDeviceVerificationSectionEnabled":false,
"unknownDeviceVerificationEnabled":false,
"object":"deviceVerificationSettings"
}))
}

4
src/api/web.rs

@ -88,8 +88,8 @@ fn static_files(filename: String) -> Result<(ContentType, &'static [u8]), Error>
"identicon.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/identicon.js"))), "identicon.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/identicon.js"))),
"datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))), "datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))),
"datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))), "datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))),
"jquery-3.6.0.slim.js" => { "jquery-3.6.1.slim.js" => {
Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.6.0.slim.js"))) Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.6.1.slim.js")))
} }
_ => err!(format!("Static file not found: {}", filename)), _ => err!(format!("Static file not found: {}", filename)),
} }

1
src/db/models/mod.rs

@ -20,6 +20,7 @@ pub use self::device::Device;
pub use self::emergency_access::{EmergencyAccess, EmergencyAccessStatus, EmergencyAccessType}; pub use self::emergency_access::{EmergencyAccess, EmergencyAccessStatus, EmergencyAccessType};
pub use self::favorite::Favorite; pub use self::favorite::Favorite;
pub use self::folder::{Folder, FolderCipher}; pub use self::folder::{Folder, FolderCipher};
pub use self::org_policy::{OrgPolicy, OrgPolicyErr, OrgPolicyType};
pub use self::group::{CollectionGroup, Group, GroupUser}; pub use self::group::{CollectionGroup, Group, GroupUser};
pub use self::org_policy::{OrgPolicy, OrgPolicyType}; pub use self::org_policy::{OrgPolicy, OrgPolicyType};
pub use self::organization::{Organization, UserOrgStatus, UserOrgType, UserOrganization}; pub use self::organization::{Organization, UserOrgStatus, UserOrgType, UserOrganization};

150
src/db/models/org_policy.rs

@ -6,7 +6,7 @@ use crate::db::DbConn;
use crate::error::MapResult; use crate::error::MapResult;
use crate::util::UpCase; use crate::util::UpCase;
use super::{UserOrgStatus, UserOrgType, UserOrganization}; use super::{TwoFactor, UserOrgStatus, UserOrgType, UserOrganization};
db_object! { db_object! {
#[derive(Identifiable, Queryable, Insertable, AsChangeset)] #[derive(Identifiable, Queryable, Insertable, AsChangeset)]
@ -21,25 +21,37 @@ db_object! {
} }
} }
// https://github.com/bitwarden/server/blob/b86a04cef9f1e1b82cf18e49fc94e017c641130c/src/Core/Enums/PolicyType.cs
#[derive(Copy, Clone, Eq, PartialEq, num_derive::FromPrimitive)] #[derive(Copy, Clone, Eq, PartialEq, num_derive::FromPrimitive)]
pub enum OrgPolicyType { pub enum OrgPolicyType {
TwoFactorAuthentication = 0, TwoFactorAuthentication = 0,
MasterPassword = 1, MasterPassword = 1,
PasswordGenerator = 2, PasswordGenerator = 2,
SingleOrg = 3, SingleOrg = 3,
// RequireSso = 4, // Not currently supported. // RequireSso = 4, // Not supported
PersonalOwnership = 5, PersonalOwnership = 5,
DisableSend = 6, DisableSend = 6,
SendOptions = 7, SendOptions = 7,
// ResetPassword = 8, // Not supported
// MaximumVaultTimeout = 9, // Not supported (Not AGPLv3 Licensed)
// DisablePersonalVaultExport = 10, // Not supported (Not AGPLv3 Licensed)
} }
// https://github.com/bitwarden/server/blob/master/src/Core/Models/Data/SendOptionsPolicyData.cs // https://github.com/bitwarden/server/blob/5cbdee137921a19b1f722920f0fa3cd45af2ef0f/src/Core/Models/Data/Organizations/Policies/SendOptionsPolicyData.cs
#[derive(Deserialize)] #[derive(Deserialize)]
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub struct SendOptionsPolicyData { pub struct SendOptionsPolicyData {
pub DisableHideEmail: bool, pub DisableHideEmail: bool,
} }
pub type OrgPolicyResult = Result<(), OrgPolicyErr>;
#[derive(Debug)]
pub enum OrgPolicyErr {
TwoFactorMissing,
SingleOrgEnforced,
}
/// Local methods /// Local methods
impl OrgPolicy { impl OrgPolicy {
pub fn new(org_uuid: String, atype: OrgPolicyType, data: String) -> Self { pub fn new(org_uuid: String, atype: OrgPolicyType, data: String) -> Self {
@ -160,11 +172,11 @@ impl OrgPolicy {
}} }}
} }
pub async fn find_by_org_and_type(org_uuid: &str, atype: i32, conn: &DbConn) -> Option<Self> { pub async fn find_by_org_and_type(org_uuid: &str, policy_type: OrgPolicyType, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
org_policies::table org_policies::table
.filter(org_policies::org_uuid.eq(org_uuid)) .filter(org_policies::org_uuid.eq(org_uuid))
.filter(org_policies::atype.eq(atype)) .filter(org_policies::atype.eq(policy_type as i32))
.first::<OrgPolicyDb>(conn) .first::<OrgPolicyDb>(conn)
.ok() .ok()
.from_db() .from_db()
@ -179,40 +191,128 @@ impl OrgPolicy {
}} }}
} }
pub async fn find_accepted_and_confirmed_by_user_and_active_policy(
user_uuid: &str,
policy_type: OrgPolicyType,
conn: &DbConn,
) -> Vec<Self> {
db_run! { conn: {
org_policies::table
.inner_join(
users_organizations::table.on(
users_organizations::org_uuid.eq(org_policies::org_uuid)
.and(users_organizations::user_uuid.eq(user_uuid)))
)
.filter(
users_organizations::status.eq(UserOrgStatus::Accepted as i32)
)
.or_filter(
users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
)
.filter(org_policies::atype.eq(policy_type as i32))
.filter(org_policies::enabled.eq(true))
.select(org_policies::all_columns)
.load::<OrgPolicyDb>(conn)
.expect("Error loading org_policy")
.from_db()
}}
}
pub async fn find_confirmed_by_user_and_active_policy(
user_uuid: &str,
policy_type: OrgPolicyType,
conn: &DbConn,
) -> Vec<Self> {
db_run! { conn: {
org_policies::table
.inner_join(
users_organizations::table.on(
users_organizations::org_uuid.eq(org_policies::org_uuid)
.and(users_organizations::user_uuid.eq(user_uuid)))
)
.filter(
users_organizations::status.eq(UserOrgStatus::Confirmed as i32)
)
.filter(org_policies::atype.eq(policy_type as i32))
.filter(org_policies::enabled.eq(true))
.select(org_policies::all_columns)
.load::<OrgPolicyDb>(conn)
.expect("Error loading org_policy")
.from_db()
}}
}
/// Returns true if the user belongs to an org that has enabled the specified policy type, /// Returns true if the user belongs to an org that has enabled the specified policy type,
/// and the user is not an owner or admin of that org. This is only useful for checking /// and the user is not an owner or admin of that org. This is only useful for checking
/// applicability of policy types that have these particular semantics. /// applicability of policy types that have these particular semantics.
pub async fn is_applicable_to_user(user_uuid: &str, policy_type: OrgPolicyType, conn: &DbConn) -> bool { pub async fn is_applicable_to_user(
// TODO: Should check confirmed and accepted users user_uuid: &str,
for policy in OrgPolicy::find_confirmed_by_user(user_uuid, conn).await { policy_type: OrgPolicyType,
if policy.enabled && policy.has_type(policy_type) { exclude_org_uuid: Option<&str>,
let org_uuid = &policy.org_uuid; conn: &DbConn,
if let Some(user) = UserOrganization::find_by_user_and_org(user_uuid, org_uuid, conn).await { ) -> bool {
if user.atype < UserOrgType::Admin { for policy in
return true; OrgPolicy::find_accepted_and_confirmed_by_user_and_active_policy(user_uuid, policy_type, conn).await
} {
// Check if we need to skip this organization.
if exclude_org_uuid.is_some() && exclude_org_uuid.unwrap() == policy.org_uuid {
continue;
}
if let Some(user) = UserOrganization::find_by_user_and_org(user_uuid, &policy.org_uuid, conn).await {
if user.atype < UserOrgType::Admin {
return true;
} }
} }
} }
false false
} }
pub async fn is_user_allowed(
user_uuid: &str,
org_uuid: &str,
exclude_current_org: bool,
conn: &DbConn,
) -> OrgPolicyResult {
// Enforce TwoFactor/TwoStep login
if TwoFactor::find_by_user(user_uuid, conn).await.is_empty() {
match Self::find_by_org_and_type(org_uuid, OrgPolicyType::TwoFactorAuthentication, conn).await {
Some(p) if p.enabled => {
return Err(OrgPolicyErr::TwoFactorMissing);
}
_ => {}
};
}
// Enforce Single Organization Policy of other organizations user is a member of
// This check here needs to exclude this current org-id, else an accepted user can not be confirmed.
let exclude_org = if exclude_current_org {
Some(org_uuid)
} else {
None
};
if Self::is_applicable_to_user(user_uuid, OrgPolicyType::SingleOrg, exclude_org, conn).await {
return Err(OrgPolicyErr::SingleOrgEnforced);
}
Ok(())
}
/// Returns true if the user belongs to an org that has enabled the `DisableHideEmail` /// Returns true if the user belongs to an org that has enabled the `DisableHideEmail`
/// option of the `Send Options` policy, and the user is not an owner or admin of that org. /// option of the `Send Options` policy, and the user is not an owner or admin of that org.
pub async fn is_hide_email_disabled(user_uuid: &str, conn: &DbConn) -> bool { pub async fn is_hide_email_disabled(user_uuid: &str, conn: &DbConn) -> bool {
for policy in OrgPolicy::find_confirmed_by_user(user_uuid, conn).await { for policy in
if policy.enabled && policy.has_type(OrgPolicyType::SendOptions) { OrgPolicy::find_confirmed_by_user_and_active_policy(user_uuid, OrgPolicyType::SendOptions, conn).await
let org_uuid = &policy.org_uuid; {
if let Some(user) = UserOrganization::find_by_user_and_org(user_uuid, org_uuid, conn).await { if let Some(user) = UserOrganization::find_by_user_and_org(user_uuid, &policy.org_uuid, conn).await {
if user.atype < UserOrgType::Admin { if user.atype < UserOrgType::Admin {
match serde_json::from_str::<UpCase<SendOptionsPolicyData>>(&policy.data) { match serde_json::from_str::<UpCase<SendOptionsPolicyData>>(&policy.data) {
Ok(opts) => { Ok(opts) => {
if opts.data.DisableHideEmail { if opts.data.DisableHideEmail {
return true; return true;
}
} }
_ => error!("Failed to deserialize policy data: {}", policy.data),
} }
_ => error!("Failed to deserialize SendOptionsPolicyData: {}", policy.data),
} }
} }
} }

118
src/db/models/organization.rs

@ -31,7 +31,9 @@ db_object! {
} }
} }
// https://github.com/bitwarden/server/blob/b86a04cef9f1e1b82cf18e49fc94e017c641130c/src/Core/Enums/OrganizationUserStatusType.cs
pub enum UserOrgStatus { pub enum UserOrgStatus {
Revoked = -1,
Invited = 0, Invited = 0,
Accepted = 1, Accepted = 1,
Confirmed = 2, Confirmed = 2,
@ -133,26 +135,29 @@ impl Organization {
public_key, public_key,
} }
} }
// https://github.com/bitwarden/server/blob/13d1e74d6960cf0d042620b72d85bf583a4236f7/src/Api/Models/Response/Organizations/OrganizationResponseModel.cs
pub fn to_json(&self) -> Value { pub fn to_json(&self) -> Value {
json!({ json!({
"Id": self.uuid, "Id": self.uuid,
"Identifier": null, // not supported by us "Identifier": null, // not supported by us
"Name": self.name, "Name": self.name,
"Seats": 10, // The value doesn't matter, we don't check server-side "Seats": 10, // The value doesn't matter, we don't check server-side
// "MaxAutoscaleSeats": null, // The value doesn't matter, we don't check server-side
"MaxCollections": 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
"MaxStorageGb": 10, // The value doesn't matter, we don't check server-side "MaxStorageGb": 10, // The value doesn't matter, we don't check server-side
"Use2fa": true, "Use2fa": true,
"UseDirectory": false, // Is supported, but this value isn't checked anywhere (yet) "UseDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
"UseEvents": false, // not supported by us "UseEvents": false, // Not supported
"UseGroups": true, "UseGroups": true,
"UseTotp": true, "UseTotp": true,
"UsePolicies": true, "UsePolicies": true,
"UseSso": false, // We do not support SSO // "UseScim": false, // Not supported (Not AGPLv3 Licensed)
"UseSso": false, // Not supported
// "UseKeyConnector": false, // Not supported
"SelfHost": true, "SelfHost": true,
"UseApi": false, // not supported by us "UseApi": false, // Not supported
"HasPublicAndPrivateKeys": self.private_key.is_some() && self.public_key.is_some(), "HasPublicAndPrivateKeys": self.private_key.is_some() && self.public_key.is_some(),
"ResetPasswordEnrolled": false, // not supported by us "UseResetPassword": false, // Not supported
"BusinessName": null, "BusinessName": null,
"BusinessAddress1": null, "BusinessAddress1": null,
@ -170,6 +175,12 @@ impl Organization {
} }
} }
// Used to either subtract or add to the current status
// The number 128 should be fine, it is well within the range of an i32
// The same goes for the database where we only use INTEGER (the same as an i32)
// It should also provide enough room for 100+ types, which i doubt will ever happen.
static ACTIVATE_REVOKE_DIFF: i32 = 128;
impl UserOrganization { impl UserOrganization {
pub fn new(user_uuid: String, org_uuid: String) -> Self { pub fn new(user_uuid: String, org_uuid: String) -> Self {
Self { Self {
@ -184,6 +195,18 @@ impl UserOrganization {
atype: UserOrgType::User as i32, atype: UserOrgType::User as i32,
} }
} }
pub fn restore(&mut self) {
if self.status < UserOrgStatus::Accepted as i32 {
self.status += ACTIVATE_REVOKE_DIFF;
}
}
pub fn revoke(&mut self) {
if self.status > UserOrgStatus::Revoked as i32 {
self.status -= ACTIVATE_REVOKE_DIFF;
}
}
} }
use crate::db::DbConn; use crate::db::DbConn;
@ -265,9 +288,10 @@ impl UserOrganization {
pub async fn to_json(&self, conn: &DbConn) -> Value { pub async fn to_json(&self, conn: &DbConn) -> Value {
let org = Organization::find_by_uuid(&self.org_uuid, conn).await.unwrap(); let org = Organization::find_by_uuid(&self.org_uuid, conn).await.unwrap();
// https://github.com/bitwarden/server/blob/13d1e74d6960cf0d042620b72d85bf583a4236f7/src/Api/Models/Response/ProfileOrganizationResponseModel.cs
json!({ json!({
"Id": self.org_uuid, "Id": self.org_uuid,
"Identifier": null, // not supported by us "Identifier": null, // Not supported
"Name": org.name, "Name": org.name,
"Seats": 10, // The value doesn't matter, we don't check server-side "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 "MaxCollections": 10, // The value doesn't matter, we don't check server-side
@ -275,44 +299,48 @@ impl UserOrganization {
"Use2fa": true, "Use2fa": true,
"UseDirectory": false, // Is supported, but this value isn't checked anywhere (yet) "UseDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
"UseEvents": false, // not supported by us "UseEvents": false, // Not supported
"UseGroups": true, "UseGroups": true,
"UseTotp": true, "UseTotp": true,
// "UseScim": false, // Not supported (Not AGPLv3 Licensed)
"UsePolicies": true, "UsePolicies": true,
"UseApi": false, // not supported by us "UseApi": false, // Not supported
"SelfHost": true, "SelfHost": true,
"HasPublicAndPrivateKeys": org.private_key.is_some() && org.public_key.is_some(), "HasPublicAndPrivateKeys": org.private_key.is_some() && org.public_key.is_some(),
"ResetPasswordEnrolled": false, // not supported by us "ResetPasswordEnrolled": false, // Not supported
"SsoBound": false, // We do not support SSO "SsoBound": false, // Not supported
"UseSso": false, // We do not support SSO "UseSso": false, // Not supported
// 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.
// https://github.com/bitwarden/server/tree/master/bitwarden_license/src/
"UseBusinessPortal": false, // Disable BusinessPortal Button
"ProviderId": null, "ProviderId": null,
"ProviderName": null, "ProviderName": null,
// "KeyConnectorEnabled": false,
// "KeyConnectorUrl": null,
// TODO: Add support for Custom User Roles // TODO: Add support for Custom User Roles
// See: https://bitwarden.com/help/article/user-types-access-control/#custom-role // See: https://bitwarden.com/help/article/user-types-access-control/#custom-role
// "Permissions": { // "Permissions": {
// "AccessBusinessPortal": false, // "AccessEventLogs": false, // Not supported
// "AccessEventLogs": false,
// "AccessImportExport": false, // "AccessImportExport": false,
// "AccessReports": false, // "AccessReports": false,
// "ManageAllCollections": false, // "ManageAllCollections": false,
// "CreateNewCollections": false,
// "EditAnyCollection": false,
// "DeleteAnyCollection": false,
// "ManageAssignedCollections": false, // "ManageAssignedCollections": false,
// "editAssignedCollections": false,
// "deleteAssignedCollections": false,
// "ManageCiphers": false, // "ManageCiphers": false,
// "ManageGroups": false, // "ManageGroups": false, // Not supported
// "ManagePolicies": false, // "ManagePolicies": false,
// "ManageResetPassword": false, // "ManageResetPassword": false, // Not supported
// "ManageSso": false, // "ManageSso": false, // Not supported
// "ManageUsers": false, // "ManageUsers": false,
// "ManageScim": false, // Not supported (Not AGPLv3 Licensed)
// }, // },
"MaxStorageGb": 10, // The value doesn't matter, we don't check server-side "MaxStorageGb": 10, // The value doesn't matter, we don't check server-side
// These are per user // These are per user
"UserId": self.user_uuid,
"Key": self.akey, "Key": self.akey,
"Status": self.status, "Status": self.status,
"Type": self.atype, "Type": self.atype,
@ -325,13 +353,21 @@ impl UserOrganization {
pub async fn to_json_user_details(&self, conn: &DbConn) -> Value { pub async fn to_json_user_details(&self, conn: &DbConn) -> Value {
let user = User::find_by_uuid(&self.user_uuid, conn).await.unwrap(); let user = User::find_by_uuid(&self.user_uuid, conn).await.unwrap();
// Because BitWarden want the status to be -1 for revoked users we need to catch that here.
// We subtract/add a number so we can restore/activate the user to it's previouse state again.
let status = if self.status < UserOrgStatus::Revoked as i32 {
UserOrgStatus::Revoked as i32
} else {
self.status
};
json!({ json!({
"Id": self.uuid, "Id": self.uuid,
"UserId": self.user_uuid, "UserId": self.user_uuid,
"Name": user.name, "Name": user.name,
"Email": user.email, "Email": user.email,
"Status": self.status, "Status": status,
"Type": self.atype, "Type": self.atype,
"AccessAll": self.access_all, "AccessAll": self.access_all,
@ -365,11 +401,19 @@ impl UserOrganization {
.collect() .collect()
}; };
// Because BitWarden want the status to be -1 for revoked users we need to catch that here.
// We subtract/add a number so we can restore/activate the user to it's previouse state again.
let status = if self.status < UserOrgStatus::Revoked as i32 {
UserOrgStatus::Revoked as i32
} else {
self.status
};
json!({ json!({
"Id": self.uuid, "Id": self.uuid,
"UserId": self.user_uuid, "UserId": self.user_uuid,
"Status": self.status, "Status": status,
"Type": self.atype, "Type": self.atype,
"AccessAll": self.access_all, "AccessAll": self.access_all,
"Collections": coll_uuids, "Collections": coll_uuids,
@ -508,6 +552,18 @@ impl UserOrganization {
}} }}
} }
pub async fn count_accepted_and_confirmed_by_user(user_uuid: &str, conn: &DbConn) -> i64 {
db_run! { conn: {
users_organizations::table
.filter(users_organizations::user_uuid.eq(user_uuid))
.filter(users_organizations::status.eq(UserOrgStatus::Accepted as i32))
.or_filter(users_organizations::status.eq(UserOrgStatus::Confirmed as i32))
.count()
.first::<i64>(conn)
.unwrap_or(0)
}}
}
pub async fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> { pub async fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
@ -528,16 +584,28 @@ impl UserOrganization {
}} }}
} }
pub async fn find_by_org_and_type(org_uuid: &str, atype: i32, conn: &DbConn) -> Vec<Self> { pub async fn find_by_org_and_type(org_uuid: &str, atype: UserOrgType, conn: &DbConn) -> Vec<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid)) .filter(users_organizations::org_uuid.eq(org_uuid))
.filter(users_organizations::atype.eq(atype)) .filter(users_organizations::atype.eq(atype as i32))
.load::<UserOrganizationDb>(conn) .load::<UserOrganizationDb>(conn)
.expect("Error loading user organizations").from_db() .expect("Error loading user organizations").from_db()
}} }}
} }
pub async fn count_confirmed_by_org_and_type(org_uuid: &str, atype: UserOrgType, conn: &DbConn) -> i64 {
db_run! { conn: {
users_organizations::table
.filter(users_organizations::org_uuid.eq(org_uuid))
.filter(users_organizations::atype.eq(atype as i32))
.filter(users_organizations::status.eq(UserOrgStatus::Confirmed as i32))
.count()
.first::<i64>(conn)
.unwrap_or(0)
}}
}
pub async fn find_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> { pub async fn find_by_user_and_org(user_uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
db_run! { conn: { db_run! { conn: {
users_organizations::table users_organizations::table

10
src/db/models/user.rs

@ -275,11 +275,11 @@ impl User {
pub async fn delete(self, conn: &DbConn) -> EmptyResult { pub async fn delete(self, conn: &DbConn) -> EmptyResult {
for user_org in UserOrganization::find_confirmed_by_user(&self.uuid, conn).await { for user_org in UserOrganization::find_confirmed_by_user(&self.uuid, conn).await {
if user_org.atype == UserOrgType::Owner { if user_org.atype == UserOrgType::Owner
let owner_type = UserOrgType::Owner as i32; && UserOrganization::count_confirmed_by_org_and_type(&user_org.org_uuid, UserOrgType::Owner, conn).await
if UserOrganization::find_by_org_and_type(&user_org.org_uuid, owner_type, conn).await.len() <= 1 { <= 1
err!("Can't delete last owner") {
} err!("Can't delete last owner")
} }
} }

1223
src/static/scripts/bootstrap.css

File diff suppressed because it is too large

190
src/static/scripts/jquery-3.6.0.slim.js → src/static/scripts/jquery-3.6.1.slim.js

@ -1,5 +1,5 @@
/*! /*!
* jQuery JavaScript Library v3.6.0 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector * jQuery JavaScript Library v3.6.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector
* https://jquery.com/ * https://jquery.com/
* *
* Includes Sizzle.js * Includes Sizzle.js
@ -9,7 +9,7 @@
* Released under the MIT license * Released under the MIT license
* https://jquery.org/license * https://jquery.org/license
* *
* Date: 2021-03-02T17:08Z * Date: 2022-08-26T17:52Z
*/ */
( function( global, factory ) { ( function( global, factory ) {
@ -23,7 +23,7 @@
// (such as Node.js), expose a factory as module.exports. // (such as Node.js), expose a factory as module.exports.
// This accentuates the need for the creation of a real `window`. // This accentuates the need for the creation of a real `window`.
// e.g. var jQuery = require("jquery")(window); // e.g. var jQuery = require("jquery")(window);
// See ticket #14549 for more info. // See ticket trac-14549 for more info.
module.exports = global.document ? module.exports = global.document ?
factory( global, true ) : factory( global, true ) :
function( w ) { function( w ) {
@ -151,7 +151,7 @@ function toType( obj ) {
var var
version = "3.6.0 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector", version = "3.6.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector",
// Define a local copy of jQuery // Define a local copy of jQuery
jQuery = function( selector, context ) { jQuery = function( selector, context ) {
@ -3129,8 +3129,8 @@ jQuery.fn.extend( {
var rootjQuery, var rootjQuery,
// A simple way to check for HTML strings // A simple way to check for HTML strings
// Prioritize #id over <tag> to avoid XSS via location.hash (#9521) // Prioritize #id over <tag> to avoid XSS via location.hash (trac-9521)
// Strict HTML recognition (#11290: must start with <) // Strict HTML recognition (trac-11290: must start with <)
// Shortcut simple #id case for speed // Shortcut simple #id case for speed
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
@ -4087,7 +4087,7 @@ jQuery.extend( {
isReady: false, isReady: false,
// A counter to track how many items to wait for before // A counter to track how many items to wait for before
// the ready event fires. See #6781 // the ready event fires. See trac-6781
readyWait: 1, readyWait: 1,
// Handle when the DOM is ready // Handle when the DOM is ready
@ -4215,7 +4215,7 @@ function fcamelCase( _all, letter ) {
// Convert dashed to camelCase; used by the css and data modules // Convert dashed to camelCase; used by the css and data modules
// Support: IE <=9 - 11, Edge 12 - 15 // Support: IE <=9 - 11, Edge 12 - 15
// Microsoft forgot to hump their vendor prefix (#9572) // Microsoft forgot to hump their vendor prefix (trac-9572)
function camelCase( string ) { function camelCase( string ) {
return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
} }
@ -4251,7 +4251,7 @@ Data.prototype = {
value = {}; value = {};
// We can accept data for non-element nodes in modern browsers, // We can accept data for non-element nodes in modern browsers,
// but we should not, see #8335. // but we should not, see trac-8335.
// Always return an empty object. // Always return an empty object.
if ( acceptData( owner ) ) { if ( acceptData( owner ) ) {
@ -4490,7 +4490,7 @@ jQuery.fn.extend( {
while ( i-- ) { while ( i-- ) {
// Support: IE 11 only // Support: IE 11 only
// The attrs elements can be null (#14894) // The attrs elements can be null (trac-14894)
if ( attrs[ i ] ) { if ( attrs[ i ] ) {
name = attrs[ i ].name; name = attrs[ i ].name;
if ( name.indexOf( "data-" ) === 0 ) { if ( name.indexOf( "data-" ) === 0 ) {
@ -4913,9 +4913,9 @@ var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );
input = document.createElement( "input" ); input = document.createElement( "input" );
// Support: Android 4.0 - 4.3 only // Support: Android 4.0 - 4.3 only
// Check state lost if the name is set (#11217) // Check state lost if the name is set (trac-11217)
// Support: Windows Web Apps (WWA) // Support: Windows Web Apps (WWA)
// `name` and `type` must use .setAttribute for WWA (#14901) // `name` and `type` must use .setAttribute for WWA (trac-14901)
input.setAttribute( "type", "radio" ); input.setAttribute( "type", "radio" );
input.setAttribute( "checked", "checked" ); input.setAttribute( "checked", "checked" );
input.setAttribute( "name", "t" ); input.setAttribute( "name", "t" );
@ -4939,7 +4939,7 @@ var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );
} )(); } )();
// We have to close these tags to support XHTML (#13200) // We have to close these tags to support XHTML (trac-13200)
var wrapMap = { var wrapMap = {
// XHTML parsers do not magically insert elements in the // XHTML parsers do not magically insert elements in the
@ -4965,7 +4965,7 @@ if ( !support.option ) {
function getAll( context, tag ) { function getAll( context, tag ) {
// Support: IE <=9 - 11 only // Support: IE <=9 - 11 only
// Use typeof to avoid zero-argument method invocation on host objects (#15151) // Use typeof to avoid zero-argument method invocation on host objects (trac-15151)
var ret; var ret;
if ( typeof context.getElementsByTagName !== "undefined" ) { if ( typeof context.getElementsByTagName !== "undefined" ) {
@ -5048,7 +5048,7 @@ function buildFragment( elems, context, scripts, selection, ignored ) {
// Remember the top-level container // Remember the top-level container
tmp = fragment.firstChild; tmp = fragment.firstChild;
// Ensure the created nodes are orphaned (#12392) // Ensure the created nodes are orphaned (trac-12392)
tmp.textContent = ""; tmp.textContent = "";
} }
} }
@ -5469,15 +5469,15 @@ jQuery.event = {
for ( ; cur !== this; cur = cur.parentNode || this ) { for ( ; cur !== this; cur = cur.parentNode || this ) {
// Don't check non-elements (#13208) // Don't check non-elements (trac-13208)
// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) // Don't process clicks on disabled elements (trac-6911, trac-8165, trac-11382, trac-11764)
if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
matchedHandlers = []; matchedHandlers = [];
matchedSelectors = {}; matchedSelectors = {};
for ( i = 0; i < delegateCount; i++ ) { for ( i = 0; i < delegateCount; i++ ) {
handleObj = handlers[ i ]; handleObj = handlers[ i ];
// Don't conflict with Object.prototype properties (#13203) // Don't conflict with Object.prototype properties (trac-13203)
sel = handleObj.selector + " "; sel = handleObj.selector + " ";
if ( matchedSelectors[ sel ] === undefined ) { if ( matchedSelectors[ sel ] === undefined ) {
@ -5731,7 +5731,7 @@ jQuery.Event = function( src, props ) {
// Create target properties // Create target properties
// Support: Safari <=6 - 7 only // Support: Safari <=6 - 7 only
// Target should not be a text node (#504, #13143) // Target should not be a text node (trac-504, trac-13143)
this.target = ( src.target && src.target.nodeType === 3 ) ? this.target = ( src.target && src.target.nodeType === 3 ) ?
src.target.parentNode : src.target.parentNode :
src.target; src.target;
@ -5854,10 +5854,10 @@ jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateTyp
return true; return true;
}, },
// Suppress native focus or blur as it's already being fired // Suppress native focus or blur if we're currently inside
// in leverageNative. // a leveraged native-event stack
_default: function() { _default: function( event ) {
return true; return dataPriv.get( event.target, type );
}, },
delegateType: delegateType delegateType: delegateType
@ -5956,7 +5956,8 @@ var
// checked="checked" or checked // checked="checked" or checked
rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;
rcleanScript = /^\s*<!\[CDATA\[|\]\]>\s*$/g;
// Prefer a tbody over its parent table for containing new rows // Prefer a tbody over its parent table for containing new rows
function manipulationTarget( elem, content ) { function manipulationTarget( elem, content ) {
@ -6070,7 +6071,7 @@ function domManip( collection, args, callback, ignored ) {
// Use the original fragment for the last item // Use the original fragment for the last item
// instead of the first because it can end up // instead of the first because it can end up
// being emptied incorrectly in certain situations (#8070). // being emptied incorrectly in certain situations (trac-8070).
for ( ; i < l; i++ ) { for ( ; i < l; i++ ) {
node = fragment; node = fragment;
@ -6111,6 +6112,12 @@ function domManip( collection, args, callback, ignored ) {
}, doc ); }, doc );
} }
} else { } else {
// Unwrap a CDATA section containing script contents. This shouldn't be
// needed as in XML documents they're already not visible when
// inspecting element contents and in HTML documents they have no
// meaning but we're preserving that logic for backwards compatibility.
// This will be removed completely in 4.0. See gh-4904.
DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc );
} }
} }
@ -6393,9 +6400,12 @@ jQuery.each( {
} ); } );
var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
var rcustomProp = /^--/;
var getStyles = function( elem ) { var getStyles = function( elem ) {
// Support: IE <=11 only, Firefox <=30 (#15098, #14150) // Support: IE <=11 only, Firefox <=30 (trac-15098, trac-14150)
// IE throws on elements created in popups // IE throws on elements created in popups
// FF meanwhile throws on frame elements through "defaultView.getComputedStyle" // FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
var view = elem.ownerDocument.defaultView; var view = elem.ownerDocument.defaultView;
@ -6430,6 +6440,15 @@ var swap = function( elem, options, callback ) {
var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
var whitespace = "[\\x20\\t\\r\\n\\f]";
var rtrimCSS = new RegExp(
"^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$",
"g"
);
( function() { ( function() {
@ -6495,7 +6514,7 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
} }
// Support: IE <=9 - 11 only // Support: IE <=9 - 11 only
// Style of cloned element affects source element cloned (#8908) // Style of cloned element affects source element cloned (trac-8908)
div.style.backgroundClip = "content-box"; div.style.backgroundClip = "content-box";
div.cloneNode( true ).style.backgroundClip = ""; div.cloneNode( true ).style.backgroundClip = "";
support.clearCloneStyle = div.style.backgroundClip === "content-box"; support.clearCloneStyle = div.style.backgroundClip === "content-box";
@ -6575,6 +6594,7 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
function curCSS( elem, name, computed ) { function curCSS( elem, name, computed ) {
var width, minWidth, maxWidth, ret, var width, minWidth, maxWidth, ret,
isCustomProp = rcustomProp.test( name ),
// Support: Firefox 51+ // Support: Firefox 51+
// Retrieving style before computed somehow // Retrieving style before computed somehow
@ -6585,11 +6605,22 @@ function curCSS( elem, name, computed ) {
computed = computed || getStyles( elem ); computed = computed || getStyles( elem );
// getPropertyValue is needed for: // getPropertyValue is needed for:
// .css('filter') (IE 9 only, #12537) // .css('filter') (IE 9 only, trac-12537)
// .css('--customProperty) (#3144) // .css('--customProperty) (gh-3144)
if ( computed ) { if ( computed ) {
ret = computed.getPropertyValue( name ) || computed[ name ]; ret = computed.getPropertyValue( name ) || computed[ name ];
// trim whitespace for custom property (issue gh-4926)
if ( isCustomProp ) {
// rtrim treats U+000D CARRIAGE RETURN and U+000C FORM FEED
// as whitespace while CSS does not, but this is not a problem
// because CSS preprocessing replaces them with U+000A LINE FEED
// (which *is* CSS whitespace)
// https://www.w3.org/TR/css-syntax-3/#input-preprocessing
ret = ret.replace( rtrimCSS, "$1" );
}
if ( ret === "" && !isAttached( elem ) ) { if ( ret === "" && !isAttached( elem ) ) {
ret = jQuery.style( elem, name ); ret = jQuery.style( elem, name );
} }
@ -6685,7 +6716,6 @@ var
// except "table", "table-cell", or "table-caption" // except "table", "table-cell", or "table-caption"
// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
rdisplayswap = /^(none|table(?!-c[ea]).+)/, rdisplayswap = /^(none|table(?!-c[ea]).+)/,
rcustomProp = /^--/,
cssShow = { position: "absolute", visibility: "hidden", display: "block" }, cssShow = { position: "absolute", visibility: "hidden", display: "block" },
cssNormalTransform = { cssNormalTransform = {
letterSpacing: "0", letterSpacing: "0",
@ -6921,15 +6951,15 @@ jQuery.extend( {
if ( value !== undefined ) { if ( value !== undefined ) {
type = typeof value; type = typeof value;
// Convert "+=" or "-=" to relative numbers (#7345) // Convert "+=" or "-=" to relative numbers (trac-7345)
if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
value = adjustCSS( elem, name, ret ); value = adjustCSS( elem, name, ret );
// Fixes bug #9237 // Fixes bug trac-9237
type = "number"; type = "number";
} }
// Make sure that null and NaN values aren't set (#7116) // Make sure that null and NaN values aren't set (trac-7116)
if ( value == null || value !== value ) { if ( value == null || value !== value ) {
return; return;
} }
@ -7149,7 +7179,6 @@ jQuery.fn.extend( {
// Based off of the plugin by Clint Helfers, with permission. // Based off of the plugin by Clint Helfers, with permission.
// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/
jQuery.fn.delay = function( time, type ) { jQuery.fn.delay = function( time, type ) {
time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
type = type || "fx"; type = type || "fx";
@ -7374,8 +7403,7 @@ jQuery.extend( {
// Support: IE <=9 - 11 only // Support: IE <=9 - 11 only
// elem.tabIndex doesn't always return the // elem.tabIndex doesn't always return the
// correct value when it hasn't been explicitly set // correct value when it hasn't been explicitly set
// https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ // Use proper attribute retrieval (trac-12072)
// Use proper attribute retrieval(#12072)
var tabindex = jQuery.find.attr( elem, "tabindex" ); var tabindex = jQuery.find.attr( elem, "tabindex" );
if ( tabindex ) { if ( tabindex ) {
@ -7479,8 +7507,7 @@ function classesToArray( value ) {
jQuery.fn.extend( { jQuery.fn.extend( {
addClass: function( value ) { addClass: function( value ) {
var classes, elem, cur, curValue, clazz, j, finalValue, var classNames, cur, curValue, className, i, finalValue;
i = 0;
if ( isFunction( value ) ) { if ( isFunction( value ) ) {
return this.each( function( j ) { return this.each( function( j ) {
@ -7488,36 +7515,35 @@ jQuery.fn.extend( {
} ); } );
} }
classes = classesToArray( value ); classNames = classesToArray( value );
if ( classes.length ) { if ( classNames.length ) {
while ( ( elem = this[ i++ ] ) ) { return this.each( function() {
curValue = getClass( elem ); curValue = getClass( this );
cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
if ( cur ) { if ( cur ) {
j = 0; for ( i = 0; i < classNames.length; i++ ) {
while ( ( clazz = classes[ j++ ] ) ) { className = classNames[ i ];
if ( cur.indexOf( " " + clazz + " " ) < 0 ) { if ( cur.indexOf( " " + className + " " ) < 0 ) {
cur += clazz + " "; cur += className + " ";
} }
} }
// Only assign if different to avoid unneeded rendering. // Only assign if different to avoid unneeded rendering.
finalValue = stripAndCollapse( cur ); finalValue = stripAndCollapse( cur );
if ( curValue !== finalValue ) { if ( curValue !== finalValue ) {
elem.setAttribute( "class", finalValue ); this.setAttribute( "class", finalValue );
} }
} }
} } );
} }
return this; return this;
}, },
removeClass: function( value ) { removeClass: function( value ) {
var classes, elem, cur, curValue, clazz, j, finalValue, var classNames, cur, curValue, className, i, finalValue;
i = 0;
if ( isFunction( value ) ) { if ( isFunction( value ) ) {
return this.each( function( j ) { return this.each( function( j ) {
@ -7529,45 +7555,42 @@ jQuery.fn.extend( {
return this.attr( "class", "" ); return this.attr( "class", "" );
} }
classes = classesToArray( value ); classNames = classesToArray( value );
if ( classes.length ) { if ( classNames.length ) {
while ( ( elem = this[ i++ ] ) ) { return this.each( function() {
curValue = getClass( elem ); curValue = getClass( this );
// This expression is here for better compressibility (see addClass) // This expression is here for better compressibility (see addClass)
cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
if ( cur ) { if ( cur ) {
j = 0; for ( i = 0; i < classNames.length; i++ ) {
while ( ( clazz = classes[ j++ ] ) ) { className = classNames[ i ];
// Remove *all* instances // Remove *all* instances
while ( cur.indexOf( " " + clazz + " " ) > -1 ) { while ( cur.indexOf( " " + className + " " ) > -1 ) {
cur = cur.replace( " " + clazz + " ", " " ); cur = cur.replace( " " + className + " ", " " );
} }
} }
// Only assign if different to avoid unneeded rendering. // Only assign if different to avoid unneeded rendering.
finalValue = stripAndCollapse( cur ); finalValue = stripAndCollapse( cur );
if ( curValue !== finalValue ) { if ( curValue !== finalValue ) {
elem.setAttribute( "class", finalValue ); this.setAttribute( "class", finalValue );
} }
} }
} } );
} }
return this; return this;
}, },
toggleClass: function( value, stateVal ) { toggleClass: function( value, stateVal ) {
var type = typeof value, var classNames, className, i, self,
type = typeof value,
isValidValue = type === "string" || Array.isArray( value ); isValidValue = type === "string" || Array.isArray( value );
if ( typeof stateVal === "boolean" && isValidValue ) {
return stateVal ? this.addClass( value ) : this.removeClass( value );
}
if ( isFunction( value ) ) { if ( isFunction( value ) ) {
return this.each( function( i ) { return this.each( function( i ) {
jQuery( this ).toggleClass( jQuery( this ).toggleClass(
@ -7577,17 +7600,20 @@ jQuery.fn.extend( {
} ); } );
} }
return this.each( function() { if ( typeof stateVal === "boolean" && isValidValue ) {
var className, i, self, classNames; return stateVal ? this.addClass( value ) : this.removeClass( value );
}
classNames = classesToArray( value );
return this.each( function() {
if ( isValidValue ) { if ( isValidValue ) {
// Toggle individual class names // Toggle individual class names
i = 0;
self = jQuery( this ); self = jQuery( this );
classNames = classesToArray( value );
while ( ( className = classNames[ i++ ] ) ) { for ( i = 0; i < classNames.length; i++ ) {
className = classNames[ i ];
// Check each className given, space separated list // Check each className given, space separated list
if ( self.hasClass( className ) ) { if ( self.hasClass( className ) ) {
@ -7721,7 +7747,7 @@ jQuery.extend( {
val : val :
// Support: IE <=10 - 11 only // Support: IE <=10 - 11 only
// option.text throws exceptions (#14686, #14858) // option.text throws exceptions (trac-14686, trac-14858)
// Strip and collapse whitespace // Strip and collapse whitespace
// https://html.spec.whatwg.org/#strip-and-collapse-whitespace // https://html.spec.whatwg.org/#strip-and-collapse-whitespace
stripAndCollapse( jQuery.text( elem ) ); stripAndCollapse( jQuery.text( elem ) );
@ -7748,7 +7774,7 @@ jQuery.extend( {
option = options[ i ]; option = options[ i ];
// Support: IE <=9 only // Support: IE <=9 only
// IE8-9 doesn't update selected after form reset (#2551) // IE8-9 doesn't update selected after form reset (trac-2551)
if ( ( option.selected || i === index ) && if ( ( option.selected || i === index ) &&
// Don't return options that are disabled or in a disabled optgroup // Don't return options that are disabled or in a disabled optgroup
@ -7891,8 +7917,8 @@ jQuery.extend( jQuery.event, {
return; return;
} }
// Determine event propagation path in advance, per W3C events spec (#9951) // Determine event propagation path in advance, per W3C events spec (trac-9951)
// Bubble up to document, then to window; watch for a global ownerDocument var (#9724) // Bubble up to document, then to window; watch for a global ownerDocument var (trac-9724)
if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) {
bubbleType = special.delegateType || type; bubbleType = special.delegateType || type;
@ -7944,7 +7970,7 @@ jQuery.extend( jQuery.event, {
acceptData( elem ) ) { acceptData( elem ) ) {
// Call a native DOM method on the target with the same name as the event. // Call a native DOM method on the target with the same name as the event.
// Don't do default actions on window, that's where global variables be (#6170) // Don't do default actions on window, that's where global variables be (trac-6170)
if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) {
// Don't re-trigger an onFOO event when we call its FOO() method // Don't re-trigger an onFOO event when we call its FOO() method
@ -8654,7 +8680,9 @@ jQuery.each(
// Support: Android <=4.0 only // Support: Android <=4.0 only
// Make sure we trim BOM and NBSP // Make sure we trim BOM and NBSP
var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; // Require that the "whitespace run" starts from a non-whitespace
// to avoid O(N^2) behavior when the engine would try matching "\s+$" at each space position.
var rtrim = /^[\s\uFEFF\xA0]+|([^\s\uFEFF\xA0])[\s\uFEFF\xA0]+$/g;
// Bind a function to a context, optionally partially applying any // Bind a function to a context, optionally partially applying any
// arguments. // arguments.
@ -8721,7 +8749,7 @@ jQuery.isNumeric = function( obj ) {
jQuery.trim = function( text ) { jQuery.trim = function( text ) {
return text == null ? return text == null ?
"" : "" :
( text + "" ).replace( rtrim, "" ); ( text + "" ).replace( rtrim, "$1" );
}; };
@ -8769,8 +8797,8 @@ jQuery.noConflict = function( deep ) {
}; };
// Expose jQuery and $ identifiers, even in AMD // Expose jQuery and $ identifiers, even in AMD
// (#7102#comment:10, https://github.com/jquery/jquery/pull/557) // (trac-7102#comment:10, https://github.com/jquery/jquery/pull/557)
// and CommonJS for browser emulators (#13566) // and CommonJS for browser emulators (trac-13566)
if ( typeof noGlobal === "undefined" ) { if ( typeof noGlobal === "undefined" ) {
window.jQuery = window.$ = jQuery; window.jQuery = window.$ = jQuery;
} }

4
src/static/templates/admin/diagnostics.hbs

@ -168,8 +168,8 @@
<dl class="row"> <dl class="row">
<dd class="col-sm-12"> <dd class="col-sm-12">
If you need support please check the following links first before you create a new issue: If you need support please check the following links first before you create a new issue:
<a href="https://vaultwarden.discourse.group/" target="_blank" rel="noreferrer">Vaultwarden Forum</a> <a href="https://vaultwarden.discourse.group/" target="_blank" rel="noreferrer noopener">Vaultwarden Forum</a>
| <a href="https://github.com/dani-garcia/vaultwarden/discussions" target="_blank" rel="noreferrer">Github Discussions</a> | <a href="https://github.com/dani-garcia/vaultwarden/discussions" target="_blank" rel="noreferrer noopener">Github Discussions</a>
</dd> </dd>
</dl> </dl>
<dl class="row"> <dl class="row">

2
src/static/templates/admin/organizations.hbs

@ -49,7 +49,7 @@
</main> </main>
<link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" /> <link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" />
<script src="{{urlpath}}/vw_static/jquery-3.6.0.slim.js"></script> <script src="{{urlpath}}/vw_static/jquery-3.6.1.slim.js"></script>
<script src="{{urlpath}}/vw_static/datatables.js"></script> <script src="{{urlpath}}/vw_static/datatables.js"></script>
<script> <script>
'use strict'; 'use strict';

2
src/static/templates/admin/settings.hbs

@ -122,7 +122,7 @@
This does not include any configuration or file attachment data that may This does not include any configuration or file attachment data that may
also be needed to fully restore a vaultwarden instance. For details on also be needed to fully restore a vaultwarden instance. For details on
how to perform complete backups, refer to the wiki page on how to perform complete backups, refer to the wiki page on
<a href="https://github.com/dani-garcia/vaultwarden/wiki/Backing-up-your-vault">backups</a>. <a href="https://github.com/dani-garcia/vaultwarden/wiki/Backing-up-your-vault" target="_blank" rel="noopener noreferrer">backups</a>.
</div> </div>
<button type="button" class="btn btn-primary" onclick="backupDatabase();">Backup Database</button> <button type="button" class="btn btn-primary" onclick="backupDatabase();">Backup Database</button>
</div> </div>

2
src/static/templates/admin/users.hbs

@ -136,7 +136,7 @@
</main> </main>
<link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" /> <link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" />
<script src="{{urlpath}}/vw_static/jquery-3.6.0.slim.js"></script> <script src="{{urlpath}}/vw_static/jquery-3.6.1.slim.js"></script>
<script src="{{urlpath}}/vw_static/datatables.js"></script> <script src="{{urlpath}}/vw_static/datatables.js"></script>
<script> <script>
'use strict'; 'use strict';

4
src/util.rs

@ -60,7 +60,7 @@ impl Fairing for AppHeaders {
// Leaked Passwords check: api.pwnedpasswords.com // Leaked Passwords check: api.pwnedpasswords.com
// 2FA/MFA Site check: 2fa.directory // 2FA/MFA Site check: 2fa.directory
// # Mail Relay: https://bitwarden.com/blog/add-privacy-and-security-using-email-aliases-with-bitwarden/ // # Mail Relay: https://bitwarden.com/blog/add-privacy-and-security-using-email-aliases-with-bitwarden/
// app.simplelogin.io, app.anonaddy.com, relay.firefox.com // app.simplelogin.io, app.anonaddy.com, api.fastmail.com
let csp = format!( let csp = format!(
"default-src 'self'; \ "default-src 'self'; \
script-src 'self'{script_src}; \ script-src 'self'{script_src}; \
@ -68,7 +68,7 @@ impl Fairing for AppHeaders {
img-src 'self' data: https://haveibeenpwned.com/ https://www.gravatar.com {icon_service_csp}; \ img-src 'self' data: https://haveibeenpwned.com/ https://www.gravatar.com {icon_service_csp}; \
child-src 'self' https://*.duosecurity.com https://*.duofederal.com; \ child-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; \ frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
connect-src 'self' https://api.pwnedpasswords.com/range/ https://2fa.directory/api/ https://app.simplelogin.io/api/ https://app.anonaddy.com/api/ https://relay.firefox.com/api/; \ connect-src 'self' https://api.pwnedpasswords.com/range/ https://2fa.directory/api/ https://app.simplelogin.io/api/ https://app.anonaddy.com/api/ https://api.fastmail.com/; \
object-src 'self' blob:; \ object-src 'self' blob:; \
frame-ancestors 'self' chrome-extension://nngceckbapebfimnlniiiahkandclblb chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh moz-extension://* {allowed_iframe_ancestors};", frame-ancestors 'self' chrome-extension://nngceckbapebfimnlniiiahkandclblb chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh moz-extension://* {allowed_iframe_ancestors};",
icon_service_csp=CONFIG._icon_service_csp(), icon_service_csp=CONFIG._icon_service_csp(),

Loading…
Cancel
Save