mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2026-03-23 02:09:20 -07:00
Optimizations and build speedup (#6339)
* Optimizations and build speedup With this commit I have changed several components to be more efficient. This can be less llvm-lines generated or less `clone()` calls. ### Config - Re-ordered the `make_config` macro to be more efficient - Created a custom Deserializer for `ConfigBuilder` less code and more efficient - Use struct's for the `prepare_json` function instead of generating a custom JSON object. This generates less code and is more efficient. - Updated the `get_support_string` function to handle the masking differently. This generates less code and also was able to remove some sub-macro-calls ### Error - Added an extra new call to prevent duplicate Strings in generated macro code. This generated less llvm-lines and seems to be more efficient. - Created a custom Serializer for `ApiError` and `CompactApiError` This makes that struct smaller in size, so better for memory, but also less llvm-lines. ### General - Removed `once_lock` and replace it all with Rust's std LazyLock - Added and fixed some Clippy lints which reduced `clone()` calls for example. - Updated build profiles for more efficiency Also added a new profile specifically for CI, which should decrease the build check - Updated several GitHub Workflows for better security and use the new `ci` build profile - Updated to Rust v1.90.0 which uses a new linker `rust-lld` which should help in faster building - Updated the Cargo.toml for all crates to better use the `workspace` variables - Added a `typos` Workflow and Pre-Commit, which should help in detecting spell error's. Also fixed a few found by it. Signed-off-by: BlackDex <black.dex@gmail.com> * Fix release profile Signed-off-by: BlackDex <black.dex@gmail.com> * Update typos and remove mimalloc check from pre-commit checks Signed-off-by: BlackDex <black.dex@gmail.com> * Misc fixes and updated typos Signed-off-by: BlackDex <black.dex@gmail.com> * Update crates and workflows Signed-off-by: BlackDex <black.dex@gmail.com> * Fix formating and pre-commit Signed-off-by: BlackDex <black.dex@gmail.com> * Update to Rust v1.91 and update crates Signed-off-by: BlackDex <black.dex@gmail.com> * Update web-vault to v2025.10.1 and xx to v1.8.0 Signed-off-by: BlackDex <black.dex@gmail.com> --------- Signed-off-by: BlackDex <black.dex@gmail.com>
This commit is contained in:
committed by
GitHub
parent
8d30285160
commit
9017ca265a
@@ -1,17 +1,16 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use reqwest::Method;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::Value;
|
||||
use std::env;
|
||||
use std::{env, sync::LazyLock};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use reqwest::Method;
|
||||
use rocket::{
|
||||
form::Form,
|
||||
http::{Cookie, CookieJar, MediaType, SameSite, Status},
|
||||
request::{FromRequest, Outcome, Request},
|
||||
response::{content::RawHtml as Html, Redirect},
|
||||
serde::json::Json,
|
||||
Catcher, Route,
|
||||
};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
api::{
|
||||
@@ -82,7 +81,7 @@ pub fn catchers() -> Vec<Catcher> {
|
||||
}
|
||||
}
|
||||
|
||||
static DB_TYPE: Lazy<&str> = Lazy::new(|| match ACTIVE_DB_TYPE.get() {
|
||||
static DB_TYPE: LazyLock<&str> = LazyLock::new(|| match ACTIVE_DB_TYPE.get() {
|
||||
#[cfg(mysql)]
|
||||
Some(DbConnType::Mysql) => "MySQL",
|
||||
#[cfg(postgresql)]
|
||||
@@ -93,9 +92,10 @@ static DB_TYPE: Lazy<&str> = Lazy::new(|| match ACTIVE_DB_TYPE.get() {
|
||||
});
|
||||
|
||||
#[cfg(sqlite)]
|
||||
static CAN_BACKUP: Lazy<bool> = Lazy::new(|| ACTIVE_DB_TYPE.get().map(|t| *t == DbConnType::Sqlite).unwrap_or(false));
|
||||
static CAN_BACKUP: LazyLock<bool> =
|
||||
LazyLock::new(|| ACTIVE_DB_TYPE.get().map(|t| *t == DbConnType::Sqlite).unwrap_or(false));
|
||||
#[cfg(not(sqlite))]
|
||||
static CAN_BACKUP: Lazy<bool> = Lazy::new(|| false);
|
||||
static CAN_BACKUP: LazyLock<bool> = LazyLock::new(|| false);
|
||||
|
||||
#[get("/")]
|
||||
fn admin_disabled() -> &'static str {
|
||||
@@ -157,10 +157,10 @@ fn admin_login(request: &Request<'_>) -> ApiResult<Html<String>> {
|
||||
err_code!("Authorization failed.", Status::Unauthorized.code);
|
||||
}
|
||||
let redirect = request.segments::<std::path::PathBuf>(0..).unwrap_or_default().display().to_string();
|
||||
render_admin_login(None, Some(redirect))
|
||||
render_admin_login(None, Some(&redirect))
|
||||
}
|
||||
|
||||
fn render_admin_login(msg: Option<&str>, redirect: Option<String>) -> ApiResult<Html<String>> {
|
||||
fn render_admin_login(msg: Option<&str>, redirect: Option<&str>) -> ApiResult<Html<String>> {
|
||||
// If there is an error, show it
|
||||
let msg = msg.map(|msg| format!("Error: {msg}"));
|
||||
let json = json!({
|
||||
@@ -194,14 +194,17 @@ fn post_admin_login(
|
||||
if crate::ratelimit::check_limit_admin(&ip.ip).is_err() {
|
||||
return Err(AdminResponse::TooManyRequests(render_admin_login(
|
||||
Some("Too many requests, try again later."),
|
||||
redirect,
|
||||
redirect.as_deref(),
|
||||
)));
|
||||
}
|
||||
|
||||
// If the token is invalid, redirect to login page
|
||||
if !_validate_token(&data.token) {
|
||||
error!("Invalid admin token. IP: {}", ip.ip);
|
||||
Err(AdminResponse::Unauthorized(render_admin_login(Some("Invalid admin token, please try again."), redirect)))
|
||||
Err(AdminResponse::Unauthorized(render_admin_login(
|
||||
Some("Invalid admin token, please try again."),
|
||||
redirect.as_deref(),
|
||||
)))
|
||||
} else {
|
||||
// If the token received is valid, generate JWT and save it as a cookie
|
||||
let claims = generate_admin_claims();
|
||||
@@ -308,7 +311,7 @@ async fn invite_user(data: Json<InviteData>, _token: AdminToken, conn: DbConn) -
|
||||
err_code!("User already exists", Status::Conflict.code)
|
||||
}
|
||||
|
||||
let mut user = User::new(data.email, None);
|
||||
let mut user = User::new(&data.email, None);
|
||||
|
||||
async fn _generate_invite(user: &User, conn: &DbConn) -> EmptyResult {
|
||||
if CONFIG.mail_enabled() {
|
||||
@@ -825,11 +828,7 @@ impl<'r> FromRequest<'r> for AdminToken {
|
||||
_ => err_handler!("Error getting Client IP"),
|
||||
};
|
||||
|
||||
if CONFIG.disable_admin_token() {
|
||||
Outcome::Success(Self {
|
||||
ip,
|
||||
})
|
||||
} else {
|
||||
if !CONFIG.disable_admin_token() {
|
||||
let cookies = request.cookies();
|
||||
|
||||
let access_token = match cookies.get(COOKIE_NAME) {
|
||||
@@ -853,10 +852,10 @@ impl<'r> FromRequest<'r> for AdminToken {
|
||||
error!("Invalid or expired admin JWT. IP: {}.", &ip.ip);
|
||||
return Outcome::Error((Status::Unauthorized, "Session expired"));
|
||||
}
|
||||
|
||||
Outcome::Success(Self {
|
||||
ip,
|
||||
})
|
||||
}
|
||||
|
||||
Outcome::Success(Self {
|
||||
ip,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ pub async fn _register(data: Json<RegisterData>, email_verification: bool, conn:
|
||||
|| CONFIG.is_signup_allowed(&email)
|
||||
|| pending_emergency_access.is_some()
|
||||
{
|
||||
User::new(email.clone(), None)
|
||||
User::new(&email, None)
|
||||
} else {
|
||||
err!("Registration not allowed or user already exists")
|
||||
}
|
||||
@@ -295,7 +295,7 @@ pub async fn _register(data: Json<RegisterData>, email_verification: bool, conn:
|
||||
// Make sure we don't leave a lingering invitation.
|
||||
Invitation::take(&email, &conn).await;
|
||||
|
||||
set_kdf_data(&mut user, data.kdf)?;
|
||||
set_kdf_data(&mut user, &data.kdf)?;
|
||||
|
||||
user.set_password(&data.master_password_hash, Some(data.key), true, None);
|
||||
user.password_hint = password_hint;
|
||||
@@ -358,7 +358,7 @@ async fn post_set_password(data: Json<SetPasswordData>, headers: Headers, conn:
|
||||
let password_hint = clean_password_hint(&data.master_password_hint);
|
||||
enforce_password_hint_setting(&password_hint)?;
|
||||
|
||||
set_kdf_data(&mut user, data.kdf)?;
|
||||
set_kdf_data(&mut user, &data.kdf)?;
|
||||
|
||||
user.set_password(
|
||||
&data.master_password_hash,
|
||||
@@ -556,7 +556,7 @@ struct ChangeKdfData {
|
||||
key: String,
|
||||
}
|
||||
|
||||
fn set_kdf_data(user: &mut User, data: KDFData) -> EmptyResult {
|
||||
fn set_kdf_data(user: &mut User, data: &KDFData) -> EmptyResult {
|
||||
if data.kdf == UserKdfType::Pbkdf2 as i32 && data.kdf_iterations < 100_000 {
|
||||
err!("PBKDF2 KDF iterations must be at least 100000.")
|
||||
}
|
||||
@@ -600,7 +600,7 @@ async fn post_kdf(data: Json<ChangeKdfData>, headers: Headers, conn: DbConn, nt:
|
||||
err!("Invalid password")
|
||||
}
|
||||
|
||||
set_kdf_data(&mut user, data.kdf)?;
|
||||
set_kdf_data(&mut user, &data.kdf)?;
|
||||
|
||||
user.set_password(&data.new_master_password_hash, Some(data.key), true, None);
|
||||
let save_result = user.save(&conn).await;
|
||||
@@ -1279,10 +1279,11 @@ async fn rotate_api_key(data: Json<PasswordOrOtpData>, headers: Headers, conn: D
|
||||
|
||||
#[get("/devices/knowndevice")]
|
||||
async fn get_known_device(device: KnownDevice, conn: DbConn) -> JsonResult {
|
||||
let mut result = false;
|
||||
if let Some(user) = User::find_by_mail(&device.email, &conn).await {
|
||||
result = Device::find_by_uuid_and_user(&device.uuid, &user.uuid, &conn).await.is_some();
|
||||
}
|
||||
let result = if let Some(user) = User::find_by_mail(&device.email, &conn).await {
|
||||
Device::find_by_uuid_and_user(&device.uuid, &user.uuid, &conn).await.is_some()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
Ok(Json(json!(result)))
|
||||
}
|
||||
|
||||
|
||||
@@ -1269,7 +1269,7 @@ async fn save_attachment(
|
||||
attachment.save(&conn).await.expect("Error saving attachment");
|
||||
}
|
||||
|
||||
save_temp_file(PathType::Attachments, &format!("{cipher_id}/{file_id}"), data.data, true).await?;
|
||||
save_temp_file(&PathType::Attachments, &format!("{cipher_id}/{file_id}"), data.data, true).await?;
|
||||
|
||||
nt.send_cipher_update(
|
||||
UpdateType::SyncCipherUpdate,
|
||||
|
||||
@@ -245,7 +245,7 @@ async fn send_invite(data: Json<EmergencyAccessInviteData>, headers: Headers, co
|
||||
invitation.save(&conn).await?;
|
||||
}
|
||||
|
||||
let mut user = User::new(email.clone(), None);
|
||||
let mut user = User::new(&email, None);
|
||||
user.save(&conn).await?;
|
||||
(user, true)
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ async fn create_organization(headers: Headers, data: Json<OrgData>, conn: DbConn
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let org = Organization::new(data.name, data.billing_email, private_key, public_key);
|
||||
let org = Organization::new(data.name, &data.billing_email, private_key, public_key);
|
||||
let mut member = Membership::new(headers.user.uuid, org.uuid.clone(), None);
|
||||
let collection = Collection::new(org.uuid.clone(), data.collection_name, None);
|
||||
|
||||
@@ -1124,7 +1124,7 @@ async fn send_invite(
|
||||
Invitation::new(email).save(&conn).await?;
|
||||
}
|
||||
|
||||
let mut new_user = User::new(email.clone(), None);
|
||||
let mut new_user = User::new(email, None);
|
||||
new_user.save(&conn).await?;
|
||||
user_created = true;
|
||||
new_user
|
||||
@@ -1591,7 +1591,7 @@ async fn edit_member(
|
||||
// HACK: We need the raw user-type to be sure custom role is selected to determine the access_all permission
|
||||
// The from_str() will convert the custom role type into a manager role type
|
||||
let raw_type = &data.r#type.into_string();
|
||||
// MembershipTyp::from_str will convert custom (4) to manager (3)
|
||||
// MembershipType::from_str will convert custom (4) to manager (3)
|
||||
let Some(new_type) = MembershipType::from_str(raw_type) else {
|
||||
err!("Invalid type")
|
||||
};
|
||||
|
||||
@@ -94,7 +94,7 @@ async fn ldap_import(data: Json<OrgImportData>, token: PublicToken, conn: DbConn
|
||||
Some(user) => user, // exists in vaultwarden
|
||||
None => {
|
||||
// User does not exist yet
|
||||
let mut new_user = User::new(user_data.email.clone(), None);
|
||||
let mut new_user = User::new(&user_data.email, None);
|
||||
new_user.save(&conn).await?;
|
||||
|
||||
if !CONFIG.mail_enabled() {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use std::{path::Path, sync::LazyLock, time::Duration};
|
||||
|
||||
use chrono::{DateTime, TimeDelta, Utc};
|
||||
use num_traits::ToPrimitive;
|
||||
use once_cell::sync::Lazy;
|
||||
use rocket::form::Form;
|
||||
use rocket::fs::NamedFile;
|
||||
use rocket::fs::TempFile;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::{
|
||||
form::Form,
|
||||
fs::{NamedFile, TempFile},
|
||||
serde::json::Json,
|
||||
};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
@@ -23,7 +22,7 @@ use crate::{
|
||||
};
|
||||
|
||||
const SEND_INACCESSIBLE_MSG: &str = "Send does not exist or is no longer available";
|
||||
static ANON_PUSH_DEVICE: Lazy<Device> = Lazy::new(|| {
|
||||
static ANON_PUSH_DEVICE: LazyLock<Device> = LazyLock::new(|| {
|
||||
let dt = crate::util::parse_date("1970-01-01T00:00:00.000000Z");
|
||||
Device {
|
||||
uuid: String::from("00000000-0000-0000-0000-000000000000").into(),
|
||||
@@ -274,7 +273,7 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo
|
||||
|
||||
let file_id = crate::crypto::generate_send_file_id();
|
||||
|
||||
save_temp_file(PathType::Sends, &format!("{}/{file_id}", send.uuid), data, true).await?;
|
||||
save_temp_file(&PathType::Sends, &format!("{}/{file_id}", send.uuid), data, true).await?;
|
||||
|
||||
let mut data_value: Value = serde_json::from_str(&send.data)?;
|
||||
if let Some(o) = data_value.as_object_mut() {
|
||||
@@ -426,7 +425,7 @@ async fn post_send_file_v2_data(
|
||||
|
||||
let file_path = format!("{send_id}/{file_id}");
|
||||
|
||||
save_temp_file(PathType::Sends, &file_path, data.data, false).await?;
|
||||
save_temp_file(&PathType::Sends, &file_path, data.data, false).await?;
|
||||
|
||||
nt.send_send_update(
|
||||
UpdateType::SyncSendCreate,
|
||||
@@ -567,7 +566,7 @@ async fn post_access_file(
|
||||
}
|
||||
|
||||
async fn download_url(host: &Host, send_id: &SendId, file_id: &SendFileId) -> Result<String, crate::Error> {
|
||||
let operator = CONFIG.opendal_operator_for_path_type(PathType::Sends)?;
|
||||
let operator = CONFIG.opendal_operator_for_path_type(&PathType::Sends)?;
|
||||
|
||||
if operator.info().scheme() == opendal::Scheme::Fs {
|
||||
let token_claims = crate::auth::generate_send_claims(send_id, file_id);
|
||||
|
||||
@@ -31,7 +31,7 @@ async fn generate_authenticator(data: Json<PasswordOrOtpData>, headers: Headers,
|
||||
|
||||
let (enabled, key) = match twofactor {
|
||||
Some(tf) => (true, tf.data),
|
||||
_ => (false, crypto::encode_random_bytes::<20>(BASE32)),
|
||||
_ => (false, crypto::encode_random_bytes::<20>(&BASE32)),
|
||||
};
|
||||
|
||||
// Upstream seems to also return `userVerificationToken`, but doesn't seem to be used at all.
|
||||
|
||||
@@ -126,7 +126,7 @@ async fn recover(data: Json<RecoverTwoFactor>, client_headers: ClientHeaders, co
|
||||
|
||||
async fn _generate_recover_code(user: &mut User, conn: &DbConn) {
|
||||
if user.totp_recover.is_none() {
|
||||
let totp_recover = crypto::encode_random_bytes::<20>(BASE32);
|
||||
let totp_recover = crypto::encode_random_bytes::<20>(&BASE32);
|
||||
user.totp_recover = Some(totp_recover);
|
||||
user.save(conn).await.ok();
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::IpAddr,
|
||||
sync::Arc,
|
||||
sync::{Arc, LazyLock},
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::{stream::StreamExt, TryFutureExt};
|
||||
use once_cell::sync::Lazy;
|
||||
use html5gum::{Emitter, HtmlString, Readable, StringReader, Tokenizer};
|
||||
use regex::Regex;
|
||||
use reqwest::{
|
||||
header::{self, HeaderMap, HeaderValue},
|
||||
@@ -16,8 +16,6 @@ use reqwest::{
|
||||
use rocket::{http::ContentType, response::Redirect, Route};
|
||||
use svg_hush::{data_url_filter, Filter};
|
||||
|
||||
use html5gum::{Emitter, HtmlString, Readable, StringReader, Tokenizer};
|
||||
|
||||
use crate::{
|
||||
config::PathType,
|
||||
error::Error,
|
||||
@@ -33,7 +31,7 @@ pub fn routes() -> Vec<Route> {
|
||||
}
|
||||
}
|
||||
|
||||
static CLIENT: Lazy<Client> = Lazy::new(|| {
|
||||
static CLIENT: LazyLock<Client> = LazyLock::new(|| {
|
||||
// Generate the default headers
|
||||
let mut default_headers = HeaderMap::new();
|
||||
default_headers.insert(
|
||||
@@ -78,7 +76,7 @@ static CLIENT: Lazy<Client> = Lazy::new(|| {
|
||||
});
|
||||
|
||||
// Build Regex only once since this takes a lot of time.
|
||||
static ICON_SIZE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?x)(\d+)\D*(\d+)").unwrap());
|
||||
static ICON_SIZE_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(?x)(\d+)\D*(\d+)").unwrap());
|
||||
|
||||
// The function name `icon_external` is checked in the `on_response` function in `AppHeaders`
|
||||
// It is used to prevent sending a specific header which breaks icon downloads.
|
||||
@@ -220,7 +218,7 @@ async fn get_cached_icon(path: &str) -> Option<Vec<u8>> {
|
||||
}
|
||||
|
||||
// Try to read the cached icon, and return it if it exists
|
||||
if let Ok(operator) = CONFIG.opendal_operator_for_path_type(PathType::IconCache) {
|
||||
if let Ok(operator) = CONFIG.opendal_operator_for_path_type(&PathType::IconCache) {
|
||||
if let Ok(buf) = operator.read(path).await {
|
||||
return Some(buf.to_vec());
|
||||
}
|
||||
@@ -230,7 +228,7 @@ async fn get_cached_icon(path: &str) -> Option<Vec<u8>> {
|
||||
}
|
||||
|
||||
async fn file_is_expired(path: &str, ttl: u64) -> Result<bool, Error> {
|
||||
let operator = CONFIG.opendal_operator_for_path_type(PathType::IconCache)?;
|
||||
let operator = CONFIG.opendal_operator_for_path_type(&PathType::IconCache)?;
|
||||
let meta = operator.stat(path).await?;
|
||||
let modified =
|
||||
meta.last_modified().ok_or_else(|| std::io::Error::other(format!("No last modified time for `{path}`")))?;
|
||||
@@ -246,7 +244,7 @@ async fn icon_is_negcached(path: &str) -> bool {
|
||||
match expired {
|
||||
// No longer negatively cached, drop the marker
|
||||
Ok(true) => {
|
||||
match CONFIG.opendal_operator_for_path_type(PathType::IconCache) {
|
||||
match CONFIG.opendal_operator_for_path_type(&PathType::IconCache) {
|
||||
Ok(operator) => {
|
||||
if let Err(e) = operator.delete(&miss_indicator).await {
|
||||
error!("Could not remove negative cache indicator for icon {path:?}: {e:?}");
|
||||
@@ -462,8 +460,8 @@ async fn get_page_with_referer(url: &str, referer: &str) -> Result<Response, Err
|
||||
/// priority2 = get_icon_priority("https://example.com/path/to/a/favicon.ico", "");
|
||||
/// ```
|
||||
fn get_icon_priority(href: &str, sizes: &str) -> u8 {
|
||||
static PRIORITY_MAP: Lazy<HashMap<&'static str, u8>> =
|
||||
Lazy::new(|| [(".png", 10), (".jpg", 20), (".jpeg", 20)].into_iter().collect());
|
||||
static PRIORITY_MAP: LazyLock<HashMap<&'static str, u8>> =
|
||||
LazyLock::new(|| [(".png", 10), (".jpg", 20), (".jpeg", 20)].into_iter().collect());
|
||||
|
||||
// Check if there is a dimension set
|
||||
let (width, height) = parse_sizes(sizes);
|
||||
@@ -597,7 +595,7 @@ async fn download_icon(domain: &str) -> Result<(Bytes, Option<&str>), Error> {
|
||||
}
|
||||
|
||||
async fn save_icon(path: &str, icon: Vec<u8>) {
|
||||
let operator = match CONFIG.opendal_operator_for_path_type(PathType::IconCache) {
|
||||
let operator = match CONFIG.opendal_operator_for_path_type(&PathType::IconCache) {
|
||||
Ok(operator) => operator,
|
||||
Err(e) => {
|
||||
warn!("Failed to get OpenDAL operator while saving icon: {e}");
|
||||
|
||||
@@ -248,7 +248,7 @@ async fn _sso_login(
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let mut user = User::new(user_infos.email, user_infos.user_name);
|
||||
let mut user = User::new(&user_infos.email, user_infos.user_name);
|
||||
user.verified_at = Some(now);
|
||||
user.save(conn).await?;
|
||||
|
||||
@@ -1061,7 +1061,7 @@ async fn oidcsignin_redirect(
|
||||
wrapper: impl FnOnce(OIDCState) -> sso::OIDCCodeWrapper,
|
||||
conn: &DbConn,
|
||||
) -> ApiResult<Redirect> {
|
||||
let state = sso::decode_state(base64_state)?;
|
||||
let state = sso::decode_state(&base64_state)?;
|
||||
let code = sso::encode_code_claims(wrapper(state.clone()));
|
||||
|
||||
let nonce = match SsoNonce::find(&state, conn).await {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
use std::{net::IpAddr, sync::Arc, time::Duration};
|
||||
use std::{
|
||||
net::IpAddr,
|
||||
sync::{Arc, LazyLock},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use rmpv::Value;
|
||||
use rocket::{futures::StreamExt, Route};
|
||||
use tokio::sync::mpsc::Sender;
|
||||
|
||||
use rocket_ws::{Message, WebSocket};
|
||||
use tokio::sync::mpsc::Sender;
|
||||
|
||||
use crate::{
|
||||
auth::{ClientIp, WsAccessTokenHeader},
|
||||
@@ -16,15 +19,13 @@ use crate::{
|
||||
Error, CONFIG,
|
||||
};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub static WS_USERS: Lazy<Arc<WebSocketUsers>> = Lazy::new(|| {
|
||||
pub static WS_USERS: LazyLock<Arc<WebSocketUsers>> = LazyLock::new(|| {
|
||||
Arc::new(WebSocketUsers {
|
||||
map: Arc::new(dashmap::DashMap::new()),
|
||||
})
|
||||
});
|
||||
|
||||
pub static WS_ANONYMOUS_SUBSCRIPTIONS: Lazy<Arc<AnonymousWebSocketSubscriptions>> = Lazy::new(|| {
|
||||
pub static WS_ANONYMOUS_SUBSCRIPTIONS: LazyLock<Arc<AnonymousWebSocketSubscriptions>> = LazyLock::new(|| {
|
||||
Arc::new(AnonymousWebSocketSubscriptions {
|
||||
map: Arc::new(dashmap::DashMap::new()),
|
||||
})
|
||||
@@ -35,7 +36,7 @@ use super::{
|
||||
push_send_update, push_user_update,
|
||||
};
|
||||
|
||||
static NOTIFICATIONS_DISABLED: Lazy<bool> = Lazy::new(|| !CONFIG.enable_websocket() && !CONFIG.push_enabled());
|
||||
static NOTIFICATIONS_DISABLED: LazyLock<bool> = LazyLock::new(|| !CONFIG.enable_websocket() && !CONFIG.push_enabled());
|
||||
|
||||
pub fn routes() -> Vec<Route> {
|
||||
if CONFIG.enable_websocket() {
|
||||
@@ -109,8 +110,7 @@ fn websockets_hub<'r>(
|
||||
ip: ClientIp,
|
||||
header_token: WsAccessTokenHeader,
|
||||
) -> Result<rocket_ws::Stream!['r], Error> {
|
||||
let addr = ip.ip;
|
||||
info!("Accepting Rocket WS connection from {addr}");
|
||||
info!("Accepting Rocket WS connection from {}", ip.ip);
|
||||
|
||||
let token = if let Some(token) = data.access_token {
|
||||
token
|
||||
@@ -133,7 +133,7 @@ fn websockets_hub<'r>(
|
||||
users.map.entry(claims.sub.to_string()).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))
|
||||
(rx, WSEntryMapGuard::new(users, claims.sub, entry_uuid, ip.ip))
|
||||
};
|
||||
|
||||
Ok({
|
||||
@@ -189,8 +189,7 @@ fn websockets_hub<'r>(
|
||||
#[allow(tail_expr_drop_order)]
|
||||
#[get("/anonymous-hub?<token..>")]
|
||||
fn anonymous_websockets_hub<'r>(ws: WebSocket, token: String, ip: ClientIp) -> Result<rocket_ws::Stream!['r], Error> {
|
||||
let addr = ip.ip;
|
||||
info!("Accepting Anonymous Rocket WS connection from {addr}");
|
||||
info!("Accepting Anonymous Rocket WS connection from {}", ip.ip);
|
||||
|
||||
let (mut rx, guard) = {
|
||||
let subscriptions = Arc::clone(&WS_ANONYMOUS_SUBSCRIPTIONS);
|
||||
@@ -200,7 +199,7 @@ fn anonymous_websockets_hub<'r>(ws: WebSocket, token: String, ip: ClientIp) -> R
|
||||
subscriptions.map.insert(token.clone(), tx);
|
||||
|
||||
// Once the guard goes out of scope, the connection will have been closed and the entry will be deleted from the map
|
||||
(rx, WSAnonymousEntryMapGuard::new(subscriptions, token, addr))
|
||||
(rx, WSAnonymousEntryMapGuard::new(subscriptions, token, ip.ip))
|
||||
};
|
||||
|
||||
Ok({
|
||||
@@ -257,11 +256,11 @@ fn anonymous_websockets_hub<'r>(ws: WebSocket, token: String, ip: ClientIp) -> R
|
||||
// Websockets server
|
||||
//
|
||||
|
||||
fn serialize(val: Value) -> Vec<u8> {
|
||||
fn serialize(val: &Value) -> Vec<u8> {
|
||||
use rmpv::encode::write_value;
|
||||
|
||||
let mut buf = Vec::new();
|
||||
write_value(&mut buf, &val).expect("Error encoding MsgPack");
|
||||
write_value(&mut buf, val).expect("Error encoding MsgPack");
|
||||
|
||||
// Add size bytes at the start
|
||||
// Extracted from BinaryMessageFormat.js
|
||||
@@ -552,7 +551,7 @@ impl AnonymousWebSocketSubscriptions {
|
||||
let data = create_anonymous_update(
|
||||
vec![("Id".into(), auth_request_id.to_string().into()), ("UserId".into(), user_id.to_string().into())],
|
||||
UpdateType::AuthRequestResponse,
|
||||
user_id.clone(),
|
||||
user_id,
|
||||
);
|
||||
self.send_update(auth_request_id, &data).await;
|
||||
}
|
||||
@@ -588,16 +587,19 @@ fn create_update(payload: Vec<(Value, Value)>, ut: UpdateType, acting_device_id:
|
||||
])]),
|
||||
]);
|
||||
|
||||
serialize(value)
|
||||
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) -> Vec<u8> {
|
||||
use rmpv::Value as V;
|
||||
|
||||
let value = V::Array(vec![
|
||||
1.into(),
|
||||
V::Map(vec![]),
|
||||
V::Nil,
|
||||
// This word is misspelled, but upstream has this too
|
||||
// https://github.com/bitwarden/server/blob/dff9f1cf538198819911cf2c20f8cda3307701c5/src/Notifications/HubHelpers.cs#L86
|
||||
// https://github.com/bitwarden/clients/blob/9612a4ac45063e372a6fbe87eb253c7cb3c588fb/libs/common/src/auth/services/anonymous-hub.service.ts#L45
|
||||
"AuthRequestResponseRecieved".into(),
|
||||
V::Array(vec![V::Map(vec![
|
||||
("Type".into(), (ut as i32).into()),
|
||||
@@ -606,11 +608,11 @@ fn create_anonymous_update(payload: Vec<(Value, Value)>, ut: UpdateType, user_id
|
||||
])]),
|
||||
]);
|
||||
|
||||
serialize(value)
|
||||
serialize(&value)
|
||||
}
|
||||
|
||||
fn create_ping() -> Vec<u8> {
|
||||
serialize(Value::Array(vec![6.into()]))
|
||||
serialize(&Value::Array(vec![6.into()]))
|
||||
}
|
||||
|
||||
// https://github.com/bitwarden/server/blob/375af7c43b10d9da03525d41452f95de3f921541/src/Core/Enums/PushType.cs
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
use std::{
|
||||
sync::LazyLock,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use reqwest::{
|
||||
header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE},
|
||||
Method,
|
||||
@@ -16,9 +21,6 @@ use crate::{
|
||||
CONFIG,
|
||||
};
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AuthPushToken {
|
||||
access_token: String,
|
||||
@@ -32,7 +34,7 @@ struct LocalAuthPushToken {
|
||||
}
|
||||
|
||||
async fn get_auth_api_token() -> ApiResult<String> {
|
||||
static API_TOKEN: Lazy<RwLock<LocalAuthPushToken>> = Lazy::new(|| {
|
||||
static API_TOKEN: LazyLock<RwLock<LocalAuthPushToken>> = LazyLock::new(|| {
|
||||
RwLock::new(LocalAuthPushToken {
|
||||
access_token: String::new(),
|
||||
valid_until: Instant::now(),
|
||||
|
||||
Reference in New Issue
Block a user