commit 0dca43f496f1998dec0e9c68f38966beb671bd81
parent a6bc308abfadeab7f0523a4ab96fb0b42cdb71b7
Author: Zack Newman <zack@philomathiclife.com>
Date: Thu, 3 Apr 2025 13:44:10 -0600
update webauthn, msrv, rust 2024, fix lints
Diffstat:
24 files changed, 244 insertions(+), 237 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -3,39 +3,39 @@ authors = ["Daniel GarcĂa <dani-garcia@users.noreply.github.com>", "Zack Newman
categories = ["api-bindings", "web-programming::http-server"]
description = "Hardened fork of Vaultwarden with fewer features and pledge(2) and unveil(2) support."
documentation = "https://github.com/dani-garcia/vaultwarden/wiki"
-edition = "2021"
+edition = "2024"
keywords = ["password", "vaultwarden"]
license = "AGPL-3.0-only"
name = "vw_small"
publish = false
readme = "README.md"
repository = "https://git.philomathiclife.com/repos/vw_small/"
-rust-version = "1.82.0"
-version = "3.0.0"
+rust-version = "1.86.0"
+version = "3.1.0"
[target.'cfg(target_os = "openbsd")'.dependencies]
priv_sep = { version = "2.1.0", default-features = false, features = ["openbsd"], optional = true }
[dependencies]
-chrono = { version = "0.4.38", default-features = false, features = ["serde"] }
-data-encoding = { version = "2.6.0", default-features = false }
-diesel = { version = "2.2.6", default-features = false, features = ["32-column-tables", "chrono", "r2d2", "sqlite"] }
-jsonwebtoken = { version = "9.3.0", default-features = false, features = ["use_pem"] }
-libsqlite3-sys = { version = "0.30.1", default-features = false, features = ["bundled"] }
-openssl = { version = "0.10.68", default-features = false }
+chrono = { version = "0.4.40", default-features = false, features = ["serde"] }
+data-encoding = { version = "2.8.0", default-features = false }
+diesel = { version = "2.2.8", default-features = false, features = ["32-column-tables", "chrono", "r2d2", "sqlite"] }
+jsonwebtoken = { version = "9.3.1", default-features = false, features = ["use_pem"] }
+libsqlite3-sys = { version = "0.31.0", default-features = false, features = ["bundled"] }
+openssl = { version = "0.10.71", default-features = false }
paste = { version = "1.0.15", default-features = false }
-rand = { version = "0.8.5", default-features = false, features = ["small_rng"] }
-ring = { version = "0.17.8", default-features = false }
+rand = { version = "0.9.0", default-features = false, features = ["small_rng"] }
+ring = { version = "0.17.14", default-features = false }
rocket = { version = "0.5.1", default-features = false, features = ["json", "tls"] }
-semver = { version = "1.0.23", default-features = false }
-serde = { version = "1.0.215", default-features = false }
-serde_json = { version = "1.0.133", default-features = false }
-tokio = { version = "1.42.0", default-features = false }
-toml = { version = "0.8.19", default-features = false, features = ["parse"] }
+semver = { version = "1.0.26", default-features = false }
+serde = { version = "1.0.219", default-features = false }
+serde_json = { version = "1.0.140", default-features = false }
+tokio = { version = "1.44.1", default-features = false }
+toml = { version = "0.8.20", default-features = false, features = ["parse"] }
totp-lite = { version = "2.0.1", default-features = false }
url = { version = "2.5.4", default-features = false }
-uuid = { version = "1.11.0", default-features = false, features = ["v4"] }
-webauthn_rp = { version = "0.2.0", features = ["bin", "custom", "serde_relaxed"] }
+uuid = { version = "1.16.0", default-features = false, features = ["v4"] }
+webauthn_rp = { version = "0.3.0", features = ["bin", "custom", "serde_relaxed"] }
[features]
priv_sep = ["dep:priv_sep"]
diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs
@@ -1,9 +1,9 @@
use crate::{
api::{EmptyResult, JsonResult, PasswordOrOtpData},
- auth::{decode_delete, ClientHeaders, Headers},
+ auth::{ClientHeaders, Headers, decode_delete},
db::{
- models::{Cipher, Device, Folder, User, UserKdfType, UserOrganization},
DbConn,
+ models::{Cipher, Device, Folder, User, UserKdfType, UserOrganization},
},
error::Error,
util::NumberOrString,
@@ -92,16 +92,18 @@ struct KeysData {
}
/// Trims whitespace from password hints, and converts blank password hints to `None`.
-fn clean_password_hint(password_hint: &Option<String>) -> Option<String> {
- password_hint.as_ref().and_then(|h| match h.trim() {
+fn clean_password_hint(password_hint: Option<&str>) -> Option<String> {
+ password_hint.and_then(|h| match h.trim() {
"" => None,
ht => Some(ht.to_owned()),
})
}
-fn enforce_password_hint_setting(password_hint: &Option<String>) -> EmptyResult {
+fn enforce_password_hint_setting(password_hint: Option<&str>) -> EmptyResult {
if password_hint.is_some() {
- err!("Password hints have been disabled by the administrator. Remove the hint and try again.");
+ err!(
+ "Password hints have been disabled by the administrator. Remove the hint and try again."
+ );
}
Ok(())
}
@@ -211,8 +213,8 @@ async fn post_password(data: Json<ChangePassData>, headers: Headers, conn: DbCon
if !user.check_valid_password(&pass_data.master_password_hash) {
err!("Invalid password")
}
- user.password_hint = clean_password_hint(&pass_data.master_password_hint);
- enforce_password_hint_setting(&user.password_hint)?;
+ user.password_hint = clean_password_hint(pass_data.master_password_hint.as_deref());
+ enforce_password_hint_setting(user.password_hint.as_deref())?;
user.set_password(
&pass_data.new_master_password_hash,
Some(pass_data.key),
@@ -628,7 +630,7 @@ impl<'r> FromRequest<'r> for KnownDevice {
return Outcome::Error((
Status::BadRequest,
"X-Request-Email value failed to decode as UTF-8",
- ))
+ ));
}
}
} else {
diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs
@@ -3,21 +3,21 @@ use crate::{
api::{self, EmptyResult, JsonResult, PasswordOrOtpData},
auth::Headers,
db::{
+ DbConn,
models::{
Cipher, Collection, CollectionCipher, CollectionUser, Favorite, Folder, FolderCipher,
OrgPolicy, OrgPolicyType, UserOrgType, UserOrganization,
},
- DbConn,
},
error::Error,
util::NumberOrString,
};
use chrono::{NaiveDateTime, Utc};
use rocket::{
+ Route,
form::{Form, FromForm},
fs::TempFile,
serde::json::Json,
- Route,
};
use serde_json::Value;
use std::collections::{HashMap, HashSet};
@@ -333,7 +333,9 @@ async fn enforce_personal_ownership_policy(
let user_uuid = &headers.user.uuid;
let policy_type = OrgPolicyType::PersonalOwnership;
if OrgPolicy::is_applicable_to_user(user_uuid, policy_type, None, conn).await {
- err!("Due to an Enterprise Policy, you are restricted from saving items to your personal vault.")
+ err!(
+ "Due to an Enterprise Policy, you are restricted from saving items to your personal vault."
+ )
}
}
Ok(())
@@ -356,7 +358,9 @@ pub async fn update_cipher_from_data(
// ISO 8601 format
Err(err) => warn!("Error parsing LastKnownRevisionDate '{}': {}", dt, err),
Ok(dt) if cipher.updated_at.signed_duration_since(dt).num_seconds() > 1 => {
- err!("The client copy of this cipher is out of date. Resync the client and try again.")
+ err!(
+ "The client copy of this cipher is out of date. Resync the client and try again."
+ )
}
Ok(_) => (),
}
@@ -419,7 +423,7 @@ pub async fn update_cipher_from_data(
.for_each(|ref mut f| {
f.as_object_mut().unwrap().remove("Response");
});
- };
+ }
json_data
}
let type_data_opt = match data.r#type {
@@ -887,7 +891,7 @@ async fn share_cipher_by_uuid(
}
}
}
- };
+ }
update_cipher_from_data(
&mut cipher,
data.cipher,
@@ -1263,7 +1267,7 @@ async fn _delete_multiple_ciphers(
for uuid in data.ids {
if let error @ Err(_) = _delete_cipher_by_uuid(&uuid, &headers, &conn, soft_delete).await {
return error;
- };
+ }
}
Ok(())
}
diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs
@@ -1,22 +1,22 @@
use crate::{
api::{
- core::{CipherSyncData, CipherSyncType},
EmptyResult, JsonResult, PasswordOrOtpData,
+ core::{CipherSyncData, CipherSyncType},
},
auth::{self, AdminHeaders, Headers, ManagerHeaders, ManagerHeadersLoose, OwnerHeaders},
db::{
+ DbConn,
models::{
Cipher, Collection, CollectionCipher, CollectionUser, OrgPolicy, OrgPolicyErr,
OrgPolicyType, Organization, TwoFactorType, User, UserOrgStatus, UserOrgType,
UserOrganization,
},
- DbConn,
},
error::Error,
util::{self, NumberOrString},
};
use core::convert;
-use rocket::{http::Status, serde::json::Json, Route};
+use rocket::{Route, http::Status, serde::json::Json};
use serde_json::Value;
use std::collections::{HashMap, HashSet};
@@ -926,10 +926,14 @@ async fn edit_user(
match OrgPolicy::is_user_allowed(&user_to_edit.user_uuid, org_id, true, &conn).await {
Ok(()) => {}
Err(OrgPolicyErr::TwoFactorMissing) => {
- err!("You cannot modify this user to this type because it has no two-step login method activated");
+ err!(
+ "You cannot modify this user to this type because it has no two-step login method activated"
+ );
}
Err(OrgPolicyErr::SingleOrgEnforced) => {
- err!("You cannot modify this user to this type because it is a member of an organization which forbids it");
+ err!(
+ "You cannot modify this user to this type because it is a member of an organization which forbids it"
+ );
}
}
}
@@ -1078,8 +1082,8 @@ async fn bulk_public_keys(
}))
}
-use super::ciphers::update_cipher_from_data;
use super::ciphers::CipherData;
+use super::ciphers::update_cipher_from_data;
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -1225,7 +1229,7 @@ async fn post_bulk_collections(
CollectionCipher::save(&cipher.uuid, collection, &conn).await?;
}
}
- };
+ }
}
Ok(())
@@ -1446,7 +1450,7 @@ async fn import(
Some(user_org) if user_org.atype >= UserOrgType::Admin => { /* Okay, nothing to do */ }
Some(_) => err!("User has insufficient permissions to use Directory Connector"),
None => err!("User not part of organization"),
- };
+ }
for user_data in &data.users {
if user_data.deleted {
// If user is marked for deletion and it exists, delete it
@@ -1676,10 +1680,14 @@ async fn _restore_organization_user(
match OrgPolicy::is_user_allowed(&user_org.user_uuid, org_id, false, conn).await {
Ok(()) => {}
Err(OrgPolicyErr::TwoFactorMissing) => {
- err!("You cannot restore this user because it has no two-step login method activated");
+ err!(
+ "You cannot restore this user because it has no two-step login method activated"
+ );
}
Err(OrgPolicyErr::SingleOrgEnforced) => {
- err!("You cannot restore this user because it is a member of an organization which forbids it");
+ err!(
+ "You cannot restore this user because it is a member of an organization which forbids it"
+ );
}
}
}
@@ -1946,7 +1954,7 @@ async fn get_org_export(org_id: &str, headers: AdminHeaders, conn: DbConn) -> Js
// Therefore, we will check for any version smaller then v2023.1.0 and return a different response.
// If we can't determine the version, we will use the latest default v2023.1.0 and higher.
// https://github.com/bitwarden/server/blob/9ca93381ce416454734418c3a9f99ab49747f1b6/src/Api/Controllers/OrganizationExportController.cs#L44
- let use_list_response_model = headers.client_version.map_or(false, |client_version| {
+ let use_list_response_model = headers.client_version.is_some_and(|client_version| {
let ver_match = VersionReq::parse("<2023.1.0").unwrap();
let client_version = Version::parse(&client_version).unwrap();
ver_match.matches(&client_version)
diff --git a/src/api/core/two_factor/email.rs b/src/api/core/two_factor/email.rs
@@ -1,5 +1,5 @@
use crate::{api::PasswordOrOtpData, auth::Headers, error::Error};
-use rocket::{serde::json::Json, Route};
+use rocket::{Route, serde::json::Json};
pub fn routes() -> Vec<Route> {
routes![email, get_email, send_email, send_email_login]
@@ -8,21 +8,21 @@ pub fn routes() -> Vec<Route> {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct SendEmailLoginData {
- #[allow(dead_code)]
+ #[expect(dead_code, reason = "upstream")]
#[serde(alias = "Email")]
email: String,
- #[allow(dead_code)]
+ #[expect(dead_code, reason = "upstream")]
#[serde(alias = "MasterPasswordHash")]
master_password_hash: String,
}
const EMAIL_DISABLED_MSG: &str = "E-mail is disabled.";
-#[allow(unused_variables, clippy::needless_pass_by_value)]
+#[expect(unused_variables, clippy::needless_pass_by_value, reason = "upstream")]
#[post("/two-factor/send-email-login", data = "<data>")]
fn send_email_login(data: Json<SendEmailLoginData>) -> Error {
Error::new(EMAIL_DISABLED_MSG, EMAIL_DISABLED_MSG)
}
-#[allow(unused_variables, clippy::needless_pass_by_value)]
+#[expect(unused_variables, clippy::needless_pass_by_value, reason = "upstream")]
#[post("/two-factor/get-email", data = "<data>")]
fn get_email(data: Json<PasswordOrOtpData>, _headers: Headers) -> Error {
Error::new(EMAIL_DISABLED_MSG, EMAIL_DISABLED_MSG)
@@ -31,15 +31,15 @@ fn get_email(data: Json<PasswordOrOtpData>, _headers: Headers) -> Error {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct SendEmailData {
- #[allow(dead_code)]
+ #[expect(dead_code, reason = "upstream")]
email: String,
- #[allow(dead_code)]
+ #[expect(dead_code, reason = "upstream")]
master_password_hash: Option<String>,
- #[allow(dead_code)]
+ #[expect(dead_code, reason = "upstream")]
otp: Option<String>,
}
-#[allow(unused_variables, clippy::needless_pass_by_value)]
+#[expect(unused_variables, clippy::needless_pass_by_value, reason = "upstream")]
#[post("/two-factor/send-email", data = "<data>")]
fn send_email(data: Json<SendEmailData>, _headers: Headers) -> Error {
Error::new(EMAIL_DISABLED_MSG, EMAIL_DISABLED_MSG)
@@ -48,16 +48,16 @@ fn send_email(data: Json<SendEmailData>, _headers: Headers) -> Error {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct EmailData {
- #[allow(dead_code)]
+ #[expect(dead_code, reason = "upstream")]
email: String,
- #[allow(dead_code)]
+ #[expect(dead_code, reason = "upstream")]
token: String,
- #[allow(dead_code)]
+ #[expect(dead_code, reason = "upstream")]
master_password_hash: Option<String>,
- #[allow(dead_code)]
+ #[expect(dead_code, reason = "upstream")]
otp: Option<String>,
}
-#[allow(unused_variables, clippy::needless_pass_by_value)]
+#[expect(unused_variables, clippy::needless_pass_by_value, reason = "upstream")]
#[put("/two-factor/email", data = "<data>")]
fn email(data: Json<EmailData>, _headers: Headers) -> Error {
Error::new(EMAIL_DISABLED_MSG, EMAIL_DISABLED_MSG)
diff --git a/src/api/core/two_factor/protected_actions.rs b/src/api/core/two_factor/protected_actions.rs
@@ -1,11 +1,10 @@
use crate::{auth::Headers, error::Error};
-use rocket::{serde::json::Json, Route};
+use rocket::{Route, serde::json::Json};
pub fn routes() -> Vec<Route> {
routes![request_otp, verify_otp]
}
const DEVICE_LOG_IN_MSG: &str = "Log in via device is disabled.";
-#[allow(clippy::needless_pass_by_value)]
#[post("/accounts/request-otp")]
fn request_otp(_headers: Headers) -> Error {
Error::new(DEVICE_LOG_IN_MSG, DEVICE_LOG_IN_MSG)
@@ -17,7 +16,7 @@ struct ProtectedActionVerify {
otp: String,
}
-#[allow(unused_variables, clippy::needless_pass_by_value)]
+#[expect(unused, clippy::needless_pass_by_value, reason = "upstream")]
#[post("/accounts/verify-otp", data = "<data>")]
fn verify_otp(data: Json<ProtectedActionVerify>, _headers: Headers) -> Error {
Error::new(DEVICE_LOG_IN_MSG, DEVICE_LOG_IN_MSG)
diff --git a/src/api/core/two_factor/webauthn.rs b/src/api/core/two_factor/webauthn.rs
@@ -3,32 +3,32 @@ use crate::{
auth::{self, Headers},
config,
db::{
- models::{WebAuthn, WebAuthnInfo},
DbConn,
+ models::{WebAuthn, WebAuthnInfo},
},
error::Error,
};
use data_encoding::{BASE64, BASE64URL_NOPAD};
-use rocket::serde::json::Json;
use rocket::Route;
+use rocket::serde::json::Json;
use serde::de::{Deserialize, Deserializer, Error as SerdeErr, MapAccess, Unexpected, Visitor};
use std::fmt::{self, Formatter};
use uuid::Uuid;
use webauthn_rp::{
+ AggErr,
request::{
- auth::PublicKeyCredentialRequestOptions,
+ InsertResult,
+ auth::NonDiscoverableCredentialRequestOptions,
register::{
Nickname, PublicKeyCredentialCreationOptions, PublicKeyCredentialUserEntity,
- UserHandle, Username,
+ UserHandle16, Username,
},
- InsertResult,
},
response::{
- auth::Authentication,
- register::{AuthenticatorAttestation, Registration},
AuthTransports, AuthenticatorAttachment, CollectedClientData, CredentialId,
+ auth::NonDiscoverableAuthentication16,
+ register::{AuthenticatorAttestation, Registration},
},
- AggErr,
};
pub fn routes() -> Vec<Route> {
@@ -65,13 +65,13 @@ async fn generate_webauthn_challenge(
let uuid = Uuid::parse_str(user.uuid.as_str())
.map_err(|e| Error::from(e.to_string()))?
.into_bytes();
- let id = UserHandle::try_from(uuid.as_slice()).map_err(AggErr::UserHandle)?;
+ let id = UserHandle16::from(uuid);
let display_name = Some(Nickname::try_from(user.name.as_str()).map_err(AggErr::Nickname)?);
let (server, client) = PublicKeyCredentialCreationOptions::second_factor(
&config::get_config().rp_id,
PublicKeyCredentialUserEntity {
name,
- id,
+ id: &id,
display_name,
},
WebAuthn::get_registered_creds(user.uuid.as_str(), &conn).await?,
@@ -98,7 +98,7 @@ impl<'e> Deserialize<'e> for Type {
D: Deserializer<'e>,
{
struct TypeVisitor;
- impl<'f> Visitor<'f> for TypeVisitor {
+ impl Visitor<'_> for TypeVisitor {
type Value = Type;
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter.write_str("public-key")
@@ -146,7 +146,7 @@ impl<'de> Deserialize<'de> for RegistrationWrapper {
D: Deserializer<'e>,
{
struct FieldVisitor;
- impl<'f> Visitor<'f> for FieldVisitor {
+ impl Visitor<'_> for FieldVisitor {
type Value = Field;
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(
@@ -178,7 +178,7 @@ impl<'de> Deserialize<'de> for RegistrationWrapper {
D: Deserializer<'e>,
{
struct RawIdVisitor;
- impl<'f> Visitor<'f> for RawIdVisitor {
+ impl Visitor<'_> for RawIdVisitor {
type Value = RawId;
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter.write_str("RawId")
@@ -230,14 +230,17 @@ impl<'de> Deserialize<'de> for RegistrationWrapper {
D: Deserializer<'g>,
{
struct AuthFieldVisitor;
- impl<'h> Visitor<'h> for AuthFieldVisitor {
+ impl Visitor<'_> for AuthFieldVisitor {
type Value = AuthField;
fn expecting(
&self,
formatter: &mut Formatter<'_>,
) -> fmt::Result
{
- write!(formatter, "'{ATTESTATION_OBJECT}' or '{CLIENT_DATA_JSON}'")
+ write!(
+ formatter,
+ "'{ATTESTATION_OBJECT}' or '{CLIENT_DATA_JSON}'"
+ )
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
@@ -264,7 +267,7 @@ impl<'de> Deserialize<'de> for RegistrationWrapper {
D: Deserializer<'g>,
{
struct Base64Visitor;
- impl<'h> Visitor<'h> for Base64Visitor {
+ impl Visitor<'_> for Base64Visitor {
type Value = Base64;
fn expecting(
&self,
@@ -444,10 +447,6 @@ async fn activate_webauthn(
otp: None,
}
.validate(&user)?;
- let uuid = Uuid::parse_str(user.uuid.as_str())
- .map_err(|e| Error::from(e.to_string()))?
- .into_bytes();
- let user_handle = UserHandle::try_from(uuid.as_slice()).map_err(AggErr::UserHandle)?;
let cred = auth::get_reg_ceremonies()
.take(
&CollectedClientData::from_client_data_json_relaxed::<true>(
@@ -459,7 +458,6 @@ async fn activate_webauthn(
.ok_or_else(|| Error::from(String::from("missing registration challenge")))?
.verify(
&config::get_config().rp_id,
- user_handle,
&data.device_response.0,
auth::get_reg_options(),
)
@@ -511,7 +509,7 @@ async fn delete_webauthn(data: Json<DeleteU2FData>, headers: Headers, conn: DbCo
pub async fn generate_webauthn_login(user_uuid: &str, conn: &DbConn) -> JsonResult {
let creds = WebAuthn::get_allowed_creds(user_uuid, conn).await?;
let (server, client) =
- PublicKeyCredentialRequestOptions::second_factor(&config::get_config().rp_id, creds)
+ NonDiscoverableCredentialRequestOptions::second_factor(&config::get_config().rp_id, creds)
.map_err(AggErr::SecondFactor)?
.start_ceremony()
.map_err(AggErr::RequestOptions)?;
@@ -523,7 +521,7 @@ pub async fn generate_webauthn_login(user_uuid: &str, conn: &DbConn) -> JsonResu
InsertResult::Duplicate => Err(Error::from(String::from("duplicate webauthn challenge"))),
}
}
-struct AuthenticationWrapper(Authentication);
+struct AuthenticationWrapper(NonDiscoverableAuthentication16);
impl<'de> Deserialize<'de> for AuthenticationWrapper {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
@@ -552,7 +550,7 @@ impl<'de> Deserialize<'de> for AuthenticationWrapper {
D: Deserializer<'e>,
{
struct FieldVisitor;
- impl<'f> Visitor<'f> for FieldVisitor {
+ impl Visitor<'_> for FieldVisitor {
type Value = Field;
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(
@@ -600,7 +598,7 @@ impl<'de> Deserialize<'de> for AuthenticationWrapper {
D: Deserializer<'g>,
{
struct FieldVisitor;
- impl<'h> Visitor<'h> for FieldVisitor {
+ impl Visitor<'_> for FieldVisitor {
type Value = Field;
fn expecting(
&self,
@@ -700,11 +698,13 @@ impl<'de> Deserialize<'de> for AuthenticationWrapper {
.and_then(|resp| {
if extensions {
if typ {
- Ok(AuthenticationWrapper(Authentication::new(
- r,
- resp,
- AuthenticatorAttachment::None,
- )))
+ Ok(AuthenticationWrapper(
+ NonDiscoverableAuthentication16::new(
+ r,
+ resp,
+ AuthenticatorAttachment::None,
+ ),
+ ))
} else {
Err(SerdeErr::missing_field(TYPE))
}
@@ -740,8 +740,8 @@ pub async fn validate_webauthn_login(
let uuid = Uuid::parse_str(user_uuid)
.map_err(|e| Error::from(e.to_string()))?
.into_bytes();
- let user_handle = UserHandle::try_from(uuid.as_slice()).map_err(AggErr::UserHandle)?;
- let mut cred = WebAuthn::get_credential(auth.raw_id(), user_uuid, user_handle, conn)
+ let user_handle = UserHandle16::from(uuid);
+ let mut cred = WebAuthn::get_credential(auth.raw_id(), user_uuid, &user_handle, conn)
.await?
.ok_or_else(|| Error::from(String::from("credential does not exist")))?;
if auth::get_auth_ceremonies()
@@ -757,7 +757,7 @@ pub async fn validate_webauthn_login(
&config::get_config().rp_id,
&auth,
&mut cred,
- &auth::get_auth_options(),
+ auth::get_auth_options(),
)
.map_err(AggErr::AuthCeremony)?
{
diff --git a/src/api/identity.rs b/src/api/identity.rs
@@ -1,21 +1,21 @@
use crate::{
api::{
- core::accounts::{PreloginData, RegisterData, _prelogin},
ApiResult, EmptyResult, JsonResult,
+ core::accounts::{_prelogin, PreloginData, RegisterData},
},
auth::{ClientHeaders, ClientIp},
config,
db::{
- models::{Device, OrgPolicy, OrgPolicyType, TwoFactorType, User},
DbConn,
+ models::{Device, OrgPolicy, OrgPolicyType, TwoFactorType, User},
},
- error::{Error, MapResult},
+ error::{Error, MapResult as _},
util,
};
use rocket::serde::json::Json;
use rocket::{
- form::{Form, FromForm},
Route,
+ form::{Form, FromForm},
};
use serde_json::Value;
@@ -29,17 +29,20 @@ async fn login(data: Form<ConnectData>, client_header: ClientHeaders, conn: DbCo
let mut user_uuid: Option<String> = None;
let login_result = match data.grant_type.as_ref() {
"refresh_token" => {
- _check_is_some(&data.refresh_token, "refresh_token cannot be blank")?;
+ _check_is_some(data.refresh_token.as_ref(), "refresh_token cannot be blank")?;
_refresh_login(data, &conn).await
}
"password" => {
- _check_is_some(&data.client_id, "client_id cannot be blank")?;
- _check_is_some(&data.password, "password cannot be blank")?;
- _check_is_some(&data.scope, "scope cannot be blank")?;
- _check_is_some(&data.username, "username cannot be blank")?;
- _check_is_some(&data.device_identifier, "device_identifier cannot be blank")?;
- _check_is_some(&data.device_name, "device_name cannot be blank")?;
- _check_is_some(&data.device_type, "device_type cannot be blank")?;
+ _check_is_some(data.client_id.as_ref(), "client_id cannot be blank")?;
+ _check_is_some(data.password.as_ref(), "password cannot be blank")?;
+ _check_is_some(data.scope.as_ref(), "scope cannot be blank")?;
+ _check_is_some(data.username.as_ref(), "username cannot be blank")?;
+ _check_is_some(
+ data.device_identifier.as_ref(),
+ "device_identifier cannot be blank",
+ )?;
+ _check_is_some(data.device_name.as_ref(), "device_name cannot be blank")?;
+ _check_is_some(data.device_type.as_ref(), "device_type cannot be blank")?;
_password_login(data, &mut user_uuid, &conn, &client_header.ip).await
}
t => err!("Invalid type", t),
@@ -252,23 +255,21 @@ async fn twofactor_auth(
let tf_type = data
.two_factor_provider
.map(|prov| {
- TwoFactorType::try_from(prov)
- .map_err(Error::from)
- .and_then(|tf| {
- if matches!(tf, TwoFactorType::WebAuthn) {
- if authn {
- Ok(tf)
- } else {
- const MSG: &str = "no webauthn registrations";
- Err(Error::new(MSG, MSG))
- }
- } else if totp_token.is_some() {
+ TwoFactorType::try_from(prov).and_then(|tf| {
+ if matches!(tf, TwoFactorType::WebAuthn) {
+ if authn {
Ok(tf)
} else {
- const MSG: &str = "no totp registrations";
+ const MSG: &str = "no webauthn registrations";
Err(Error::new(MSG, MSG))
}
- })
+ } else if totp_token.is_some() {
+ Ok(tf)
+ } else {
+ const MSG: &str = "no totp registrations";
+ Err(Error::new(MSG, MSG))
+ }
+ })
})
.transpose()?
.unwrap_or({
@@ -394,7 +395,7 @@ struct ConnectData {
auth_request: Option<String>,
}
-fn _check_is_some<T>(value: &Option<T>, msg: &str) -> EmptyResult {
+fn _check_is_some<T>(value: Option<&T>, msg: &str) -> EmptyResult {
if value.is_none() {
err!(msg)
}
diff --git a/src/auth.rs b/src/auth.rs
@@ -3,20 +3,20 @@ use crate::{
error::Error,
};
use chrono::{TimeDelta, Utc};
-use jsonwebtoken::{self, errors::ErrorKind, Algorithm, DecodingKey, EncodingKey, Header};
+use jsonwebtoken::{self, Algorithm, DecodingKey, EncodingKey, Header, errors::ErrorKind};
use openssl::pkey::{Id, PKey};
use serde::de::DeserializeOwned;
use serde::ser::Serialize;
use std::fs::File;
-use std::io::{Read, Write};
+use std::io::{Read as _, Write as _};
use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
use webauthn_rp::request::{
+ BackupReq, BuildIdentityHasher, FixedCapHashSet,
auth::{
- AuthenticationServerState, AuthenticationVerificationOptions,
- AuthenticatorAttachmentEnforcement, SignatureCounterEnforcement,
+ AuthenticationVerificationOptions, AuthenticatorAttachmentEnforcement,
+ NonDiscoverableAuthenticationServerState, SignatureCounterEnforcement,
},
register::{RegistrationServerState, RegistrationVerificationOptions},
- BackupReq, BuildIdentityHasher, FixedCapHashSet,
};
static ALLOWED_ORIGINS: OnceLock<[&str; 1]> = OnceLock::new();
#[inline]
@@ -33,7 +33,7 @@ fn get_allowed_origins() -> &'static [&'static str] {
.as_slice()
}
static REG_CEREMONIES: OnceLock<
- Arc<Mutex<FixedCapHashSet<RegistrationServerState, BuildIdentityHasher>>>,
+ Arc<Mutex<FixedCapHashSet<RegistrationServerState<16>, BuildIdentityHasher>>>,
> = OnceLock::new();
#[inline]
fn init_reg_ceremonies() {
@@ -42,8 +42,8 @@ fn init_reg_ceremonies() {
.expect("REG_CEREMONIES must only be initialized once");
}
#[inline]
-pub fn get_reg_ceremonies(
-) -> MutexGuard<'static, FixedCapHashSet<RegistrationServerState, BuildIdentityHasher>> {
+pub fn get_reg_ceremonies()
+-> MutexGuard<'static, FixedCapHashSet<RegistrationServerState<16>, BuildIdentityHasher>> {
REG_CEREMONIES
.get()
.expect("REG_CEREMONIES must be initialized in main")
@@ -66,14 +66,14 @@ fn init_reg_options() {
.expect("REG_OPTIONS must only be initialized once");
}
#[inline]
-pub fn get_reg_options(
-) -> &'static RegistrationVerificationOptions<'static, 'static, &'static str, &'static str> {
+pub fn get_reg_options()
+-> &'static RegistrationVerificationOptions<'static, 'static, &'static str, &'static str> {
REG_OPTIONS
.get()
.expect("REG_OPTIONS must be initialized in main")
}
static AUTH_CEREMONIES: OnceLock<
- Arc<Mutex<FixedCapHashSet<AuthenticationServerState, BuildIdentityHasher>>>,
+ Arc<Mutex<FixedCapHashSet<NonDiscoverableAuthenticationServerState, BuildIdentityHasher>>>,
> = OnceLock::new();
#[inline]
fn init_auth_ceremonies() {
@@ -82,8 +82,10 @@ fn init_auth_ceremonies() {
.expect("AUTH_CEREMONIES must only be initialized once");
}
#[inline]
-pub fn get_auth_ceremonies(
-) -> MutexGuard<'static, FixedCapHashSet<AuthenticationServerState, BuildIdentityHasher>> {
+pub fn get_auth_ceremonies() -> MutexGuard<
+ 'static,
+ FixedCapHashSet<NonDiscoverableAuthenticationServerState, BuildIdentityHasher>,
+> {
AUTH_CEREMONIES
.get()
.expect("AUTH_CEREMONIES must be initialized in main")
@@ -108,8 +110,8 @@ fn init_auth_options() {
.expect("AUTH_OPTIONS must only be initialized once");
}
#[inline]
-pub fn get_auth_options(
-) -> &'static AuthenticationVerificationOptions<'static, 'static, &'static str, &'static str> {
+pub fn get_auth_options()
+-> &'static AuthenticationVerificationOptions<'static, 'static, &'static str, &'static str> {
AUTH_OPTIONS
.get()
.expect("AUTH_OPTIONS must be initialized in main")
@@ -374,10 +376,10 @@ pub struct BasicJwtClaims {
pub sub: String,
}
use crate::db::{
+ DbConn,
models::{
Collection, Device, User, UserOrgStatus, UserOrgType, UserOrganization, UserStampException,
},
- DbConn,
};
use rocket::{
outcome::try_outcome,
@@ -547,8 +549,8 @@ impl<'r> FromRequest<'r> for OrgHeaders {
user
} else {
err_handler!(
- "The current user isn't confirmed member of the organization"
- )
+ "The current user isn't confirmed member of the organization"
+ )
}
}
None => {
diff --git a/src/config.rs b/src/config.rs
@@ -1,8 +1,10 @@
-use core::fmt::{self, Display, Formatter};
-use core::num::NonZeroU8;
-use core::str;
+use core::{
+ fmt::{self, Display, Formatter},
+ num::{NonZeroU8, NonZeroUsize},
+ str,
+};
use rocket::config::{CipherSuite, LogLevel, TlsConfig};
-use rocket::data::{Limits, ToByteUnit};
+use rocket::data::{Limits, ToByteUnit as _};
use std::error;
use std::fs;
use std::io::Error;
@@ -133,7 +135,7 @@ impl Config {
..Default::default()
};
if let Some(count) = config_file.workers {
- rocket.workers = usize::from(count.get());
+ rocket.workers = NonZeroUsize::from(count).get();
}
let domain =
AsciiDomain::try_from(config_file.domain).map_err(|_e| ConfigErr::BadDomain)?;
diff --git a/src/crypto.rs b/src/crypto.rs
@@ -22,7 +22,7 @@ pub fn verify_password_hash(secret: &[u8], salt: &[u8], previous: &[u8], iterati
/// Return an array holding `N` random bytes.
pub fn get_random_bytes<const N: usize>() -> [u8; N] {
- use ring::rand::{SecureRandom, SystemRandom};
+ use ring::rand::{SecureRandom as _, SystemRandom};
let mut array = [0; N];
SystemRandom::new()
.fill(&mut array)
@@ -34,11 +34,3 @@ pub fn get_random_bytes<const N: usize>() -> [u8; N] {
pub fn encode_random_bytes<const N: usize>(e: &Encoding) -> String {
e.encode(&get_random_bytes::<N>())
}
-
-//
-// Constant time compare
-//
-pub fn ct_eq<T: AsRef<[u8]>, U: AsRef<[u8]>>(a: T, b: U) -> bool {
- use ring::constant_time::verify_slices_are_equal;
- verify_slices_are_equal(a.as_ref(), b.as_ref()).is_ok()
-}
diff --git a/src/db/mod.rs b/src/db/mod.rs
@@ -1,16 +1,17 @@
use crate::{
config::{self, Config},
- error::{Error, MapResult},
+ error::{Error, MapResult as _},
};
+use core::num::{NonZeroU32, NonZeroUsize};
use diesel::{
- connection::SimpleConnection,
- r2d2::{self, ConnectionManager, CustomizeConnection, Pool, PooledConnection},
SqliteConnection,
+ connection::SimpleConnection as _,
+ r2d2::{self, ConnectionManager, CustomizeConnection, Pool, PooledConnection},
};
use rocket::{
+ Request,
http::Status,
request::{FromRequest, Outcome},
- Request,
};
use std::{panic, sync::Arc, time::Duration};
use tokio::{
@@ -83,7 +84,7 @@ impl DbPool {
let url = Config::DATABASE_URL;
let manager = ConnectionManager::new(url);
let pool = Pool::builder()
- .max_size(u32::from(config::get_config().database_max_conns.get()))
+ .max_size(NonZeroU32::from(config::get_config().database_max_conns).get())
.connection_timeout(Duration::from_secs(u64::from(
config::get_config().database_timeout,
)))
@@ -92,9 +93,9 @@ impl DbPool {
.map_res("Failed to create pool")?;
Ok(Self {
pool: Some(pool),
- semaphore: Arc::new(Semaphore::new(usize::from(
- config::get_config().database_max_conns.get(),
- ))),
+ semaphore: Arc::new(Semaphore::new(
+ NonZeroUsize::from(config::get_config().database_max_conns).get(),
+ )),
})
}
// Get a connection from the pool
@@ -127,7 +128,7 @@ macro_rules! db_run {
use diesel::prelude::*;
use tokio::task;
#[allow(unused)]
- use $crate::db::FromDb;
+ use $crate::db::FromDb as _;
let mut con = $conn.conn.clone().lock_owned().await;
paste::paste! {
#[allow(unused)] use $crate::db::schema::{self, *};
diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs
@@ -108,7 +108,7 @@ impl Cipher {
}
use crate::api::EmptyResult;
use crate::db::DbConn;
-use crate::error::MapResult;
+use crate::error::MapResult as _;
/// Database methods
impl Cipher {
pub async fn to_json(
@@ -329,7 +329,7 @@ impl Cipher {
}
}
}
- };
+ }
user_uuids
}
@@ -421,7 +421,10 @@ impl Cipher {
}
/// Returns whether this cipher is owned by an org in which the user has full access.
- #[allow(clippy::else_if_without_else)]
+ #[expect(
+ clippy::else_if_without_else,
+ reason = "don't want a blank else branch"
+ )]
async fn is_in_full_access_org(
&self,
user_uuid: &str,
diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs
@@ -151,7 +151,7 @@ impl Collection {
use crate::api::EmptyResult;
use crate::db::DbConn;
-use crate::error::MapResult;
+use crate::error::MapResult as _;
/// Database methods
impl Collection {
diff --git a/src/db/models/device.rs b/src/db/models/device.rs
@@ -63,7 +63,7 @@ impl Device {
// let orguser: Vec<_> = orgs.iter().filter(|o| o.atype == 2).map(|o| o.org_uuid.clone()).collect();
// let orgmanager: Vec<_> = orgs.iter().filter(|o| o.atype == 3).map(|o| o.org_uuid.clone()).collect();
// Create the JWT claims struct, to send to the client
- use crate::auth::{self, encode_jwt, LoginJwtClaims};
+ use crate::auth::{self, LoginJwtClaims, encode_jwt};
let claims = LoginJwtClaims {
nbf: time_now.timestamp(),
exp: time_now
@@ -101,7 +101,7 @@ impl Device {
use crate::api::EmptyResult;
use crate::db::DbConn;
-use crate::error::MapResult;
+use crate::error::MapResult as _;
/// Database methods
impl Device {
diff --git a/src/db/models/favorite.rs b/src/db/models/favorite.rs
@@ -1,7 +1,7 @@
use super::User;
use crate::api::EmptyResult;
use crate::db::DbConn;
-use crate::error::MapResult;
+use crate::error::MapResult as _;
db_object! {
#[derive(Insertable)]
#[diesel(table_name = favorites)]
diff --git a/src/db/models/folder.rs b/src/db/models/folder.rs
@@ -58,7 +58,7 @@ impl FolderCipher {
use crate::api::EmptyResult;
use crate::db::DbConn;
-use crate::error::MapResult;
+use crate::error::MapResult as _;
/// Database methods
impl Folder {
diff --git a/src/db/models/org_policy.rs b/src/db/models/org_policy.rs
@@ -1,8 +1,8 @@
use super::{UserOrgStatus, UserOrgType, UserOrganization};
use crate::api::EmptyResult;
-use crate::db::models::TwoFactorType;
use crate::db::DbConn;
-use crate::error::{Error, MapResult};
+use crate::db::models::TwoFactorType;
+use crate::error::{Error, MapResult as _};
use crate::util;
use diesel::result::{self, DatabaseErrorKind};
use serde::Deserialize;
@@ -253,7 +253,7 @@ impl OrgPolicy {
return Err(OrgPolicyErr::TwoFactorMissing);
}
_ => {}
- };
+ }
}
// Enforce Single Organization Policy of other organizations user is a member of
diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs
@@ -295,7 +295,7 @@ impl UserOrganization {
use crate::api::EmptyResult;
use crate::db::DbConn;
-use crate::error::MapResult;
+use crate::error::MapResult as _;
/// Database methods
impl Organization {
diff --git a/src/db/models/two_factor.rs b/src/db/models/two_factor.rs
@@ -1,27 +1,28 @@
use crate::{
api::EmptyResult,
db::{
- schema::{totp, webauthn},
DbConn, FromDb,
+ schema::{totp, webauthn},
},
error::Error,
};
-use serde::ser::{Serialize, SerializeStruct, Serializer};
+use serde::ser::{Serialize, SerializeStruct as _, Serializer};
use tokio::task;
use webauthn_rp::{
- bin::{Decode, Encode},
+ AggErr, AuthenticatedCredential, RegisteredCredential,
+ bin::{Decode as _, Encode as _},
request::{
- auth::AllowedCredentials, register::UserHandle, Credentials, PublicKeyCredentialDescriptor,
+ Credentials, PublicKeyCredentialDescriptor, auth::AllowedCredentials,
+ register::UserHandle16,
},
response::{
+ AuthTransports, CredentialId,
register::{
AuthenticatorExtensionOutputStaticState, CompressedP256PubKey, CompressedP384PubKey,
CompressedPubKey, CredentialProtectionPolicy, DynamicState, Ed25519PubKey, RsaPubKey,
StaticState, UncompressedPubKey,
},
- AuthTransports, CredentialId,
},
- AggErr, AuthenticatedCredential, RegisteredCredential,
};
db_object! {
/// Exactly one of the following is true:
@@ -64,8 +65,9 @@ db_object! {
}
}
impl WebAuthn {
+ #[allow(clippy::as_conversions, clippy::cast_possible_wrap)]
pub fn new(
- cred: &RegisteredCredential<'_, '_>,
+ cred: &RegisteredCredential<'_, 16>,
user_uuid: String,
id: i64,
name: String,
@@ -203,7 +205,7 @@ impl Totp {
impl TwoFactorType {
#[allow(clippy::clone_on_ref_ptr, clippy::shadow_unrelated)]
pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
- use diesel::prelude::{Connection, ExpressionMethods, RunQueryDsl};
+ use diesel::prelude::{Connection as _, ExpressionMethods as _, RunQueryDsl as _};
use diesel::result;
let mut con_res = conn.conn.clone().lock_owned().await;
let con = con_res.as_mut().expect("unable to get a pooled connection");
@@ -224,7 +226,7 @@ impl TwoFactorType {
}
#[allow(clippy::clone_on_ref_ptr)]
pub async fn delete_by_user(self, user_uuid: &str, conn: &DbConn) -> EmptyResult {
- use diesel::prelude::{ExpressionMethods, RunQueryDsl};
+ use diesel::prelude::{ExpressionMethods as _, RunQueryDsl as _};
use diesel::result;
let mut con_res = conn.conn.clone().lock_owned().await;
let con = con_res.as_mut().expect("unable to get a pooled connection");
@@ -243,7 +245,9 @@ impl TwoFactorType {
}
#[allow(clippy::clone_on_ref_ptr, clippy::shadow_unrelated)]
pub async fn has_twofactor(user_uuid: &str, conn: &DbConn) -> Result<bool, Error> {
- use diesel::prelude::{Connection, ExpressionMethods, QueryDsl, RunQueryDsl};
+ use diesel::prelude::{
+ Connection as _, ExpressionMethods as _, QueryDsl as _, RunQueryDsl as _,
+ };
use diesel::result;
let mut con_res = conn.conn.clone().lock_owned().await;
let con = con_res.as_mut().expect("unable to get a pooled connection");
@@ -276,9 +280,11 @@ impl TwoFactorType {
user_uuid: &str,
conn: &DbConn,
) -> Result<(bool, Option<String>), Error> {
- use diesel::prelude::{Connection, ExpressionMethods, QueryDsl, RunQueryDsl};
+ use diesel::OptionalExtension as _;
+ use diesel::prelude::{
+ Connection as _, ExpressionMethods as _, QueryDsl as _, RunQueryDsl as _,
+ };
use diesel::result;
- use diesel::OptionalExtension;
let mut con_res = conn.conn.clone().lock_owned().await;
let con = con_res.as_mut().expect("unable to get a pooled connection");
task::block_in_place(move || {
@@ -304,7 +310,7 @@ impl TwoFactorType {
impl WebAuthnInfo {
#[allow(clippy::clone_on_ref_ptr)]
pub async fn get_all_by_user(user_uuid: &str, conn: &DbConn) -> Result<Vec<Self>, Error> {
- use diesel::prelude::{ExpressionMethods, QueryDsl, RunQueryDsl};
+ use diesel::prelude::{ExpressionMethods as _, QueryDsl as _, RunQueryDsl as _};
use diesel::result;
let mut con_res = conn.conn.clone().lock_owned().await;
let con = con_res.as_mut().expect("unable to get a pooled connection");
@@ -325,7 +331,7 @@ impl WebAuthn {
T: Credentials,
PublicKeyCredentialDescriptor<Vec<u8>>: Into<T::Credential>,
{
- use diesel::prelude::{ExpressionMethods, QueryDsl, RunQueryDsl};
+ use diesel::prelude::{ExpressionMethods as _, QueryDsl as _, RunQueryDsl as _};
use diesel::result;
let mut con_res = conn.conn.clone().lock_owned().await;
let con = con_res.as_mut().expect("unable to get a pooled connection");
@@ -366,7 +372,7 @@ impl WebAuthn {
#[allow(clippy::clone_on_ref_ptr)]
pub async fn insert(self, conn: &DbConn) -> EmptyResult {
use __sqlite_model::WebAuthnDb;
- use diesel::prelude::RunQueryDsl;
+ use diesel::prelude::RunQueryDsl as _;
use diesel::result;
let mut con_res = conn.conn.clone().lock_owned().await;
let con = con_res.as_mut().expect("unable to get a pooled connection");
@@ -392,7 +398,7 @@ impl WebAuthn {
dynamic_state: DynamicState,
conn: &DbConn,
) -> EmptyResult {
- use diesel::prelude::{ExpressionMethods, RunQueryDsl};
+ use diesel::prelude::{ExpressionMethods as _, RunQueryDsl as _};
use diesel::result;
let mut con_res = conn.conn.clone().lock_owned().await;
let con = con_res.as_mut().expect("unable to get a pooled connection");
@@ -413,25 +419,30 @@ impl WebAuthn {
})
})
}
- #[allow(clippy::clone_on_ref_ptr)]
+ #[allow(
+ clippy::as_conversions,
+ clippy::cast_sign_loss,
+ clippy::clone_on_ref_ptr
+ )]
pub async fn get_credential<'a, 'b>(
credential_id: CredentialId<&'a [u8]>,
user_uuid: &str,
- user_handle: UserHandle<&'b [u8]>,
+ user_handle: &'b UserHandle16,
conn: &DbConn,
) -> Result<
Option<
AuthenticatedCredential<
'a,
'b,
+ 16,
CompressedPubKey<[u8; 32], [u8; 32], [u8; 48], Vec<u8>>,
>,
>,
Error,
> {
- use diesel::prelude::{ExpressionMethods, QueryDsl, RunQueryDsl};
+ use diesel::OptionalExtension as _;
+ use diesel::prelude::{ExpressionMethods as _, QueryDsl as _, RunQueryDsl as _};
use diesel::result;
- use diesel::OptionalExtension;
let mut con_res = conn.conn.clone().lock_owned().await;
let con = con_res.as_mut().expect("unable to get a pooled connection");
task::block_in_place(move || {
@@ -553,7 +564,7 @@ impl WebAuthn {
id: i64,
conn: &DbConn,
) -> EmptyResult {
- use diesel::prelude::{ExpressionMethods, RunQueryDsl};
+ use diesel::prelude::{ExpressionMethods as _, RunQueryDsl as _};
use diesel::result;
let mut con_res = conn.conn.clone().lock_owned().await;
let con = con_res.as_mut().expect("unable to get a pooled connection");
@@ -580,7 +591,7 @@ impl Totp {
#[allow(clippy::clone_on_ref_ptr, clippy::shadow_unrelated)]
pub async fn replace(self, conn: &DbConn) -> EmptyResult {
use __sqlite_model::TotpDb;
- use diesel::prelude::{ExpressionMethods, RunQueryDsl};
+ use diesel::prelude::{ExpressionMethods as _, RunQueryDsl as _};
use diesel::result;
let mut con_res = conn.conn.clone().lock_owned().await;
let con = con_res.as_mut().expect("unable to get a pooled connection");
@@ -614,9 +625,9 @@ impl Totp {
#[allow(clippy::clone_on_ref_ptr)]
pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Result<Option<Self>, Error> {
use __sqlite_model::TotpDb;
- use diesel::prelude::{ExpressionMethods, QueryDsl, RunQueryDsl};
+ use diesel::OptionalExtension as _;
+ use diesel::prelude::{ExpressionMethods as _, QueryDsl as _, RunQueryDsl as _};
use diesel::result;
- use diesel::OptionalExtension;
let mut con_res = conn.conn.clone().lock_owned().await;
let con = con_res.as_mut().expect("unable to get a pooled connection");
task::block_in_place(move || {
diff --git a/src/db/models/user.rs b/src/db/models/user.rs
@@ -168,17 +168,6 @@ impl User {
.expect("underflow converting password iterations into a u32"),
)
}
-
- pub fn check_valid_recovery_code(&self, recovery_code: &str) -> bool {
- self.totp_recover.as_ref().map_or(false, |totp_recover| {
- crypto::ct_eq(recovery_code, totp_recover.to_lowercase())
- })
- }
-
- pub fn check_valid_api_key(&self, key: &str) -> bool {
- matches!(self.api_key, Some(ref api_key) if crypto::ct_eq(api_key, key))
- }
-
/// Set the password hash generated
/// And resets the security_stamp. Based upon the allow_next_route the security_stamp will be different.
///
@@ -187,9 +176,8 @@ impl User {
/// * `password` - A str which contains a hashed version of the users master password.
/// * `new_key` - A String which contains the new aKey value of the users master password.
/// * `allow_next_route` - A Option<Vec<String>> with the function names of the next allowed (rocket) routes.
- /// These routes are able to use the previous stamp id for the next 2 minutes.
- /// After these 2 minutes this stamp will expire.
- ///
+ /// These routes are able to use the previous stamp id for the next 2 minutes.
+ /// After these 2 minutes this stamp will expire.
pub fn set_password(
&mut self,
password: &str,
@@ -222,9 +210,8 @@ impl User {
///
/// # Arguments
/// * `route_exception` - A Vec<String> with the function names of the next allowed (rocket) routes.
- /// These routes are able to use the previous stamp id for the next 2 minutes.
- /// After these 2 minutes this stamp will expire.
- ///
+ /// These routes are able to use the previous stamp id for the next 2 minutes.
+ /// After these 2 minutes this stamp will expire.
pub fn set_stamp_exception(&mut self, route_exception: Vec<String>) {
let stamp_exception = UserStampException {
routes: route_exception,
@@ -251,7 +238,7 @@ impl User {
use super::{Cipher, Device, Favorite, Folder, TwoFactorType, UserOrgType, UserOrganization};
use crate::api::EmptyResult;
use crate::db::DbConn;
-use crate::error::MapResult;
+use crate::error::MapResult as _;
/// Database methods
impl User {
diff --git a/src/error.rs b/src/error.rs
@@ -4,7 +4,6 @@
use core::error::Error as StdError;
use core::fmt::{self, Debug, Display, Formatter};
-#[expect(edition_2024_expr_fragment_specifier, reason = "false positive")]
macro_rules! make_error {
( $( $name:ident ( $ty:ty ): $src_fn:expr, $usr_msg_fun:expr ),+ $(,)? ) => {
const BAD_REQUEST: u16 = 400;
@@ -36,9 +35,9 @@ macro_rules! make_error {
}
use core::any::Any;
use core::convert::Infallible;
+use diesel::ConnectionError as DieselConErr;
use diesel::r2d2::PoolError as R2d2Err;
use diesel::result::Error as DieselErr;
-use diesel::ConnectionError as DieselConErr;
use jsonwebtoken::errors::Error as JwtErr;
use openssl::error::ErrorStack as SSLErr;
#[cfg(all(feature = "priv_sep", target_os = "openbsd"))]
@@ -223,7 +222,7 @@ use rocket::request::Request;
use rocket::response::{self, Responder, Response};
use std::io::Cursor;
-impl<'r> Responder<'r, 'static> for Error {
+impl Responder<'_, 'static> for Error {
fn respond_to(self, _: &Request<'_>) -> response::Result<'static> {
let code = Status::from_code(self.error_code).unwrap_or(Status::BadRequest);
let body = self.to_string();
@@ -238,7 +237,6 @@ impl<'r> Responder<'r, 'static> for Error {
//
// Error return macros
//
-#[expect(edition_2024_expr_fragment_specifier, reason = "false positive")]
#[macro_export]
macro_rules! err {
($msg:expr) => {{
@@ -255,7 +253,6 @@ macro_rules! err {
}};
}
-#[expect(edition_2024_expr_fragment_specifier, reason = "false positive")]
#[macro_export]
macro_rules! err_silent {
($msg:expr) => {{
@@ -266,7 +263,6 @@ macro_rules! err_silent {
}};
}
-#[expect(edition_2024_expr_fragment_specifier, reason = "false positive")]
#[macro_export]
macro_rules! err_code {
($msg:expr, $err_code:expr) => {{
@@ -277,7 +273,6 @@ macro_rules! err_code {
}};
}
-#[expect(edition_2024_expr_fragment_specifier, reason = "false positive")]
#[macro_export]
macro_rules! err_discard {
($msg:expr, $data:expr) => {{
@@ -290,7 +285,6 @@ macro_rules! err_discard {
}};
}
-#[expect(edition_2024_expr_fragment_specifier, reason = "false positive")]
#[macro_export]
macro_rules! err_json {
($expr:expr, $log_value:expr) => {{
@@ -301,7 +295,6 @@ macro_rules! err_json {
}};
}
-#[expect(edition_2024_expr_fragment_specifier, reason = "false positive")]
#[macro_export]
macro_rules! err_handler {
($expr:expr) => {{
diff --git a/src/main.rs b/src/main.rs
@@ -22,7 +22,10 @@
clippy::style,
clippy::suspicious
)]
-#![allow(
+#![expect(
+ clippy::allow_attributes,
+ clippy::allow_attributes_without_reason,
+ clippy::arbitrary_source_item_ordering,
clippy::blanket_clippy_restriction_lints,
clippy::doc_markdown,
clippy::expect_used,
@@ -35,18 +38,15 @@
clippy::missing_errors_doc,
clippy::missing_trait_methods,
clippy::mod_module_files,
- clippy::module_name_repetitions,
clippy::multiple_crate_versions,
clippy::multiple_inherent_impl,
- clippy::multiple_unsafe_ops_per_block,
- clippy::no_effect_underscore_binding,
clippy::panic,
- clippy::panic_in_result_fn,
clippy::partial_pub_fields,
clippy::pub_use,
clippy::question_mark_used,
clippy::redundant_type_annotations,
clippy::ref_patterns,
+ clippy::return_and_then,
clippy::shadow_reuse,
clippy::significant_drop_in_scrutinee,
clippy::significant_drop_tightening,
@@ -59,7 +59,9 @@
clippy::unseparated_literal_suffix,
clippy::unwrap_in_result,
clippy::unwrap_used,
- clippy::used_underscore_binding
+ clippy::used_underscore_binding,
+ clippy::used_underscore_items,
+ reason = "noisy, opinionated, and likely doesn't prevent bugs or improve APIs"
)]
// The recursion_limit is mainly triggered by the json!() macro.
// The more key/value pairs there are the more recursion occurs.
@@ -84,6 +86,7 @@ mod db;
mod priv_sep;
mod util;
use config::Config;
+use core::num::NonZeroU32;
pub use error::{Error, MapResult};
use std::env;
use std::{path::Path, process};
@@ -132,14 +135,13 @@ fn static_init() {
});
auth::init_values();
}
-
-#[allow(clippy::exit)]
+#[expect(clippy::exit, reason = "upstream")]
fn check_data_folder() {
if !Path::new(Config::DATA_FOLDER).is_dir() {
process::exit(1);
}
}
-#[allow(clippy::exit)]
+#[expect(clippy::exit, reason = "upstream")]
fn check_web_vault() {
if config::get_config().web_vault_enabled
&& !Path::new(Config::WEB_VAULT_FOLDER)
@@ -149,11 +151,11 @@ fn check_web_vault() {
process::exit(1);
}
}
-#[allow(clippy::exit)]
+#[expect(clippy::exit, reason = "upstream")]
async fn create_db_pool() -> db::DbPool {
(util::retry_db(
db::DbPool::from_config,
- u32::from(config::get_config().db_connection_retries.get()),
+ NonZeroU32::from(config::get_config().db_connection_retries).get(),
)
.await)
.unwrap_or_else(|_| {
diff --git a/src/priv_sep.rs b/src/priv_sep.rs
@@ -50,7 +50,7 @@ pub fn pledge_away_unveil(promises: &mut Promises) -> Result<(), Error> {
#[allow(clippy::unnecessary_wraps)]
#[cfg(not(all(feature = "priv_sep", target_os = "openbsd")))]
#[inline]
-pub fn pledge_away_unveil(_: &mut Zst) -> Result<(), Infallible> {
+pub const fn pledge_away_unveil(_: &mut Zst) -> Result<(), Infallible> {
Ok(())
}
/// Calls `unveil` on `path` with `Permissions::READ`.