From 3d11f4cd16d8464835fb684e7c5528eb54ed535d Mon Sep 17 00:00:00 2001
From: BlackDex <black.dex@gmail.com>
Date: Thu, 30 Mar 2023 17:18:59 +0200
Subject: [PATCH] WebSockets via Rocket's Upgrade connection

This PR implements a (not yet fully released) new feature of Rocket which allows WebSockets/Upgrade connections.
No more need for multiple ports to be opened for Vaultwarden.
No explicit need for a reverse proxy to get WebSockets to work (Although I still suggest to use a reverse proxy).

- Using a git revision for Rocket, since `rocket_ws` is not yet released.
- Updated other crates as well.
- Added a connection guard to clear the WS connection from the Users list.

Fixes #685
Fixes #2917
Fixes #1424
---
 Cargo.lock               | 286 ++++++++++++++++++++++++---------------
 Cargo.toml               |  17 ++-
 src/api/notifications.rs | 231 ++++++++++++++++++++-----------
 3 files changed, 343 insertions(+), 191 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index df4b04f8..01e0309f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -79,9 +79,9 @@ dependencies = [
 
 [[package]]
 name = "async-executor"
-version = "1.5.0"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b"
+checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb"
 dependencies = [
  "async-lock",
  "async-task",
@@ -137,9 +137,9 @@ dependencies = [
 
 [[package]]
 name = "async-process"
-version = "1.6.0"
+version = "1.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6381ead98388605d0d9ff86371043b5aa922a3905824244de40dc263a14fcba4"
+checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9"
 dependencies = [
  "async-io",
  "async-lock",
@@ -148,9 +148,9 @@ dependencies = [
  "cfg-if",
  "event-listener",
  "futures-lite",
- "libc",
+ "rustix",
  "signal-hook",
- "windows-sys 0.42.0",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -182,9 +182,9 @@ dependencies = [
 
 [[package]]
 name = "async-stream"
-version = "0.3.4"
+version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad445822218ce64be7a341abfb0b1ea43b5c23aa83902542a4542e78309d8e5e"
+checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51"
 dependencies = [
  "async-stream-impl",
  "futures-core",
@@ -193,13 +193,13 @@ dependencies = [
 
 [[package]]
 name = "async-stream-impl"
-version = "0.3.4"
+version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965"
+checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 1.0.109",
+ "syn 2.0.13",
 ]
 
 [[package]]
@@ -236,9 +236,9 @@ dependencies = [
 
 [[package]]
 name = "atomic-waker"
-version = "1.1.0"
+version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599"
+checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3"
 
 [[package]]
 name = "autocfg"
@@ -278,9 +278,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "bitflags"
-version = "2.0.2"
+version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1"
+checksum = "c70beb79cbb5ce9c4f8e20849978f34225931f665bb49efa6982875a4d5facb3"
 
 [[package]]
 name = "blake2"
@@ -302,9 +302,9 @@ dependencies = [
 
 [[package]]
 name = "blocking"
-version = "1.3.0"
+version = "1.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8"
+checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65"
 dependencies = [
  "async-channel",
  "async-lock",
@@ -312,6 +312,7 @@ dependencies = [
  "atomic-waker",
  "fastrand",
  "futures-lite",
+ "log",
 ]
 
 [[package]]
@@ -418,9 +419,9 @@ dependencies = [
 
 [[package]]
 name = "chrono-tz"
-version = "0.8.1"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa48fa079165080f11d7753fd0bc175b7d391f276b965fe4b55bfad67856e463"
+checksum = "cf9cc2b23599e6d7479755f3594285efb3f74a1bdca7a7374948bc831e23a552"
 dependencies = [
  "chrono",
  "chrono-tz-build",
@@ -450,9 +451,9 @@ dependencies = [
 
 [[package]]
 name = "concurrent-queue"
-version = "2.1.0"
+version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e"
+checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c"
 dependencies = [
  "crossbeam-utils",
 ]
@@ -523,9 +524,9 @@ dependencies = [
 
 [[package]]
 name = "core-foundation-sys"
-version = "0.8.3"
+version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
 
 [[package]]
 name = "cpufeatures"
@@ -715,7 +716,7 @@ version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "35b50dba0afdca80b187392b24f2499a88c336d5a8493e4b4ccfb608708be56a"
 dependencies = [
- "bitflags 2.0.2",
+ "bitflags 2.1.0",
  "proc-macro2",
  "proc-macro2-diagnostics",
  "quote",
@@ -839,13 +840,13 @@ dependencies = [
 
 [[package]]
 name = "errno"
-version = "0.3.0"
+version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0"
+checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
 dependencies = [
  "errno-dragonfly",
  "libc",
- "windows-sys 0.45.0",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -996,9 +997,9 @@ checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
 
 [[package]]
 name = "futures-lite"
-version = "1.12.0"
+version = "1.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
+checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
 dependencies = [
  "fastrand",
  "futures-core",
@@ -1081,9 +1082,9 @@ dependencies = [
 
 [[package]]
 name = "getrandom"
-version = "0.2.8"
+version = "0.2.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
+checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
 dependencies = [
  "cfg-if",
  "libc",
@@ -1301,16 +1302,16 @@ dependencies = [
 
 [[package]]
 name = "iana-time-zone"
-version = "0.1.54"
+version = "0.1.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c17cc76786e99f8d2f055c11159e7f0091c42474dcc3189fbab96072e873e6d"
+checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c"
 dependencies = [
  "android_system_properties",
  "core-foundation-sys",
  "iana-time-zone-haiku",
  "js-sys",
  "wasm-bindgen",
- "windows 0.46.0",
+ "windows 0.48.0",
 ]
 
 [[package]]
@@ -1378,13 +1379,13 @@ dependencies = [
 
 [[package]]
 name = "io-lifetimes"
-version = "1.0.9"
+version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb"
+checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
 dependencies = [
  "hermit-abi 0.3.1",
  "libc",
- "windows-sys 0.45.0",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -1407,14 +1408,14 @@ checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
 
 [[package]]
 name = "is-terminal"
-version = "0.4.6"
+version = "0.4.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8"
+checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
 dependencies = [
  "hermit-abi 0.3.1",
  "io-lifetimes",
  "rustix",
- "windows-sys 0.45.0",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -1509,15 +1510,15 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.140"
+version = "0.2.141"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
+checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
 
 [[package]]
 name = "libmimalloc-sys"
-version = "0.1.30"
+version = "0.1.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd8c7cbf8b89019683667e347572e6d55a7df7ea36b0c4ce69961b0cde67b174"
+checksum = "43a558e3d911bc3c7bfc8c78bc580b404d6e51c1cefbf656e176a94b49b0df40"
 dependencies = [
  "cc",
  "libc",
@@ -1658,9 +1659,9 @@ dependencies = [
 
 [[package]]
 name = "mimalloc"
-version = "0.1.34"
+version = "0.1.36"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9dcb174b18635f7561a0c6c9fc2ce57218ac7523cf72c50af80e2d79ab8f3ba1"
+checksum = "3d88dad3f985ec267a3fcb7a1726f5cb1a7e8cad8b646e70a84f967210df23da"
 dependencies = [
  "libmimalloc-sys",
 ]
@@ -1712,7 +1713,7 @@ dependencies = [
  "log",
  "memchr",
  "mime",
- "spin 0.9.7",
+ "spin 0.9.8",
  "tokio",
  "tokio-util",
  "version_check",
@@ -1846,9 +1847,9 @@ checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
 
 [[package]]
 name = "openssl"
-version = "0.10.49"
+version = "0.10.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d2f106ab837a24e03672c59b1239669a0596406ff657c3c0835b6b7f0f35a33"
+checksum = "7e30d8bc91859781f0a943411186324d580f2bbeb71b452fe91ae344806af3f1"
 dependencies = [
  "bitflags 1.3.2",
  "cfg-if",
@@ -1887,9 +1888,9 @@ dependencies = [
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.84"
+version = "0.9.85"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a20eace9dc2d82904039cb76dcf50fb1a0bba071cfd1629720b5d6f1ddba0fa"
+checksum = "0d3d193fb1488ad46ffe3aaabc912cc931d02ee8518fe2959aea8ef52718b0c0"
 dependencies = [
  "cc",
  "libc",
@@ -1906,9 +1907,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
 
 [[package]]
 name = "parking"
-version = "2.0.0"
+version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
+checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e"
 
 [[package]]
 name = "parking_lot"
@@ -2105,9 +2106,9 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
 
 [[package]]
 name = "polling"
-version = "2.6.0"
+version = "2.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e1f879b2998099c2d69ab9605d145d5b661195627eccc680002c4918a7fb6fa"
+checksum = "4be1c66a6add46bff50935c313dae30a5030cf8385c5206e8a95e9e9def974aa"
 dependencies = [
  "autocfg",
  "bitflags 1.3.2",
@@ -2116,7 +2117,7 @@ dependencies = [
  "libc",
  "log",
  "pin-project-lite",
- "windows-sys 0.45.0",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
@@ -2160,9 +2161,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.55"
+version = "1.0.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d0dd4be24fcdcfeaa12a432d588dc59bbad6cad3510c67e74a2b6b2fc950564"
+checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
 dependencies = [
  "unicode-ident",
 ]
@@ -2440,8 +2441,7 @@ dependencies = [
 [[package]]
 name = "rocket"
 version = "0.5.0-rc.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58734f7401ae5cfd129685b48f61182331745b357b96f2367f01aebaf1cc9cc9"
+source = "git+https://github.com/SergioBenitez/Rocket?rev=a82508b403420bd941c32ddec3ee3e4875f2b8a5#a82508b403420bd941c32ddec3ee3e4875f2b8a5"
 dependencies = [
  "async-stream",
  "async-trait",
@@ -2479,8 +2479,7 @@ dependencies = [
 [[package]]
 name = "rocket_codegen"
 version = "0.5.0-rc.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7093353f14228c744982e409259fb54878ba9563d08214f2d880d59ff2fc508b"
+source = "git+https://github.com/SergioBenitez/Rocket?rev=a82508b403420bd941c32ddec3ee3e4875f2b8a5#a82508b403420bd941c32ddec3ee3e4875f2b8a5"
 dependencies = [
  "devise",
  "glob",
@@ -2495,8 +2494,7 @@ dependencies = [
 [[package]]
 name = "rocket_http"
 version = "0.5.0-rc.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "936012c99162a03a67f37f9836d5f938f662e26f2717809761a9ac46432090f4"
+source = "git+https://github.com/SergioBenitez/Rocket?rev=a82508b403420bd941c32ddec3ee3e4875f2b8a5#a82508b403420bd941c32ddec3ee3e4875f2b8a5"
 dependencies = [
  "cookie 0.17.0",
  "either",
@@ -2522,6 +2520,15 @@ dependencies = [
  "uncased",
 ]
 
+[[package]]
+name = "rocket_ws"
+version = "0.1.0-rc.3"
+source = "git+https://github.com/SergioBenitez/Rocket?rev=a82508b403420bd941c32ddec3ee3e4875f2b8a5#a82508b403420bd941c32ddec3ee3e4875f2b8a5"
+dependencies = [
+ "rocket",
+ "tokio-tungstenite",
+]
+
 [[package]]
 name = "rpassword"
 version = "7.2.0"
@@ -2545,28 +2552,28 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.37.6"
+version = "0.37.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d097081ed288dfe45699b72f5b5d648e5f15d64d900c7080273baa20c16a6849"
+checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77"
 dependencies = [
  "bitflags 1.3.2",
  "errno",
  "io-lifetimes",
  "libc",
  "linux-raw-sys",
- "windows-sys 0.45.0",
+ "windows-sys 0.48.0",
 ]
 
 [[package]]
 name = "rustls"
-version = "0.20.8"
+version = "0.21.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
+checksum = "07180898a28ed6a7f7ba2311594308f595e3dd2e3c3812fa0a80a47b45f17e5d"
 dependencies = [
  "log",
  "ring",
+ "rustls-webpki",
  "sct",
- "webpki",
 ]
 
 [[package]]
@@ -2578,6 +2585,16 @@ dependencies = [
  "base64 0.21.0",
 ]
 
+[[package]]
+name = "rustls-webpki"
+version = "0.100.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
 [[package]]
 name = "rustversion"
 version = "1.0.12"
@@ -2839,9 +2856,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
 
 [[package]]
 name = "spin"
-version = "0.9.7"
+version = "0.9.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0959fd6f767df20b231736396e4f602171e00d95205676286e79d4a4eb67bef"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
 
 [[package]]
 name = "stable-pattern"
@@ -3055,13 +3072,12 @@ dependencies = [
 
 [[package]]
 name = "tokio-rustls"
-version = "0.23.4"
+version = "0.24.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
+checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5"
 dependencies = [
  "rustls",
  "tokio",
- "webpki",
 ]
 
 [[package]]
@@ -3363,9 +3379,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
 
 [[package]]
 name = "uuid"
-version = "1.3.0"
+version = "1.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
+checksum = "5b55a3fef2a1e3b3a00ce878640918820d3c51081576ac657d23af9fc7928fdb"
 dependencies = [
  "getrandom",
 ]
@@ -3413,7 +3429,6 @@ dependencies = [
  "job_scheduler_ng",
  "jsonwebtoken",
  "lettre",
- "libmimalloc-sys",
  "libsqlite3-sys",
  "log",
  "mimalloc",
@@ -3430,6 +3445,7 @@ dependencies = [
  "ring",
  "rmpv",
  "rocket",
+ "rocket_ws",
  "rpassword",
  "semver",
  "serde",
@@ -3605,16 +3621,6 @@ dependencies = [
  "url",
 ]
 
-[[package]]
-name = "webpki"
-version = "0.22.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
-dependencies = [
- "ring",
- "untrusted",
-]
-
 [[package]]
 name = "which"
 version = "4.4.0"
@@ -3669,16 +3675,16 @@ version = "0.44.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b"
 dependencies = [
- "windows-targets",
+ "windows-targets 0.42.2",
 ]
 
 [[package]]
 name = "windows"
-version = "0.46.0"
+version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25"
+checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
 dependencies = [
- "windows-targets",
+ "windows-targets 0.48.0",
 ]
 
 [[package]]
@@ -3687,13 +3693,13 @@ version = "0.42.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
 dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
 ]
 
 [[package]]
@@ -3702,7 +3708,16 @@ version = "0.45.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
 dependencies = [
- "windows-targets",
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.0",
 ]
 
 [[package]]
@@ -3711,13 +3726,28 @@ version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
 dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.0",
+ "windows_aarch64_msvc 0.48.0",
+ "windows_i686_gnu 0.48.0",
+ "windows_i686_msvc 0.48.0",
+ "windows_x86_64_gnu 0.48.0",
+ "windows_x86_64_gnullvm 0.48.0",
+ "windows_x86_64_msvc 0.48.0",
 ]
 
 [[package]]
@@ -3726,42 +3756,84 @@ version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
 
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
 
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+
 [[package]]
 name = "windows_i686_gnu"
 version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
 
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+
 [[package]]
 name = "windows_i686_msvc"
 version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
 
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
 
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+
 [[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
 
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.42.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
 
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+
 [[package]]
 name = "winreg"
 version = "0.10.1"
diff --git a/Cargo.toml b/Cargo.toml
index 4a5e6893..a5167a26 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -56,6 +56,8 @@ num-derive = "0.3.3"
 
 # Web framework
 rocket = { version = "0.5.0-rc.3", features = ["tls", "json"], default-features = false }
+# rocket_ws = { version ="0.1.0-rc.3" }
+rocket_ws = { git = 'https://github.com/SergioBenitez/Rocket', rev = "a82508b403420bd941c32ddec3ee3e4875f2b8a5" }
 
 # WebSockets libraries
 tokio-tungstenite = "0.18.0"
@@ -85,11 +87,11 @@ rand = { version = "0.8.5", features = ["small_rng"] }
 ring = "0.16.20"
 
 # UUID generation
-uuid = { version = "1.3.0", features = ["v4"] }
+uuid = { version = "1.3.1", features = ["v4"] }
 
 # Date and time libraries
 chrono = { version = "0.4.24", features = ["clock", "serde"], default-features = false }
-chrono-tz = "0.8.1"
+chrono-tz = "0.8.2"
 time = "0.3.20"
 
 # Job scheduler
@@ -114,7 +116,7 @@ webauthn-rs = "0.3.2"
 url = "2.3.1"
 
 # Email libraries
-lettre = { version = "0.10.3", features = ["smtp-transport", "sendmail-transport", "builder", "serde", "tokio1-native-tls", "hostname", "tracing", "tokio1"], default-features = false }
+lettre = { version = "0.10.4", features = ["smtp-transport", "sendmail-transport", "builder", "serde", "tokio1-native-tls", "hostname", "tracing", "tokio1"], default-features = false }
 percent-encoding = "2.2.0" # URL encoding library used for URL's in the emails
 email_address = "0.2.4"
 
@@ -138,7 +140,7 @@ cookie = "0.16.2"
 cookie_store = "0.19.0"
 
 # Used by U2F, JWT and PostgreSQL
-openssl = "0.10.48"
+openssl = "0.10.50"
 
 # CLI argument parsing
 pico-args = "0.5.0"
@@ -152,8 +154,7 @@ semver = "1.0.17"
 
 # Allow overriding the default memory allocator
 # Mainly used for the musl builds, since the default musl malloc is very slow
-mimalloc = { version = "=0.1.34", features = ["secure"], default-features = false, optional = true }
-libmimalloc-sys = "=0.1.30"
+mimalloc = { version = "0.1.36", features = ["secure"], default-features = false, optional = true }
 which = "4.4.0"
 
 # Argon2 library with support for the PHC format
@@ -162,6 +163,10 @@ argon2 = "0.5.0"
 # Reading a password from the cli for generating the Argon2id ADMIN_TOKEN
 rpassword = "7.2.0"
 
+[patch.crates-io]
+rocket = { git = 'https://github.com/SergioBenitez/Rocket', rev = 'a82508b403420bd941c32ddec3ee3e4875f2b8a5' }
+# rocket_ws = { git = 'https://github.com/SergioBenitez/Rocket', rev = 'a82508b403420bd941c32ddec3ee3e4875f2b8a5' }
+
 # Strip debuginfo from the release builds
 # Also enable thin LTO for some optimizations
 [profile.release]
diff --git a/src/api/notifications.rs b/src/api/notifications.rs
index 2c96f0e4..22c3bf1b 100644
--- a/src/api/notifications.rs
+++ b/src/api/notifications.rs
@@ -1,16 +1,15 @@
 use std::{
-    net::SocketAddr,
-    sync::{
-        atomic::{AtomicBool, Ordering},
-        Arc,
-    },
+    net::{IpAddr, SocketAddr},
+    sync::Arc,
     time::Duration,
 };
 
 use chrono::NaiveDateTime;
-use futures::{SinkExt, StreamExt};
 use rmpv::Value;
-use rocket::Route;
+use rocket::{
+    futures::{SinkExt, StreamExt},
+    Route,
+};
 use tokio::{
     net::{TcpListener, TcpStream},
     sync::mpsc::Sender,
@@ -21,34 +20,127 @@ use tokio_tungstenite::{
 };
 
 use crate::{
-    api::EmptyResult,
-    db::models::{Cipher, Folder, Send, User},
+    auth::ClientIp,
+    db::models::{Cipher, Folder, Send as DbSend, User},
     Error, CONFIG,
 };
 
+use once_cell::sync::Lazy;
+
+static WS_USERS: Lazy<Arc<WebSocketUsers>> = Lazy::new(|| {
+    Arc::new(WebSocketUsers {
+        map: Arc::new(dashmap::DashMap::new()),
+    })
+});
+
 pub fn routes() -> Vec<Route> {
-    routes![websockets_err]
+    routes![websockets_hub]
 }
 
-#[get("/hub")]
-fn websockets_err() -> EmptyResult {
-    static SHOW_WEBSOCKETS_MSG: AtomicBool = AtomicBool::new(true);
+#[derive(FromForm, Debug)]
+struct WsAccessToken {
+    access_token: Option<String>,
+}
 
-    if CONFIG.websocket_enabled()
-        && SHOW_WEBSOCKETS_MSG.compare_exchange(true, false, Ordering::Relaxed, Ordering::Relaxed).is_ok()
-    {
-        err!(
-            "
-    ###########################################################
-    '/notifications/hub' should be proxied to the websocket server or notifications won't work.
-    Go to the Wiki for more info, or disable WebSockets setting WEBSOCKET_ENABLED=false.
-    ###########################################################################################\n"
-        )
-    } else {
-        Err(Error::empty())
+struct WSEntryMapGuard {
+    users: Arc<WebSocketUsers>,
+    user_uuid: String,
+    entry_uuid: uuid::Uuid,
+    addr: IpAddr,
+}
+
+impl WSEntryMapGuard {
+    fn new(users: Arc<WebSocketUsers>, user_uuid: String, entry_uuid: uuid::Uuid, addr: IpAddr) -> Self {
+        Self {
+            users,
+            user_uuid,
+            entry_uuid,
+            addr,
+        }
     }
 }
 
+impl Drop for WSEntryMapGuard {
+    fn drop(&mut self) {
+        info!("Closing WS connection from {}", self.addr);
+        if let Some(mut entry) = self.users.map.get_mut(&self.user_uuid) {
+            entry.retain(|(uuid, _)| uuid != &self.entry_uuid);
+        }
+    }
+}
+
+#[get("/hub?<data..>")]
+async fn websockets_hub<'r>(
+    ws: rocket_ws::WebSocket,
+    data: WsAccessToken,
+    ip: ClientIp,
+) -> Result<rocket_ws::Channel<'r>, Error> {
+    let addr = ip.ip;
+    info!("Accepting Rocket WS connection from {addr}");
+
+    let Some(token) = data.access_token else { err_code!("Invalid claim", 401) };
+    let Ok(claims) = crate::auth::decode_login(&token) else { err_code!("Invalid token", 401) };
+
+    let (mut rx, guard) = {
+        let users = Arc::clone(&WS_USERS);
+
+        // Add a channel to send messages to this client to the map
+        let entry_uuid = uuid::Uuid::new_v4();
+        let (tx, rx) = tokio::sync::mpsc::channel::<Message>(100);
+        users.map.entry(claims.sub.clone()).or_default().push((entry_uuid, tx));
+
+        // Once the guard goes out of scope, the connection will have been closed and the entry will be deleted from the map
+        (rx, WSEntryMapGuard::new(users, claims.sub, entry_uuid, addr))
+    };
+
+    Ok(ws.channel(move |mut stream| {
+        Box::pin(async move {
+            // Make sure the guard is moved into the channel future so it's not dropped earlier
+            let _guard = guard;
+            let mut interval = tokio::time::interval(Duration::from_secs(15));
+            loop {
+                tokio::select! {
+                    res = stream.next() =>  {
+                        match res {
+                            Some(Ok(message)) => {
+                                match message {
+                                    // Respond to any pings
+                                    Message::Ping(ping) => stream.send(Message::Pong(ping)).await?,
+                                    Message::Pong(_) => {/* Ignored */},
+
+                                    // We should receive an initial message with the protocol and version, and we will reply to it
+                                    Message::Text(ref message) => {
+                                        let msg = message.strip_suffix(RECORD_SEPARATOR as char).unwrap_or(message);
+
+                                        if serde_json::from_str(msg).ok() == Some(INITIAL_MESSAGE) {
+                                            stream.send(Message::binary(INITIAL_RESPONSE)).await?;
+                                            continue;
+                                        }
+                                    }
+                                    // Just echo anything else the client sends
+                                    _ => stream.send(message).await?,
+                                }
+                            }
+                            _ => break,
+                        }
+                    }
+
+                    res = rx.recv() => {
+                        match res {
+                            Some(res) => stream.send(res).await?,
+                            None => break,
+                        }
+                    }
+
+                    _ = interval.tick() => stream.send(Message::Ping(create_ping())).await?
+                }
+            }
+
+            Ok(())
+        })
+    }))
+}
+
 //
 // Websockets server
 //
@@ -127,8 +219,8 @@ impl WebSocketUsers {
     async fn send_update(&self, user_uuid: &str, data: &[u8]) {
         if let Some(user) = self.map.get(user_uuid).map(|v| v.clone()) {
             for (_, sender) in user.iter() {
-                if sender.send(Message::binary(data)).await.is_err() {
-                    // TODO: Delete from map here too?
+                if let Err(e) = sender.send(Message::binary(data)).await {
+                    error!("Error sending WS update {e}");
                 }
             }
         }
@@ -196,7 +288,7 @@ impl WebSocketUsers {
         }
     }
 
-    pub async fn send_send_update(&self, ut: UpdateType, send: &Send, user_uuids: &[String]) {
+    pub async fn send_send_update(&self, ut: UpdateType, send: &DbSend, user_uuids: &[String]) {
         let user_uuid = convert_option(send.user_uuid.clone());
 
         let data = create_update(
@@ -280,15 +372,12 @@ pub enum UpdateType {
     None = 100,
 }
 
-pub type Notify<'a> = &'a rocket::State<WebSocketUsers>;
-
-pub fn start_notification_server() -> WebSocketUsers {
-    let users = WebSocketUsers {
-        map: Arc::new(dashmap::DashMap::new()),
-    };
+pub type Notify<'a> = &'a rocket::State<Arc<WebSocketUsers>>;
 
+pub fn start_notification_server() -> Arc<WebSocketUsers> {
+    let users = Arc::clone(&WS_USERS);
     if CONFIG.websocket_enabled() {
-        let users2 = users.clone();
+        let users2 = Arc::<WebSocketUsers>::clone(&users);
         tokio::spawn(async move {
             let addr = (CONFIG.websocket_address(), CONFIG.websocket_port());
             info!("Starting WebSockets server on {}:{}", addr.0, addr.1);
@@ -300,7 +389,7 @@ pub fn start_notification_server() -> WebSocketUsers {
             loop {
                 tokio::select! {
                     Ok((stream, addr)) = listener.accept() => {
-                        tokio::spawn(handle_connection(stream, users2.clone(), addr));
+                        tokio::spawn(handle_connection(stream, Arc::<WebSocketUsers>::clone(&users2), addr));
                     }
 
                     _ = &mut shutdown_rx => {
@@ -316,7 +405,7 @@ pub fn start_notification_server() -> WebSocketUsers {
     users
 }
 
-async fn handle_connection(stream: TcpStream, users: WebSocketUsers, addr: SocketAddr) -> Result<(), Error> {
+async fn handle_connection(stream: TcpStream, users: Arc<WebSocketUsers>, addr: SocketAddr) -> Result<(), Error> {
     let mut user_uuid: Option<String> = None;
 
     info!("Accepting WS connection from {addr}");
@@ -336,41 +425,39 @@ async fn handle_connection(stream: TcpStream, users: WebSocketUsers, addr: Socke
 
     let user_uuid = user_uuid.expect("User UUID should be set after the handshake");
 
-    // Add a channel to send messages to this client to the map
-    let entry_uuid = uuid::Uuid::new_v4();
-    let (tx, mut rx) = tokio::sync::mpsc::channel(100);
-    users.map.entry(user_uuid.clone()).or_default().push((entry_uuid, tx));
+    let (mut rx, guard) = {
+        // Add a channel to send messages to this client to the map
+        let entry_uuid = uuid::Uuid::new_v4();
+        let (tx, rx) = tokio::sync::mpsc::channel::<Message>(100);
+        users.map.entry(user_uuid.clone()).or_default().push((entry_uuid, tx));
 
+        // Once the guard goes out of scope, the connection will have been closed and the entry will be deleted from the map
+        (rx, WSEntryMapGuard::new(users, user_uuid, entry_uuid, addr.ip()))
+    };
+
+    let _guard = guard;
     let mut interval = tokio::time::interval(Duration::from_secs(15));
     loop {
         tokio::select! {
             res = stream.next() =>  {
                 match res {
                     Some(Ok(message)) => {
-                        // Respond to any pings
-                        if let Message::Ping(ping) = message {
-                            if stream.send(Message::Pong(ping)).await.is_err() {
-                                break;
+                        match message {
+                            // Respond to any pings
+                            Message::Ping(ping) => stream.send(Message::Pong(ping)).await?,
+                            Message::Pong(_) => {/* Ignored */},
+
+                            // We should receive an initial message with the protocol and version, and we will reply to it
+                            Message::Text(ref message) => {
+                                let msg = message.strip_suffix(RECORD_SEPARATOR as char).unwrap_or(message);
+
+                                if serde_json::from_str(msg).ok() == Some(INITIAL_MESSAGE) {
+                                    stream.send(Message::binary(INITIAL_RESPONSE)).await?;
+                                    continue;
+                                }
                             }
-                            continue;
-                        } else if let Message::Pong(_) = message {
-                            /* Ignored */
-                            continue;
-                        }
-
-                        // We should receive an initial message with the protocol and version, and we will reply to it
-                        if let Message::Text(ref message) = message {
-                            let msg = message.strip_suffix(RECORD_SEPARATOR as char).unwrap_or(message);
-
-                            if serde_json::from_str(msg).ok() == Some(INITIAL_MESSAGE) {
-                                stream.send(Message::binary(INITIAL_RESPONSE)).await?;
-                                continue;
-                            }
-                        }
-
-                        // Just echo anything else the client sends
-                        if stream.send(message).await.is_err() {
-                            break;
+                            // Just echo anything else the client sends
+                            _ => stream.send(message).await?,
                         }
                     }
                     _ => break,
@@ -379,27 +466,15 @@ async fn handle_connection(stream: TcpStream, users: WebSocketUsers, addr: Socke
 
             res = rx.recv() => {
                 match res {
-                    Some(res) => {
-                        if stream.send(res).await.is_err() {
-                            break;
-                        }
-                    },
+                    Some(res) => stream.send(res).await?,
                     None => break,
                 }
             }
 
-            _= interval.tick() => {
-                if stream.send(Message::Ping(create_ping())).await.is_err() {
-                    break;
-                }
-            }
+            _ = interval.tick() => stream.send(Message::Ping(create_ping())).await?
         }
     }
 
-    info!("Closing WS connection from {addr}");
-
-    //  Delete from map
-    users.map.entry(user_uuid).or_default().retain(|(uuid, _)| uuid != &entry_uuid);
     Ok(())
 }