Browse Source

Merge 69b159775e into 58a980f850

pull/7817/merge
Mathijs van Veluw 2 days ago
committed by GitHub
parent
commit
c676e445a2
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 357
      Cargo.lock
  2. 6
      Cargo.toml
  3. 1
      src/api/core/accounts.rs
  4. 2
      src/api/core/ciphers.rs
  5. 4
      src/api/core/organizations.rs
  6. 1
      src/api/core/two_factor/authenticator.rs
  7. 1
      src/api/core/two_factor/duo.rs
  8. 1
      src/api/core/two_factor/email.rs
  9. 2
      src/api/core/two_factor/webauthn.rs
  10. 1
      src/api/core/two_factor/yubikey.rs
  11. 2
      src/api/mod.rs
  12. 29
      src/api/notifications.rs
  13. 86
      src/api/web.rs
  14. 4
      src/auth.rs
  15. 6
      src/error.rs
  16. 4
      src/mail.rs
  17. 74
      src/main.rs
  18. 113
      src/util.rs

357
Cargo.lock

@ -327,12 +327,6 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5161fb43cb8f231f06983cb2b8f9ee9cee14be0b1356d8383398cc0f312800d1"
[[package]]
name = "atomic"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba"
[[package]]
name = "atomic"
version = "0.6.1"
@ -376,7 +370,7 @@ dependencies = [
"bytes",
"fastrand",
"hex",
"http 1.5.0",
"http",
"sha1 0.10.7",
"time",
"tokio",
@ -414,8 +408,8 @@ dependencies = [
"bytes",
"bytes-utils",
"fastrand",
"http 1.5.0",
"http-body 1.1.0",
"http",
"http-body",
"percent-encoding",
"pin-project-lite",
"tracing",
@ -442,7 +436,7 @@ dependencies = [
"aws-types",
"bytes",
"fastrand",
"http 1.5.0",
"http",
"regex-lite",
"tracing",
]
@ -467,7 +461,7 @@ dependencies = [
"aws-types",
"bytes",
"fastrand",
"http 1.5.0",
"http",
"regex-lite",
"tracing",
]
@ -493,7 +487,7 @@ dependencies = [
"aws-smithy-xml",
"aws-types",
"fastrand",
"http 1.5.0",
"http",
"regex-lite",
"tracing",
]
@ -512,7 +506,7 @@ dependencies = [
"form_urlencoded",
"hex",
"hmac 0.13.0",
"http 1.5.0",
"http",
"percent-encoding",
"sha2 0.11.0",
"time",
@ -542,8 +536,8 @@ dependencies = [
"bytes-utils",
"futures-core",
"futures-util",
"http 1.5.0",
"http-body 1.1.0",
"http",
"http-body",
"http-body-util",
"percent-encoding",
"pin-project-lite",
@ -598,8 +592,8 @@ dependencies = [
"aws-smithy-types",
"bytes",
"fastrand",
"http 1.5.0",
"http-body 1.1.0",
"http",
"http-body",
"http-body-util",
"pin-project-lite",
"pin-utils",
@ -617,7 +611,7 @@ dependencies = [
"aws-smithy-runtime-api-macros",
"aws-smithy-types",
"bytes",
"http 1.5.0",
"http",
"pin-project-lite",
"tokio",
"tracing",
@ -643,7 +637,7 @@ checksum = "e8f395d93304280b64b7632fea798d177e74897fe7f063416ce627cd6fa24829"
dependencies = [
"aws-smithy-runtime-api",
"aws-smithy-types",
"http 1.5.0",
"http",
]
[[package]]
@ -655,8 +649,8 @@ dependencies = [
"base64-simd",
"bytes",
"bytes-utils",
"http 1.5.0",
"http-body 1.1.0",
"http",
"http-body",
"http-body-util",
"itoa",
"num-integer",
@ -1896,7 +1890,7 @@ version = "0.10.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3"
dependencies = [
"atomic 0.6.1",
"atomic",
"pear",
"serde",
"toml 0.8.23",
@ -2203,7 +2197,7 @@ dependencies = [
"fnv",
"futures-core",
"futures-sink",
"http 1.5.0",
"http",
"indexmap 2.14.2",
"slab",
"tokio",
@ -2412,17 +2406,6 @@ dependencies = [
"jetscii",
]
[[package]]
name = "http"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http"
version = "1.5.0"
@ -2433,17 +2416,6 @@ dependencies = [
"itoa",
]
[[package]]
name = "http-body"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http 0.2.12",
"pin-project-lite",
]
[[package]]
name = "http-body"
version = "1.1.0"
@ -2451,7 +2423,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
dependencies = [
"bytes",
"http 1.5.0",
"http",
]
[[package]]
@ -2462,8 +2434,8 @@ checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c"
dependencies = [
"bytes",
"futures-core",
"http 1.5.0",
"http-body 1.1.0",
"http",
"http-body",
"pin-project-lite",
]
@ -2488,29 +2460,6 @@ dependencies = [
"typenum",
]
[[package]]
name = "hyper"
version = "0.14.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"http 0.2.12",
"http-body 0.4.6",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2 0.5.10",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "hyper"
version = "1.11.1"
@ -2522,9 +2471,10 @@ dependencies = [
"futures-channel",
"futures-core",
"h2",
"http 1.5.0",
"http-body 1.1.0",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"smallvec",
@ -2538,12 +2488,12 @@ version = "0.27.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53"
dependencies = [
"http 1.5.0",
"hyper 1.11.1",
"http",
"hyper",
"hyper-util",
"rustls 0.23.45",
"rustls",
"tokio",
"tokio-rustls 0.26.6",
"tokio-rustls",
"tower-service",
]
@ -2557,15 +2507,15 @@ dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http 1.5.0",
"http-body 1.1.0",
"http",
"http-body",
"httparse",
"hyper 1.11.1",
"hyper",
"ipnet",
"libc",
"percent-encoding",
"pin-project-lite",
"socket2 0.6.5",
"socket2",
"system-configuration",
"tokio",
"tower-service",
@ -2742,7 +2692,7 @@ version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222"
dependencies = [
"socket2 0.6.5",
"socket2",
"widestring",
"windows-registry",
"windows-result",
@ -2999,12 +2949,12 @@ dependencies = [
"nom 8.0.0",
"percent-encoding",
"quoted_printable",
"rustls 0.23.45",
"rustls",
"rustls-native-certs",
"serde",
"socket2 0.6.5",
"socket2",
"tokio",
"tokio-rustls 0.26.6",
"tokio-rustls",
"tracing",
"url",
]
@ -3217,7 +3167,7 @@ dependencies = [
"bytes",
"encoding_rs",
"futures-util",
"http 1.5.0",
"http",
"httparse",
"memchr",
"mime",
@ -3411,7 +3361,7 @@ dependencies = [
"base64 0.22.1",
"chrono",
"getrandom 0.2.17",
"http 1.5.0",
"http",
"rand 0.8.8",
"serde",
"serde_json",
@ -3462,7 +3412,7 @@ dependencies = [
"base64 0.23.1",
"bytes",
"futures",
"http 1.5.0",
"http",
"jiff",
"log",
"md-5",
@ -3485,8 +3435,8 @@ checksum = "4d6588678f3040a3eab1819364dcbc6a746a8286d1ceb23a70a6614faa01dc6c"
dependencies = [
"bytes",
"futures",
"http 1.5.0",
"http-body 1.1.0",
"http",
"http-body",
"opendal-core",
"reqwest",
]
@ -3514,7 +3464,7 @@ dependencies = [
"base64 0.23.1",
"bytes",
"crc-fast",
"http 1.5.0",
"http",
"log",
"md-5",
"opendal-core",
@ -3537,7 +3487,7 @@ dependencies = [
"dyn-clone",
"ed25519-dalek",
"hmac 0.12.1",
"http 1.5.0",
"http",
"itertools",
"log",
"oauth2",
@ -4251,6 +4201,12 @@ dependencies = [
"syn 3.0.6",
]
[[package]]
name = "ref-swap"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09c30c54dffee5b40af088d5d50aa3455c91a0127164b51f0215efc4cb28fb3c"
[[package]]
name = "regex"
version = "1.13.1"
@ -4306,7 +4262,7 @@ dependencies = [
"bytes",
"form_urlencoded",
"hex",
"http 1.5.0",
"http",
"log",
"percent-encoding",
"quick-xml 0.42.0",
@ -4325,7 +4281,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "906bfd028f96bfe73fdb892ce26f3b3f99b4f30feb9bd024be58bd6657f68b2b"
dependencies = [
"bytes",
"http 1.5.0",
"http",
"log",
"quick-xml 0.42.0",
"reqsign-aws-core",
@ -4345,7 +4301,7 @@ dependencies = [
"bytes",
"hex",
"hmac 0.13.0",
"http 1.5.0",
"http",
"jiff",
"log",
"percent-encoding",
@ -4379,10 +4335,10 @@ dependencies = [
"futures-core",
"futures-util",
"h2",
"http 1.5.0",
"http-body 1.1.0",
"http",
"http-body",
"http-body-util",
"hyper 1.11.1",
"hyper",
"hyper-rustls",
"hyper-util",
"js-sys",
@ -4390,7 +4346,7 @@ dependencies = [
"mime",
"percent-encoding",
"pin-project-lite",
"rustls 0.23.45",
"rustls",
"rustls-pki-types",
"rustls-platform-verifier",
"serde",
@ -4398,7 +4354,7 @@ dependencies = [
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tokio-rustls 0.26.6",
"tokio-rustls",
"tokio-util",
"tower",
"tower-http",
@ -4441,118 +4397,117 @@ dependencies = [
]
[[package]]
name = "rmp"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c"
dependencies = [
"num-traits",
]
[[package]]
name = "rmpv"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a4e1d4b9b938a26d2996af33229f0ca0956c652c1375067f0b45291c1df8417"
dependencies = [
"rmp",
]
[[package]]
name = "rocket"
version = "0.5.1"
name = "rkt"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a516907296a31df7dc04310e7043b61d71954d703b603cc6867a026d7e72d73f"
checksum = "1105eb4e327458258fa921de564fd9bd45b9f8fd27d771e5fb82314c703eb8a3"
dependencies = [
"async-stream",
"async-trait",
"atomic 0.5.3",
"binascii",
"bytes",
"cookie",
"either",
"figment",
"futures",
"http",
"hyper",
"hyper-util",
"indexmap 2.14.2",
"log",
"libc",
"memchr",
"multer",
"num_cpus",
"parking_lot",
"pin-project-lite",
"rand 0.8.8",
"rand 0.10.3",
"ref-cast",
"rocket_codegen",
"rocket_http",
"ref-swap",
"rkt_codegen",
"rkt_http",
"rustls",
"rustls-pki-types",
"serde",
"serde_json",
"state",
"tempfile",
"time",
"tokio",
"tokio-rustls",
"tokio-stream",
"tokio-util",
"tracing",
"ubyte",
"version_check",
"yansi",
]
[[package]]
name = "rocket_codegen"
version = "0.5.1"
name = "rkt_codegen"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46"
checksum = "55143dea09d0f0c86aeb8d5cd0998b80a7920518bbb412dc860292be9524a259"
dependencies = [
"devise",
"glob",
"indexmap 2.14.2",
"proc-macro2",
"quote",
"rocket_http",
"rkt_http",
"syn 2.0.119",
"unicode-xid",
"version_check",
]
[[package]]
name = "rocket_http"
version = "0.5.1"
name = "rkt_http"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e274915a20ee3065f611c044bd63c40757396b6dbc057d6046aec27f14f882b9"
checksum = "ccb2598aa857ab8d59d009604d862a7780c1213d3e162321bd30f72e93129637"
dependencies = [
"cookie",
"either",
"futures",
"http 0.2.12",
"hyper 0.14.32",
"indexmap 2.14.2",
"log",
"memchr",
"pear",
"percent-encoding",
"pin-project-lite",
"ref-cast",
"rustls 0.21.12",
"rustls-pemfile",
"serde",
"smallvec",
"stable-pattern",
"state",
"time",
"tokio",
"tokio-rustls 0.24.1",
"tinyvec",
"uncased",
]
[[package]]
name = "rocket_ws"
version = "0.1.1"
name = "rkt_ws"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25f1877668c937b701177c349f21383c556cd3bb4ba8fa1d07fa96ccb3a8782e"
checksum = "cc1a9b1739c2b945e0aae4efb1ccb0cea1a410893143651077977f57e376cfab"
dependencies = [
"rocket",
"rkt",
"tokio-tungstenite",
]
[[package]]
name = "rmp"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c"
dependencies = [
"num-traits",
]
[[package]]
name = "rmpv"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a4e1d4b9b938a26d2996af33229f0ca0956c652c1375067f0b45291c1df8417"
dependencies = [
"rmp",
]
[[package]]
name = "rpassword"
version = "7.5.4"
@ -4645,18 +4600,6 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "rustls"
version = "0.21.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
dependencies = [
"log",
"ring",
"rustls-webpki 0.101.7",
"sct",
]
[[package]]
name = "rustls"
version = "0.23.45"
@ -4667,7 +4610,7 @@ dependencies = [
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki 0.103.15",
"rustls-webpki",
"subtle",
"zeroize",
]
@ -4684,15 +4627,6 @@ dependencies = [
"security-framework",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
"base64 0.21.7",
]
[[package]]
name = "rustls-pki-types"
version = "1.15.1"
@ -4713,10 +4647,10 @@ dependencies = [
"jni",
"log",
"once_cell",
"rustls 0.23.45",
"rustls",
"rustls-native-certs",
"rustls-platform-verifier-android",
"rustls-webpki 0.103.15",
"rustls-webpki",
"security-framework",
"security-framework-sys",
"webpki-root-certs",
@ -4729,16 +4663,6 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f"
[[package]]
name = "rustls-webpki"
version = "0.101.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "rustls-webpki"
version = "0.103.15"
@ -4825,16 +4749,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sct"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "sec1"
version = "0.7.3"
@ -5165,16 +5079,6 @@ version = "1.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e"
[[package]]
name = "socket2"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "socket2"
version = "0.6.5"
@ -5500,7 +5404,7 @@ dependencies = [
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2 0.6.5",
"socket2",
"tokio-macros",
"windows-sys 0.61.2",
]
@ -5516,23 +5420,13 @@ dependencies = [
"syn 3.0.6",
]
[[package]]
name = "tokio-rustls"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
dependencies = [
"rustls 0.21.12",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db"
dependencies = [
"rustls 0.23.45",
"rustls",
"tokio",
]
@ -5549,9 +5443,9 @@ dependencies = [
[[package]]
name = "tokio-tungstenite"
version = "0.21.0"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38"
checksum = "17a073bfed563fa236697a068031408a93cd9522e08abf9933ead3e73411bd71"
dependencies = [
"futures-util",
"log",
@ -5705,8 +5599,8 @@ dependencies = [
"bytes",
"futures-core",
"futures-util",
"http 1.5.0",
"http-body 1.1.0",
"http",
"http-body",
"http-body-util",
"pin-project-lite",
"tokio",
@ -5799,21 +5693,18 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "tungstenite"
version = "0.21.0"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1"
checksum = "e48ac77174b19c110a50ab2128b24215ac9cb40e0e12e093fb602d175c569d22"
dependencies = [
"byteorder",
"bytes",
"data-encoding",
"http 1.5.0",
"http",
"httparse",
"log",
"rand 0.8.8",
"sha1 0.10.7",
"thiserror 1.0.69",
"url",
"utf-8",
"rand 0.10.3",
"sha1 0.11.0",
"thiserror 2.0.21",
]
[[package]]
@ -5890,12 +5781,6 @@ version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf8_iter"
version = "1.0.4"
@ -5957,7 +5842,7 @@ dependencies = [
"handlebars",
"hickory-resolver",
"html5gum",
"http 1.5.0",
"http",
"ipnet",
"job_scheduler_ng",
"jsonwebtoken",
@ -5983,11 +5868,11 @@ dependencies = [
"reqsign-core",
"reqwest",
"ring",
"rkt",
"rkt_ws",
"rmpv",
"rocket",
"rocket_ws",
"rpassword",
"rustls 0.23.45",
"rustls",
"semver",
"serde",
"serde_json",

6
Cargo.toml

@ -79,9 +79,9 @@ num-traits = "0.2.19"
num-derive = "0.5.1"
bigdecimal = "0.4.11"
# Web framework
rocket = { version = "0.5.1", default-features = false, features = ["json", "tls"] }
rocket_ws = { version = "0.1.1" }
# Web framework fork from Rocket https://github.com/rustfoo/rkt
rocket = { package = "rkt", version = "1.2.0", default-features = false, features = ["json", "tls"] }
rocket_ws = { package = "rkt_ws", version = "1.2.0" }
# WebSockets libraries
rmpv = "1.3.1" # MessagePack library

1
src/api/core/accounts.rs

@ -1323,6 +1323,7 @@ async fn post_delete_account(data: Json<PasswordOrOtpData>, headers: Headers, co
delete_account(data, headers, conn).await
}
#[suppress(dubious_payload)]
#[delete("/accounts", data = "<data>")]
async fn delete_account(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> EmptyResult {
let data: PasswordOrOtpData = data.into_inner();

2
src/api/core/ciphers.rs

@ -1532,6 +1532,7 @@ async fn delete_cipher_admin(cipher_id: CipherId, headers: Headers, conn: DbConn
// permanent delete
}
#[suppress(dubious_payload)]
#[delete("/ciphers", data = "<data>")]
async fn delete_cipher_selected(
data: Json<CipherIdsData>,
@ -1565,6 +1566,7 @@ async fn delete_cipher_selected_put(
// soft delete
}
#[suppress(dubious_payload)]
#[delete("/ciphers/admin", data = "<data>")]
async fn delete_cipher_selected_admin(
data: Json<CipherIdsData>,

4
src/api/core/organizations.rs

@ -215,6 +215,7 @@ async fn create_organization(headers: Headers, data: Json<OrgData>, conn: DbConn
Ok(Json(org.to_json()))
}
#[suppress(dubious_payload)]
#[delete("/organizations/<org_id>", data = "<data>")]
async fn delete_organization(
org_id: OrganizationId,
@ -711,6 +712,7 @@ struct BulkCollectionIds {
ids: Vec<CollectionId>,
}
#[suppress(dubious_payload)]
#[delete("/organizations/<org_id>/collections", data = "<data>")]
async fn bulk_delete_organization_collections(
org_id: OrganizationId,
@ -1593,6 +1595,7 @@ async fn edit_member(
member_to_edit.save(&conn).await
}
#[suppress(dubious_payload)]
#[delete("/organizations/<org_id>/users", data = "<data>")]
async fn bulk_delete_member(
org_id: OrganizationId,
@ -2677,6 +2680,7 @@ async fn delete_group_impl(
group.delete(org_id, conn).await
}
#[suppress(dubious_payload)]
#[delete("/organizations/<org_id>/groups", data = "<data>")]
async fn bulk_delete_groups(
org_id: OrganizationId,

1
src/api/core/two_factor/authenticator.rs

@ -177,6 +177,7 @@ struct DisableAuthenticatorData {
user_verification_token: String,
}
#[suppress(dubious_payload)]
#[delete("/two-factor/authenticator", data = "<data>")]
async fn disable_authenticator(data: Json<DisableAuthenticatorData>, headers: Headers, conn: DbConn) -> EmptyResult {
let user = headers.user;

1
src/api/core/two_factor/duo.rs

@ -143,6 +143,7 @@ async fn activate_duo_put(data: Json<EnableDuoData>, headers: Headers, conn: DbC
activate_duo(data, headers, conn).await
}
#[suppress(dubious_payload)]
#[delete("/two-factor/duo", data = "<data>")]
async fn disable_duo(data: Json<VerificationTokenData>, headers: Headers, conn: DbConn) -> EmptyResult {
let user = headers.user;

1
src/api/core/two_factor/email.rs

@ -255,6 +255,7 @@ async fn email(data: Json<EmailData>, headers: Headers, conn: DbConn) -> JsonRes
Ok(Json(json!({})))
}
#[suppress(dubious_payload)]
#[delete("/two-factor/email", data = "<data>")]
async fn disable_email(data: Json<VerificationTokenData>, headers: Headers, conn: DbConn) -> EmptyResult {
let user = headers.user;

2
src/api/core/two_factor/webauthn.rs

@ -325,11 +325,13 @@ struct DeleteWebauthnData {
user_verification_token: String,
}
#[suppress(dubious_payload)]
#[delete("/two-factor/webauthn", data = "<data>")]
async fn delete_webauthn(data: Json<DeleteWebauthnData>, headers: Headers, conn: DbConn) -> JsonResult {
inner_delete_webauthns(&data.user_verification_token, |key| key.id != data.id, headers, &conn).await
}
#[suppress(dubious_payload)]
#[delete("/two-factor/webauthn/all", data = "<data>")]
async fn delete_webauthns(data: Json<VerificationTokenData>, headers: Headers, conn: DbConn) -> JsonResult {
inner_delete_webauthns(&data.user_verification_token, |_| false, headers, &conn).await

1
src/api/core/two_factor/yubikey.rs

@ -202,6 +202,7 @@ async fn activate_yubikey_put(data: Json<EnableYubikeyData>, headers: Headers, c
activate_yubikey(data, headers, conn).await
}
#[suppress(dubious_payload)]
#[delete("/two-factor/yubikey", data = "<data>")]
async fn delete_yubikeys(data: Json<VerificationTokenData>, headers: Headers, conn: DbConn) -> EmptyResult {
let user = headers.user;

2
src/api/mod.rs

@ -30,7 +30,7 @@ pub use crate::api::{
},
web::catchers as web_catchers,
web::routes as web_routes,
web::{invalidate_css_cache, static_files},
web::{STATIC_FILES, invalidate_css_cache},
};
use crate::{
CONFIG,

29
src/api/notifications.rs

@ -4,6 +4,7 @@ use std::{
time::{Duration, Instant},
};
use bytes::Bytes;
use chrono::{NaiveDateTime, Utc};
use rmpv::Value;
use rocket::{Route, futures::StreamExt};
@ -212,7 +213,7 @@ fn websockets_hub<'r>(
if last_received.elapsed() > WS_CLIENT_TIMEOUT {
break;
}
yield Message::Ping(create_ping());
yield Message::Ping(PING);
}
}
}
@ -295,7 +296,7 @@ fn anonymous_websockets_hub<'r>(ws: WebSocket, token: String, ip: ClientIp) -> R
if last_received.elapsed() > WS_CLIENT_TIMEOUT {
break;
}
yield Message::Ping(create_ping());
yield Message::Ping(PING);
}
}
}
@ -307,7 +308,7 @@ fn anonymous_websockets_hub<'r>(ws: WebSocket, token: String, ip: ClientIp) -> R
// Websockets server
//
fn serialize(val: &Value) -> Vec<u8> {
fn serialize(val: &Value) -> Bytes {
use rmpv::encode::write_value;
let mut buf = Vec::new();
@ -335,7 +336,7 @@ fn serialize(val: &Value) -> Vec<u8> {
}
len_buf.append(&mut buf);
len_buf
len_buf.into()
}
fn serialize_date(date: NaiveDateTime) -> Value {
@ -358,7 +359,9 @@ fn convert_option<T: Into<Value>>(option: Option<T>) -> Value {
}
const RECORD_SEPARATOR: u8 = 0x1e;
const INITIAL_RESPONSE: [u8; 3] = [0x7b, 0x7d, RECORD_SEPARATOR]; // {, }, <RS>
const INITIAL_RESPONSE: Bytes = Bytes::from_static(&[0x7b, 0x7d, RECORD_SEPARATOR]); // {, }, <RS>
// Same result as `serialize(&Value::Array(vec![6.into()]))`
const PING: Bytes = Bytes::from_static(&[0x02, 0x91, 0x06]);
#[derive(Deserialize, Copy, Clone, Eq, PartialEq)]
struct InitialMessage<'a> {
@ -379,10 +382,10 @@ pub struct WebSocketUsers {
}
impl WebSocketUsers {
async fn send_update(&self, user_id: &UserId, data: &[u8]) {
async fn send_update(&self, user_id: &UserId, data: &Bytes) {
if let Some(user) = self.map.get(user_id.as_ref()).map(|v| v.clone()) {
for (_, sender) in &user {
if let Err(e) = sender.send(Message::binary(data)).await {
if let Err(e) = sender.send(Message::binary(data.clone())).await {
error!("Error sending WS update {e}");
}
}
@ -614,11 +617,11 @@ impl AnonymousWebSocketSubscriptions {
}
}
async fn send_update(&self, token: &str, data: &[u8]) {
async fn send_update(&self, token: &str, data: &Bytes) {
// Clone the senders so the map isn't kept locked while sending.
let senders = self.map.get(token).map(|v| v.clone()).unwrap_or_default();
for (_, sender) in senders {
if let Err(e) = sender.send(Message::binary(data)).await {
if let Err(e) = sender.send(Message::binary(data.clone())).await {
error!("Error sending WS update {e}");
}
}
@ -652,7 +655,7 @@ impl AnonymousWebSocketSubscriptions {
]
]
*/
fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType, acting_device_id: Option<DeviceId>) -> Vec<u8> {
fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType, acting_device_id: Option<DeviceId>) -> Bytes {
use rmpv::Value as V;
let value = V::Array(vec![
@ -670,7 +673,7 @@ fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType, acting_device_id:
serialize(&value)
}
fn create_anonymous_update(payload: Vec<(Value, Value)>, ut: UpdateType, user_id: &UserId) -> Vec<u8> {
fn create_anonymous_update(payload: Vec<(Value, Value)>, ut: UpdateType, user_id: &UserId) -> Bytes {
use rmpv::Value as V;
let value = V::Array(vec![
@ -691,10 +694,6 @@ fn create_anonymous_update(payload: Vec<(Value, Value)>, ut: UpdateType, user_id
serialize(&value)
}
fn create_ping() -> Vec<u8> {
serialize(&Value::Array(vec![6.into()]))
}
// https://github.com/bitwarden/server/blob/375af7c43b10d9da03525d41452f95de3f921541/src/Core/Enums/PushType.cs
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum UpdateType {

86
src/api/web.rs

@ -1,6 +1,7 @@
use std::{
collections::HashMap,
path::{Path, PathBuf},
sync::{Arc, RwLock},
sync::{Arc, LazyLock, RwLock},
};
use rocket::{
@ -256,7 +257,7 @@ fn alive_head(_conn: DbConn) -> EmptyResult {
// This endpoint/function is used during development and development only.
// It allows to easily develop the admin interface by always loading the files from disk instead from a slice of bytes
// This will only be active during a debug build and only when `RELOAD_TEMPLATES` is set to `true`
// NOTE: Do not forget to add any new files added to the `static_files` function below!
// NOTE: Do not forget to add any new files added to the `STATIC_FILES` HashMap below!
#[cfg(debug_assertions)]
#[get("/vw_static/<filename>", rank = 1)]
pub async fn static_files_dev(filename: PathBuf) -> Option<NamedFile> {
@ -276,32 +277,61 @@ pub async fn static_files_dev(filename: PathBuf) -> Option<NamedFile> {
None
}
pub struct StaticFile {
pub content_type: ContentType,
pub bytes: &'static [u8],
pub etag: String,
}
pub static STATIC_FILES: LazyLock<HashMap<&'static str, StaticFile>> = LazyLock::new(|| {
[
("404.png", ContentType::PNG, &include_bytes!("../static/images/404.png")[..]),
("mail-github.png", ContentType::PNG, &include_bytes!("../static/images/mail-github.png")[..]),
("logo-gray.png", ContentType::PNG, &include_bytes!("../static/images/logo-gray.png")[..]),
("error-x.svg", ContentType::SVG, &include_bytes!("../static/images/error-x.svg")[..]),
("hibp.png", ContentType::PNG, &include_bytes!("../static/images/hibp.png")[..]),
("vaultwarden-icon.png", ContentType::PNG, &include_bytes!("../static/images/vaultwarden-icon.png")[..]),
("vaultwarden-favicon.png", ContentType::PNG, &include_bytes!("../static/images/vaultwarden-favicon.png")[..]),
("404.css", ContentType::CSS, &include_bytes!("../static/scripts/404.css")[..]),
("admin.css", ContentType::CSS, &include_bytes!("../static/scripts/admin.css")[..]),
("admin.js", ContentType::JavaScript, &include_bytes!("../static/scripts/admin.js")[..]),
("admin_settings.js", ContentType::JavaScript, &include_bytes!("../static/scripts/admin_settings.js")[..]),
("admin_users.js", ContentType::JavaScript, &include_bytes!("../static/scripts/admin_users.js")[..]),
(
"admin_organizations.js",
ContentType::JavaScript,
&include_bytes!("../static/scripts/admin_organizations.js")[..],
),
(
"admin_diagnostics.js",
ContentType::JavaScript,
&include_bytes!("../static/scripts/admin_diagnostics.js")[..],
),
("bootstrap.css", ContentType::CSS, &include_bytes!("../static/scripts/bootstrap.css")[..]),
("bootstrap.bundle.js", ContentType::JavaScript, &include_bytes!("../static/scripts/bootstrap.bundle.js")[..]),
("jdenticon-3.3.0.js", ContentType::JavaScript, &include_bytes!("../static/scripts/jdenticon-3.3.0.js")[..]),
("datatables.js", ContentType::JavaScript, &include_bytes!("../static/scripts/datatables.js")[..]),
("datatables.css", ContentType::CSS, &include_bytes!("../static/scripts/datatables.css")[..]),
]
.into_iter()
.map(|(name, content_type, bytes)| {
(
name,
StaticFile {
content_type,
bytes,
etag: sha256_hex(bytes),
},
)
})
.collect()
});
#[get("/vw_static/<filename>", rank = 2)]
pub fn static_files(filename: &str) -> Result<(ContentType, &'static [u8]), Error> {
match filename {
"404.png" => Ok((ContentType::PNG, include_bytes!("../static/images/404.png"))),
"mail-github.png" => Ok((ContentType::PNG, include_bytes!("../static/images/mail-github.png"))),
"logo-gray.png" => Ok((ContentType::PNG, include_bytes!("../static/images/logo-gray.png"))),
"error-x.svg" => Ok((ContentType::SVG, include_bytes!("../static/images/error-x.svg"))),
"hibp.png" => Ok((ContentType::PNG, include_bytes!("../static/images/hibp.png"))),
"vaultwarden-icon.png" => Ok((ContentType::PNG, include_bytes!("../static/images/vaultwarden-icon.png"))),
"vaultwarden-favicon.png" => Ok((ContentType::PNG, include_bytes!("../static/images/vaultwarden-favicon.png"))),
"404.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/404.css"))),
"admin.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/admin.css"))),
"admin.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/admin.js"))),
"admin_settings.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/admin_settings.js"))),
"admin_users.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/admin_users.js"))),
"admin_organizations.js" => {
Ok((ContentType::JavaScript, include_bytes!("../static/scripts/admin_organizations.js")))
}
"admin_diagnostics.js" => {
Ok((ContentType::JavaScript, include_bytes!("../static/scripts/admin_diagnostics.js")))
}
"bootstrap.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/bootstrap.css"))),
"bootstrap.bundle.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/bootstrap.bundle.js"))),
"jdenticon-3.3.0.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jdenticon-3.3.0.js"))),
"datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))),
"datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))),
_ => err!(format!("Static file not found: {filename}")),
pub fn static_files(filename: &str) -> Result<EtagCached<(ContentType, &'static [u8])>, Error> {
if let Some(file) = STATIC_FILES.get(filename) {
Ok(EtagCached::new((file.content_type.clone(), file.bytes), &file.etag))
} else {
err!(format!("Static file not found: {filename}"))
}
}

4
src/auth.rs

@ -77,7 +77,7 @@ pub async fn initialize_keys() -> Result<(), Error> {
let rsa_key_filename = crate::storage::file_name(&CONFIG.private_rsa_key())
.ok_or_else(|| IoError::other("Private RSA key path missing filename"))?;
let operator = CONFIG.opendal_operator_for_path_type(&PathType::RsaKey).map_err(IoError::other)?;
let operator = CONFIG.opendal_operator_for_path_type(&PathType::RsaKey)?;
let priv_key_buffer = match operator.read(&rsa_key_filename).await {
Ok(buffer) => Some(buffer),
@ -1139,7 +1139,7 @@ impl<'r> FromRequest<'r> for ClientIp {
type Error = ();
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let remote = req.remote().map(|r| r.ip());
let remote = req.remote().and_then(rocket::listener::Endpoint::ip);
let ip = if CONFIG._ip_header_enabled() && ip_header_is_trusted(remote) {
client_ip_from_header(req, &CONFIG.ip_header(), |ip| ip_header_is_trusted(Some(ip)))

6
src/error.rs

@ -165,10 +165,12 @@ impl Error {
self
}
#[must_use]
pub fn get_event(&self) -> &Option<ErrorEvent> {
&self.event
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
@ -425,12 +427,12 @@ macro_rules! err_json {
macro_rules! err_handler {
($expr:expr) => {{
error!(target: "auth", "Unauthorized Error: {}", $expr);
return ::rocket::request::Outcome::Error((rocket::http::Status::Unauthorized, $expr));
return ::rocket::request::Outcome::Error((::rocket::http::Status::Unauthorized, $expr));
}};
($usr_msg:expr, $log_value:expr) => {{
let usr_msg = $usr_msg;
let log_value = $log_value;
error!(target: "auth", "Unauthorized Error: {usr_msg}. {log_value}");
return ::rocket::request::Outcome::Error((rocket::http::Status::Unauthorized, usr_msg));
return ::rocket::request::Outcome::Error((::rocket::http::Status::Unauthorized, usr_msg));
}};
}

4
src/mail.rs

@ -738,8 +738,8 @@ async fn send_email(address: &str, subject: &str, body_html: String, body_text:
let smtp_from = Address::from_str(&CONFIG.smtp_from())?;
let body = if CONFIG.smtp_embed_images() {
let logo_gray_body = Body::new(crate::api::static_files("logo-gray.png").unwrap().1.to_vec());
let mail_github_body = Body::new(crate::api::static_files("mail-github.png").unwrap().1.to_vec());
let logo_gray_body = Body::new(crate::api::STATIC_FILES["logo-gray.png"].bytes.to_vec());
let mail_github_body = Body::new(crate::api::STATIC_FILES["mail-github.png"].bytes.to_vec());
MultiPart::alternative().singlepart(SinglePart::plain(body_text)).multipart(
MultiPart::related()
.singlepart(SinglePart::html(body_html))

74
src/main.rs

@ -13,6 +13,9 @@ static GLOBAL: MiMalloc = MiMalloc;
#[macro_use]
extern crate rocket;
// `rkt` codegen generates code using `rkt::` and not `rocket::`
// This ensures both `rocket` and `rkt` point to the same crate
extern crate rocket as rkt;
#[macro_use]
extern crate serde;
#[macro_use]
@ -273,10 +276,11 @@ fn init_logging() -> Result<log::LevelFilter, Error> {
log::LevelFilter::Off
};
// Only show Rocket underscore `_` logs when the level is Debug or higher
// Only show some Rocket/Rkt logs when the level is Debug or higher
// These replace the Rocket underscore `_` logs. Rkt logs some of them as Info.
// Else this will bloat the log output with useless messages.
let rocket_underscore_level = if level >= log::LevelFilter::Debug {
log::LevelFilter::Warn
let rkt_debug_level = if level >= log::LevelFilter::Debug {
log::LevelFilter::Info
} else {
log::LevelFilter::Off
};
@ -296,24 +300,20 @@ fn init_logging() -> Result<log::LevelFilter, Error> {
log::LevelFilter::Off
};
// NOTE: Rocket has been replaced with Rkt, since the crate is called `rkt`, `rocket` does not work even though we use this throughout the code!
let mut default_levels = HashMap::from([
// Hide unknown certificate errors if using self-signed
("rustls::session", log::LevelFilter::Off),
// Hide failed to close stream messages
("hyper::server", log::LevelFilter::Warn),
// Silence Rocket `_` logs
("_", rocket_underscore_level),
("rocket::response::responder::_", rocket_underscore_level),
("rocket::server::_", rocket_underscore_level),
("vaultwarden::api::admin::_", rocket_underscore_level),
("vaultwarden::api::notifications::_", rocket_underscore_level),
// Silence Rocket logs
("rocket::launch", log::LevelFilter::Error),
("rocket::launch_", log::LevelFilter::Error),
("rocket::rocket", log::LevelFilter::Warn),
("rocket::server", log::LevelFilter::Warn),
("rocket::fairing::fairings", log::LevelFilter::Warn),
("rocket::shield::shield", log::LevelFilter::Warn),
// Silence Rkt logs, this covers all `rocket::*`/`rkt::*` targets
("rkt", log::LevelFilter::Warn),
// Rkt request/data guard logs (previously the Rocket `_` logs)
("rkt::codegen", rkt_debug_level),
// Rkt per-connection warnings, like client header read timeouts (15s) on idle keep-alive connections and connections cancelled during shutdown
("rkt::error", log::LevelFilter::Error),
// Hide the "incoming request URI normalized for compatibility" warning logged by `AdHoc::uri_normalizer()`
("rkt::fairing::ad_hoc", log::LevelFilter::Error),
("hyper::proto", log::LevelFilter::Off),
("hyper::client", log::LevelFilter::Off),
// Filter handlebars logs
@ -562,40 +562,56 @@ async fn create_db_pool() -> db::DbPool {
async fn launch_rocket(pool: db::DbPool, extra_debug: bool) -> Result<(), Error> {
let basepath = &CONFIG.domain_path();
// With Rkt and using the `uri_normalizer()` fairing below it will cause collisions when there is a basepath and we append a `/`
// The fairing will add the same path with a `/` too which will break.
// This only needs to be done for the main routes and not the others
let webpath = if basepath.is_empty() {
"/"
} else {
basepath
};
let mut config = rocket::Config::from(rocket::Config::figment());
// We install our own signal handlers below; disable Rocket's built-in handlers
config.shutdown.ctrlc = false;
#[cfg(unix)]
config.shutdown.signals.clear();
config.temp_dir = canonicalize(CONFIG.tmp_folder()).unwrap().into();
config.cli_colors = false; // Make sure Rocket does not color any values for logging.
config.limits = Limits::new()
let limits = Limits::new()
.limit("json", 20.megabytes()) // 20MB should be enough for very large imports, something like 5000+ vault entries
.limit("data-form", 525.megabytes()) // This needs to match the maximum allowed file size for Send
.limit("file", 525.megabytes()); // This needs to match the maximum allowed file size for attachments
let mut config = rocket::Config::figment()
.merge(("shutdown.ctrlc", false)) // We install our own signal handlers below; disable Rocket's built-in handlers
.merge(("temp_dir", canonicalize(CONFIG.tmp_folder()).unwrap()))
.merge(("cli_colors", rocket::config::CliColors::Never)) // Make sure Rocket does not color any values for logging
.merge(("limits", limits));
#[cfg(unix)]
{
config = config.merge(("shutdown.signals", Vec::<String>::new()));
}
if CONFIG._ip_header_enabled() {
config = config.merge(("ip_header", CONFIG.ip_header()));
}
// If adding more paths here, consider also adding them to
// crate::utils::LOGGED_ROUTES to make sure they appear in the log
let instance = rocket::custom(config)
.mount([basepath, "/"].concat(), api::web_routes())
.mount(webpath, api::web_routes())
.mount([basepath, "/api"].concat(), api::core_routes())
.mount([basepath, "/admin"].concat(), api::admin_routes())
.mount([basepath, "/events"].concat(), api::core_events_routes())
.mount([basepath, "/identity"].concat(), api::identity_routes())
.mount([basepath, "/icons"].concat(), api::icons_routes())
.mount([basepath, "/notifications"].concat(), api::notifications_routes())
.register([basepath, "/"].concat(), api::web_catchers())
.register(webpath, api::web_catchers())
.register([basepath, "/api"].concat(), api::core_catchers())
.register([basepath, "/admin"].concat(), api::admin_catchers())
.manage(pool)
.manage(Arc::clone(&WS_USERS))
.manage(Arc::clone(&WS_ANONYMOUS_SUBSCRIPTIONS))
// Rkt no longer ignores trailing slashes and empty path segments like Rocket 0.5 did, this restores that
.attach(rocket::fairing::AdHoc::uri_normalizer())
.attach(util::AppHeaders())
.attach(util::Cors())
.attach(util::BetterLogging(extra_debug))
.attach(util::BetterLogging::new(extra_debug, basepath.clone()))
.ignite()
.await?;

113
src/util.rs

@ -299,72 +299,117 @@ impl<'r, R: 'r + Responder<'r, 'static> + Send> Responder<'r, 'static> for EtagC
// Effectively ignores, any static file route, and the alive endpoint
const LOGGED_ROUTES: [&str; 7] = ["/api", "/admin", "/identity", "/icons", "/attachments", "/events", "/notifications"];
// Same format as the `Display` of a Rocket route, which Rkt does not have anymore
struct RouteDisplay<'a>(&'a rocket::Route);
impl fmt::Display for RouteDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let route = self.0;
if let Some(name) = &route.name {
write!(f, "({name}) ")?;
}
write!(f, "{} {}", route.method.map_or("ANY", |m| m.as_str()), route.uri)?;
if route.rank > 1 {
write!(f, " [{}]", route.rank)?;
}
if let Some(format) = &route.format {
write!(f, " {format}")?;
}
Ok(())
}
}
// Boolean is extra debug, when true, we ignore the whitelist above and also print the mounts
pub struct BetterLogging(pub bool);
pub struct BetterLogging {
extra: bool,
log_request: bool,
log_response: bool,
domain_path: String,
}
impl BetterLogging {
pub fn new(extra: bool, domain_path: String) -> Self {
Self {
extra,
log_request: log_enabled!(target: "request", log::Level::Info),
log_response: log_enabled!(target: "response", log::Level::Info),
domain_path,
}
}
fn should_log(&self, method: Method, path: &str) -> bool {
if self.extra {
return true;
}
if method == Method::Options {
return false;
}
let subpath = path.strip_prefix(&self.domain_path).unwrap_or(path);
LOGGED_ROUTES.iter().any(|r| subpath.starts_with(r))
}
}
#[rocket::async_trait]
impl Fairing for BetterLogging {
fn info(&self) -> Info {
let mut kind = Kind::Liftoff;
if self.log_request {
kind = kind | Kind::Request;
}
if self.log_response {
kind = kind | Kind::Response;
}
Info {
name: "Better Logging",
kind: Kind::Liftoff | Kind::Request | Kind::Response,
kind,
}
}
async fn on_liftoff(&self, rocket: &Rocket<Orbit>) {
if self.0 {
if self.extra {
info!(target: "routes", "Routes loaded:");
let mut routes: Vec<_> = rocket.routes().collect();
routes.sort_by_key(|r| r.uri.path());
routes.sort_by(|a, b| a.uri.path().as_str().cmp(b.uri.path().as_str()));
for route in routes {
let method = route.method.map_or("ANY", |m| m.as_str());
if route.rank < 0 {
info!(target: "routes", "{:<6} {}", route.method, route.uri);
info!(target: "routes", "{:<6} {}", method, route.uri);
} else {
info!(target: "routes", "{:<6} {} [{}]", route.method, route.uri, route.rank);
info!(target: "routes", "{:<6} {} [{}]", method, route.uri, route.rank);
}
}
}
let config = rocket.config();
let scheme = if config.tls_enabled() {
"https"
} else {
"http"
};
let addr = format!("{scheme}://{}:{}", config.address, config.port);
info!(target: "start", "Rocket has launched from {addr}");
for endpoint in rocket.endpoints() {
info!(target: "start", "Rocket has launched from {endpoint}");
}
}
async fn on_request(&self, request: &mut Request<'_>, _data: &mut Data<'_>) {
let uri = request.uri();
let uri_path = uri.path();
let method = request.method();
if !self.0 && method == Method::Options {
if !self.should_log(method, uri_path.as_str()) {
return;
}
let uri = request.uri();
let uri_path = uri.path();
let uri_path_str = uri_path.url_decode_lossy();
let uri_subpath = uri_path_str.strip_prefix(&CONFIG.domain_path()).unwrap_or(&uri_path_str);
if self.0 || LOGGED_ROUTES.iter().any(|r| uri_subpath.starts_with(r)) {
match uri.query() {
Some(q) => info!(target: "request", "{method} {uri_path_str}?{}", &q[..q.len().min(30)]),
None => info!(target: "request", "{method} {uri_path_str}"),
let path = uri_path.url_decode_lossy();
match uri.query() {
Some(q) => {
let q = q.as_str();
info!(target: "request", "{method} {}?{}", path.escape_debug(), q[..q.floor_char_boundary(30)].escape_debug());
}
None => info!(target: "request", "{method} {}", path.escape_debug()),
}
}
async fn on_response<'r>(&self, request: &'r Request<'_>, response: &mut Response<'r>) {
if !self.0 && request.method() == Method::Options {
let uri = request.uri();
if !self.should_log(request.method(), uri.path().as_str()) {
return;
}
let uri_path = request.uri().path();
let uri_path_str = uri_path.url_decode_lossy();
let uri_subpath = uri_path_str.strip_prefix(&CONFIG.domain_path()).unwrap_or(&uri_path_str);
if self.0 || LOGGED_ROUTES.iter().any(|r| uri_subpath.starts_with(r)) {
let status = response.status();
if let Some(route) = request.route() {
info!(target: "response", "{route} => {status}");
} else {
info!(target: "response", "{status}");
}
let status = response.status();
match request.route() {
Some(r) => info!(target: "response", "{} => {status}", RouteDisplay(r)),
None => info!(target: "response", "{status}"),
}
}
}

Loading…
Cancel
Save