commit 91ae28f2ffdf190f485172e31e7ed46938791aef
parent 2612a25bb8c575f6916a599cd073aaad1fb8cc05
Author: Zack Newman <zack@philomathiclife.com>
Date: Fri, 15 Dec 2023 14:45:49 -0700
use better types. improve database types
Diffstat:
10 files changed, 170 insertions(+), 111 deletions(-)
diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs
@@ -233,9 +233,9 @@ async fn post_password(
#[allow(non_snake_case)]
struct ChangeKdfData {
Kdf: i32,
- KdfIterations: i32,
- KdfMemory: Option<i32>,
- KdfParallelism: Option<i32>,
+ KdfIterations: u32,
+ KdfMemory: Option<u32>,
+ KdfParallelism: Option<u32>,
MasterPasswordHash: String,
NewMasterPasswordHash: String,
Key: String,
@@ -253,34 +253,34 @@ async fn post_kdf(
if !user.check_valid_password(&kdf_data.MasterPasswordHash) {
err!("Invalid password")
}
- if kdf_data.Kdf == i32::from(UserKdfType::Pbkdf2) && kdf_data.KdfIterations < 100_000i32 {
+ if kdf_data.Kdf == i32::from(UserKdfType::Pbkdf2) && kdf_data.KdfIterations < 100_000u32 {
err!("PBKDF2 KDF iterations must be at least 100000.")
}
if kdf_data.Kdf == i32::from(UserKdfType::Argon2id) {
- if kdf_data.KdfIterations < 1i32 {
+ if kdf_data.KdfIterations < 1u32 {
err!("Argon2 KDF iterations must be at least 1.")
}
if let Some(m) = kdf_data.KdfMemory {
- if !(15i32..=1024i32).contains(&m) {
+ if !(15u32..=1024u32).contains(&m) {
err!("Argon2 memory must be between 15 MB and 1024 MB.")
}
- user.client_kdf_memory = kdf_data.KdfMemory;
+ user.set_client_kdf_memory(kdf_data.KdfMemory);
} else {
err!("Argon2 memory parameter is required.")
}
if let Some(p) = kdf_data.KdfParallelism {
- if !(1i32..=16i32).contains(&p) {
+ if !(1u32..=16u32).contains(&p) {
err!("Argon2 parallelism must be between 1 and 16.")
}
- user.client_kdf_parallelism = kdf_data.KdfParallelism;
+ user.set_client_kdf_parallelism(kdf_data.KdfParallelism);
} else {
err!("Argon2 parallelism parameter is required.")
}
} else {
- user.client_kdf_memory = None;
- user.client_kdf_parallelism = None;
+ user.set_client_kdf_memory(None);
+ user.set_client_kdf_parallelism(None);
}
- user.client_kdf_iter = kdf_data.KdfIterations;
+ user.set_client_kdf_iter(kdf_data.KdfIterations);
user.client_kdf_type = kdf_data.Kdf;
user.set_password(
&kdf_data.NewMasterPasswordHash,
@@ -467,7 +467,7 @@ async fn post_verify_email_token(
}
user.verified_at = Some(Utc::now().naive_utc());
user.last_verifying_at = None;
- user.login_verify_count = 0i32;
+ user.set_login_verify_count(0);
user.save(&conn).await
}
@@ -564,9 +564,9 @@ pub async fn _prelogin(data: JsonUpcase<PreloginData>, conn: DbConn) -> Json<Val
match User::find_by_mail(&login_data.Email, &conn).await {
Some(user) => (
user.client_kdf_type,
- user.client_kdf_iter,
- user.client_kdf_memory,
- user.client_kdf_parallelism,
+ user.client_kdf_iter(),
+ user.client_kdf_memory(),
+ user.client_kdf_parallelism(),
),
None => (
User::client_kdf_type_default(),
diff --git a/src/api/core/two_factor/authenticator.rs b/src/api/core/two_factor/authenticator.rs
@@ -134,21 +134,19 @@ async fn validate_totp_code(
);
// Get the current system time in UNIX Epoch (UTC)
let current_time = chrono::Utc::now();
- let current_timestamp = current_time.timestamp();
- let time_step = current_timestamp / 30i64;
- // We need to calculate the time offsite and cast it as a u64.
+ let current_timestamp = u64::try_from(current_time.timestamp()).expect("underflow");
+ let time_step = current_timestamp / 30u64;
+ // We need to calculate the time offset and cast it as a u64.
// Since we only have times into the future and the totp generator needs, a u64 instead of the default i64.
- let time = u64::try_from(current_timestamp).expect("underflow when casting to a u64 in TOTP");
- let generated = totp_custom::<Sha1>(30, 6, &decoded_secret, time);
+ let generated = totp_custom::<Sha1>(30, 6, &decoded_secret, current_timestamp);
// Check the given code equals the generated one and if the time_step is larger than the one last used.
- if generated == totp_code && time_step > i64::from(twofactor.last_used) {
+ if generated == totp_code && time_step > u64::from(twofactor.last_used()) {
// Save the last used time step so only totp time steps higher then this one are allowed.
// This will also save a newly created twofactor if the code is correct.
- twofactor.last_used =
- i32::try_from(time_step).expect("overflow or underflow when casting to an i32 in TOTP");
+ twofactor.set_last_used(u32::try_from(time_step).expect("overflow"));
twofactor.save(conn).await?;
Ok(())
- } else if generated == totp_code && time_step <= i64::from(twofactor.last_used) {
+ } else if generated == totp_code && time_step <= u64::from(twofactor.last_used()) {
warn!("This TOTP or a TOTP code within 0 steps back or forward has already been used!");
err!(format!(
"Invalid TOTP code! Server time: {} IP: {}",
diff --git a/src/api/identity.rs b/src/api/identity.rs
@@ -84,9 +84,9 @@ async fn _refresh_login(data: ConnectData, conn: &DbConn) -> JsonResult {
"Key": user.akey,
"PrivateKey": user.private_key,
"Kdf": user.client_kdf_type,
- "KdfIterations": user.client_kdf_iter,
- "KdfMemory": user.client_kdf_memory,
- "KdfParallelism": user.client_kdf_parallelism,
+ "KdfIterations": user.client_kdf_iter(),
+ "KdfMemory": user.client_kdf_memory(),
+ "KdfParallelism": user.client_kdf_parallelism(),
"ResetMasterPassword": false, // TODO: according to official server seems something like: user.password_hash.is_empty(), but would need testing
"scope": scope,
"unofficialServer": true,
@@ -141,8 +141,8 @@ async fn _password_login(
)
}
// Change the KDF Iterations
- if user.password_iterations != config::get_config().password_iterations {
- user.password_iterations = config::get_config().password_iterations;
+ if user.password_iterations() != config::get_config().password_iterations {
+ user.set_password_iterations(config::get_config().password_iterations);
user.set_password(password, None, false, None);
if let Err(e) = user.save(conn).await {
panic!("Error updating user: {e:#?}");
@@ -174,9 +174,9 @@ async fn _password_login(
"Key": user.akey,
"PrivateKey": user.private_key,
"Kdf": user.client_kdf_type,
- "KdfIterations": user.client_kdf_iter,
- "KdfMemory": user.client_kdf_memory,
- "KdfParallelism": user.client_kdf_parallelism,
+ "KdfIterations": user.client_kdf_iter(),
+ "KdfMemory": user.client_kdf_memory(),
+ "KdfParallelism": user.client_kdf_parallelism(),
"ResetMasterPassword": false,// TODO: Same as above
"scope": scope,
"unofficialServer": true,
@@ -260,9 +260,9 @@ async fn _user_api_key_login(
"Key": user.akey,
"PrivateKey": user.private_key,
"Kdf": user.client_kdf_type,
- "KdfIterations": user.client_kdf_iter,
- "KdfMemory": user.client_kdf_memory,
- "KdfParallelism": user.client_kdf_parallelism,
+ "KdfIterations": user.client_kdf_iter(),
+ "KdfMemory": user.client_kdf_memory(),
+ "KdfParallelism": user.client_kdf_parallelism(),
"ResetMasterPassword": false, // TODO: Same as above
"scope": "api",
"unofficialServer": true,
@@ -457,7 +457,7 @@ struct ConnectData {
#[field(name = uncased("two_factor_remember"))]
#[field(name = uncased("twofactorremember"))]
#[allow(dead_code)]
- two_factor_remember: Option<i32>,
+ two_factor_remember: Option<u32>,
#[field(name = uncased("authrequest"))]
auth_request: Option<String>,
}
diff --git a/src/auth.rs b/src/auth.rs
@@ -478,7 +478,9 @@ impl<'r> FromRequest<'r> for Headers {
// Check if the stamp exception has expired first.
// Then, check if the current route matches any of the allowed routes.
// After that check the stamp in exception matches the one in the claims.
- if Utc::now().naive_utc().timestamp() > stamp_exception.expire {
+ if u64::try_from(Utc::now().naive_utc().timestamp()).expect("underflow")
+ > stamp_exception.expire
+ {
// If the stamp exception has been expired remove it from the database.
// This prevents checking this stamp exception for new requests.
let mut user = user;
diff --git a/src/config.rs b/src/config.rs
@@ -26,7 +26,7 @@ pub enum ConfigErr {
De(de::Error),
Url(ParseError),
BadDomain,
- InvalidPasswordIterations(i32),
+ InvalidPasswordIterations(u32),
}
impl Display for ConfigErr {
#[inline]
@@ -78,7 +78,7 @@ struct ConfigFile {
db_connection_retries: Option<NonZeroU8>,
domain: String,
ip: IpAddr,
- password_iterations: Option<i32>,
+ password_iterations: Option<u32>,
port: u16,
tls: Tls,
web_vault_enabled: Option<bool>,
@@ -90,7 +90,7 @@ pub struct Config {
pub database_timeout: u16,
pub db_connection_retries: NonZeroU8,
pub domain: Url,
- pub password_iterations: i32,
+ pub password_iterations: u32,
pub rocket: rocket::Config,
pub web_vault_enabled: bool,
}
@@ -157,7 +157,7 @@ impl Config {
password_iterations: match config_file.password_iterations {
None => 600_000,
Some(count) => {
- if count < 100_000i32 {
+ if count < 100_000u32 {
return Err(ConfigErr::InvalidPasswordIterations(count));
}
count
diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs
@@ -548,15 +548,18 @@ impl Cipher {
}}
}
- pub async fn count_owned_by_user(user_uuid: &str, conn: &DbConn) -> i64 {
- db_run! {conn: {
- ciphers::table
- .filter(ciphers::user_uuid.eq(user_uuid))
- .count()
- .first::<i64>(conn)
- .ok()
- .unwrap_or(0)
- }}
+ pub async fn count_owned_by_user(user_uuid: &str, conn: &DbConn) -> u64 {
+ u64::try_from({
+ db_run! {conn: {
+ ciphers::table
+ .filter(ciphers::user_uuid.eq(user_uuid))
+ .count()
+ .first::<i64>(conn)
+ .ok()
+ .unwrap_or(0)
+ }}
+ })
+ .expect("underflow")
}
pub async fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
@@ -567,15 +570,18 @@ impl Cipher {
}}
}
- pub async fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 {
- db_run! {conn: {
- ciphers::table
- .filter(ciphers::organization_uuid.eq(org_uuid))
- .count()
- .first::<i64>(conn)
- .ok()
- .unwrap_or(0)
- }}
+ pub async fn count_by_org(org_uuid: &str, conn: &DbConn) -> u64 {
+ u64::try_from({
+ db_run! {conn: {
+ ciphers::table
+ .filter(ciphers::organization_uuid.eq(org_uuid))
+ .count()
+ .first::<i64>(conn)
+ .ok()
+ .unwrap_or(0)
+ }}
+ })
+ .expect("underflow")
}
pub async fn find_by_folder(folder_uuid: &str, conn: &DbConn) -> Vec<Self> {
diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs
@@ -227,15 +227,18 @@ impl Collection {
}}
}
- pub async fn count_by_org(org_uuid: &str, conn: &DbConn) -> i64 {
- db_run! { conn: {
- collections::table
- .filter(collections::org_uuid.eq(org_uuid))
- .count()
- .first::<i64>(conn)
- .ok()
- .unwrap_or(0)
- }}
+ pub async fn count_by_org(org_uuid: &str, conn: &DbConn) -> u64 {
+ u64::try_from({
+ db_run! { conn: {
+ collections::table
+ .filter(collections::org_uuid.eq(org_uuid))
+ .count()
+ .first::<i64>(conn)
+ .ok()
+ .unwrap_or(0)
+ }}
+ })
+ .expect("underflow")
}
pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs
@@ -190,9 +190,9 @@ impl Organization {
"Id": self.uuid,
"Identifier": null, // not supported by us
"Name": self.name,
- "Seats": 10i32, // The value doesn't matter, we don't check server-side
- "MaxCollections": 10i32, // The value doesn't matter, we don't check server-side
- "MaxStorageGb": 10i32, // The value doesn't matter, we don't check server-side
+ "Seats": 10u32, // The value doesn't matter, we don't check server-side
+ "MaxCollections": 10u32, // The value doesn't matter, we don't check server-side
+ "MaxStorageGb": 10u32, // The value doesn't matter, we don't check server-side
"Use2fa": true,
"UseDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
"UseEvents": false,
@@ -212,7 +212,7 @@ impl Organization {
"BusinessTaxNumber": null,
"BillingEmail": self.billing_email,
"Plan": "TeamsAnnually",
- "PlanType": 5i32, // TeamsAnnually plan
+ "PlanType": 5u32, // TeamsAnnually plan
"UsersGetPremium": true,
"Object": "organization",
})
@@ -356,8 +356,8 @@ impl UserOrganization {
"Id": self.org_uuid,
"Identifier": null, // Not supported
"Name": org.name,
- "Seats": 10i32, // The value doesn't matter, we don't check server-side
- "MaxCollections": 10i32, // The value doesn't matter, we don't check server-side
+ "Seats": 10u32, // The value doesn't matter, we don't check server-side
+ "MaxCollections": 10u32, // The value doesn't matter, we don't check server-side
"UsersGetPremium": true,
"Use2fa": true,
"UseDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
@@ -374,7 +374,7 @@ impl UserOrganization {
"UseSso": false, // Not supported
"ProviderId": null,
"ProviderName": null,
- "MaxStorageGb": 10i32, // The value doesn't matter, we don't check server-side
+ "MaxStorageGb": 10u32, // The value doesn't matter, we don't check server-side
"UserId": self.user_uuid,
"Key": self.akey,
"Status": self.status,
@@ -554,16 +554,19 @@ impl UserOrganization {
}}
}
- pub async fn count_accepted_and_confirmed_by_user(user_uuid: &str, conn: &DbConn) -> i64 {
- db_run! { conn: {
- users_organizations::table
- .filter(users_organizations::user_uuid.eq(user_uuid))
- .filter(users_organizations::status.eq(i32::from(UserOrgStatus::Accepted)))
- .or_filter(users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed)))
- .count()
- .first::<i64>(conn)
- .unwrap_or(0)
- }}
+ pub async fn count_accepted_and_confirmed_by_user(user_uuid: &str, conn: &DbConn) -> u64 {
+ u64::try_from({
+ db_run! { conn: {
+ users_organizations::table
+ .filter(users_organizations::user_uuid.eq(user_uuid))
+ .filter(users_organizations::status.eq(i32::from(UserOrgStatus::Accepted)))
+ .or_filter(users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed)))
+ .count()
+ .first::<i64>(conn)
+ .unwrap_or(0)
+ }}
+ })
+ .expect("underflow")
}
pub async fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
@@ -593,16 +596,19 @@ impl UserOrganization {
org_uuid: &str,
atype: UserOrgType,
conn: &DbConn,
- ) -> i64 {
- db_run! { conn: {
- users_organizations::table
- .filter(users_organizations::org_uuid.eq(org_uuid))
- .filter(users_organizations::atype.eq(i32::from(atype)))
- .filter(users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed)))
- .count()
- .first::<i64>(conn)
- .unwrap_or(0)
- }}
+ ) -> u64 {
+ u64::try_from({
+ db_run! { conn: {
+ users_organizations::table
+ .filter(users_organizations::org_uuid.eq(org_uuid))
+ .filter(users_organizations::atype.eq(i32::from(atype)))
+ .filter(users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed)))
+ .count()
+ .first::<i64>(conn)
+ .unwrap_or(0)
+ }}
+ })
+ .expect("underflow")
}
pub async fn find_by_user_and_org(
diff --git a/src/db/models/two_factor.rs b/src/db/models/two_factor.rs
@@ -11,7 +11,15 @@ db_object! {
pub atype: i32,
pub enabled: bool,
pub data: String,
- pub last_used: i32,
+ last_used: i32,
+ }
+}
+impl TwoFactor {
+ pub fn last_used(&self) -> u32 {
+ u32::try_from(self.last_used).expect("underflow")
+ }
+ pub fn set_last_used(&mut self, last: u32) {
+ self.last_used = i32::try_from(last).expect("overflow");
}
}
diff --git a/src/db/models/user.rs b/src/db/models/user.rs
@@ -15,14 +15,14 @@ db_object! {
pub updated_at: NaiveDateTime,
pub verified_at: Option<NaiveDateTime>,
pub last_verifying_at: Option<NaiveDateTime>,
- pub login_verify_count: i32,
+ login_verify_count: i32,
pub email: String,
pub email_new: Option<String>,
pub email_new_token: Option<String>,
pub name: String,
pub password_hash: Vec<u8>,
pub salt: Vec<u8>,
- pub password_iterations: i32,
+ password_iterations: i32,
pub password_hint: Option<String>,
pub akey: String,
pub private_key: Option<String>,
@@ -35,9 +35,9 @@ db_object! {
pub equivalent_domains: String,
pub excluded_globals: String,
pub client_kdf_type: i32,
- pub client_kdf_iter: i32,
- pub client_kdf_memory: Option<i32>,
- pub client_kdf_parallelism: Option<i32>,
+ client_kdf_iter: i32,
+ client_kdf_memory: Option<i32>,
+ client_kdf_parallelism: Option<i32>,
pub api_key: Option<String>,
pub avatar_color: Option<String>,
pub external_id: Option<String>, // Todo: Needs to be removed in the future, this is not used anymore.
@@ -76,7 +76,7 @@ impl From<UserStatus> for i32 {
pub struct UserStampException {
pub routes: Vec<String>,
pub security_stamp: String,
- pub expire: i64,
+ pub expire: u64,
}
/// Local methods
@@ -84,7 +84,7 @@ impl User {
pub fn client_kdf_type_default() -> i32 {
i32::from(UserKdfType::Pbkdf2)
}
- pub const CLIENT_KDF_ITER_DEFAULT: i32 = 600_000i32;
+ pub const CLIENT_KDF_ITER_DEFAULT: u32 = 600_000u32;
pub fn new(email: &str) -> Self {
let now = Utc::now().naive_utc();
@@ -104,7 +104,8 @@ impl User {
email_new_token: None,
password_hash: Vec::new(),
salt: crypto::get_random_bytes::<64>().to_vec(),
- password_iterations: config::get_config().password_iterations,
+ password_iterations: i32::try_from(config::get_config().password_iterations)
+ .expect("overflow"),
security_stamp: crate::util::get_uuid(),
stamp_exception: None,
password_hint: None,
@@ -115,7 +116,7 @@ impl User {
equivalent_domains: "[]".to_owned(),
excluded_globals: "[]".to_owned(),
client_kdf_type: Self::client_kdf_type_default(),
- client_kdf_iter: Self::CLIENT_KDF_ITER_DEFAULT,
+ client_kdf_iter: i32::try_from(Self::CLIENT_KDF_ITER_DEFAULT).expect("overflow"),
client_kdf_memory: None,
client_kdf_parallelism: None,
api_key: None,
@@ -123,6 +124,38 @@ impl User {
external_id: None, // Todo: Needs to be removed in the future, this is not used anymore.
}
}
+ pub fn login_verify_count(&self) -> u32 {
+ u32::try_from(self.login_verify_count).expect("underflow")
+ }
+ pub fn set_login_verify_count(&mut self, count: u32) {
+ self.login_verify_count = i32::try_from(count).expect("overflow");
+ }
+ pub fn password_iterations(&self) -> u32 {
+ u32::try_from(self.password_iterations).expect("underflow")
+ }
+ pub fn set_password_iterations(&mut self, iter: u32) {
+ self.password_iterations = i32::try_from(iter).expect("overflow");
+ }
+ pub fn client_kdf_iter(&self) -> u32 {
+ u32::try_from(self.client_kdf_iter).expect("underflow")
+ }
+ pub fn set_client_kdf_iter(&mut self, iter: u32) {
+ self.password_iterations = i32::try_from(iter).expect("overflow");
+ }
+ pub fn client_kdf_memory(&self) -> Option<u32> {
+ self.client_kdf_memory
+ .map(|mem| u32::try_from(mem).expect("underflow"))
+ }
+ pub fn set_client_kdf_memory(&mut self, mem: Option<u32>) {
+ self.client_kdf_memory = mem.map(|kdf| i32::try_from(kdf).expect("overflow"));
+ }
+ pub fn client_kdf_parallelism(&self) -> Option<u32> {
+ self.client_kdf_parallelism
+ .map(|mem| u32::try_from(mem).expect("underflow"))
+ }
+ pub fn set_client_kdf_parallelism(&mut self, par: Option<u32>) {
+ self.client_kdf_parallelism = par.map(|pll| i32::try_from(pll).expect("overflow"));
+ }
pub fn check_valid_password(&self, password: &str) -> bool {
crypto::verify_password_hash(
@@ -194,11 +227,14 @@ impl User {
let stamp_exception = UserStampException {
routes: route_exception,
security_stamp: self.security_stamp.clone(),
- expire: (Utc::now()
- .naive_utc()
- .checked_add_signed(Duration::minutes(2)))
- .expect("Duration add overflowed")
- .timestamp(),
+ expire: u64::try_from(
+ (Utc::now()
+ .naive_utc()
+ .checked_add_signed(Duration::minutes(2)))
+ .expect("Duration add overflowed")
+ .timestamp(),
+ )
+ .expect("underflow"),
};
self.stamp_exception = Some(serde_json::to_string(&stamp_exception).unwrap_or_default());
}