vw_small

Hardened fork of Vaultwarden (https://github.com/dani-garcia/vaultwarden) with fewer features.
git clone https://git.philomathiclife.com/repos/vw_small
Log | Files | Refs | README

commit 6a99849a1ec97a5db11b3f5721e64737ed20256e
parent 172f1770cfd927d16e55383a321455e0f41d4e0a
Author: Daniel GarcĂ­a <dani-garcia@users.noreply.github.com>
Date:   Wed, 19 Dec 2018 21:52:53 +0100

Implemented proper error handling, now we can do `user.save($conn)?;` and it works.
In the future, maybe we can do the same with the `find_by_id` methods that return an Option.

Diffstat:
Msrc/api/admin.rs | 18+++++++++++++-----
Msrc/api/core/accounts.rs | 60++++++++++++++----------------------------------------------
Msrc/api/core/ciphers.rs | 100+++++++++++++++++++++----------------------------------------------------------
Msrc/api/core/folders.rs | 19++++++-------------
Msrc/api/core/mod.rs | 7+++----
Msrc/api/core/organizations.rs | 111+++++++++++++++++++------------------------------------------------------------
Msrc/api/core/two_factor.rs | 103+++++++++++++++++++++++++++++++------------------------------------------------
Msrc/api/identity.rs | 41+++++++++++++++++++----------------------
Msrc/api/mod.rs | 7+++----
Msrc/db/models/attachment.rs | 36+++++++++++++++++++-----------------
Msrc/db/models/cipher.rs | 64+++++++++++++++++++++++++++++-----------------------------------
Msrc/db/models/collection.rs | 54+++++++++++++++++++++++++++++++++---------------------
Msrc/db/models/device.rs | 14+++++++++-----
Msrc/db/models/folder.rs | 35++++++++++++++++++++++-------------
Msrc/db/models/organization.rs | 35+++++++++++++++++++++--------------
Msrc/db/models/two_factor.rs | 12+++++++++---
Msrc/db/models/user.rs | 30++++++++++++++++++------------
Asrc/error.rs | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mail.rs | 9++++++---
Msrc/main.rs | 3+--
Msrc/static/admin.html | 4++--
Msrc/util.rs | 47-----------------------------------------------
22 files changed, 475 insertions(+), 490 deletions(-)

diff --git a/src/api/admin.rs b/src/api/admin.rs @@ -36,7 +36,17 @@ fn invite_user(data: JsonUpcase<InviteData>, _token: AdminToken, conn: DbConn) - err!("User already exists") } - err!("Unimplemented") + if !CONFIG.invitations_allowed { + err!("Invitations are not allowed") + } + + let mut invitation = Invitation::new(data.Email.clone()); + let mut user = User::new(data.Email); + + invitation.save(&conn)?; + user.save(&conn)?; + + Ok(Json(json!({}))) } #[post("/users/<uuid>/delete")] @@ -46,10 +56,8 @@ fn delete_user(uuid: String, _token: AdminToken, conn: DbConn) -> JsonResult { None => err!("User doesn't exist"), }; - match user.delete(&conn) { - Ok(_) => Ok(Json(json!({}))), - Err(e) => err!("Error deleting user", e), - } + user.delete(&conn)?; + Ok(Json(json!({}))) } pub struct AdminToken {} diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs @@ -65,9 +65,7 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { if CONFIG.mail.is_none() { for mut user_org in UserOrganization::find_invited_by_user(&user.uuid, &conn).iter_mut() { user_org.status = UserOrgStatus::Accepted as i32; - if user_org.save(&conn).is_err() { - err!("Failed to accept user to organization") - } + user_org.save(&conn)?; } if !Invitation::take(&data.Email, &conn) { err!("Error accepting invitation") @@ -128,10 +126,7 @@ fn register(data: JsonUpcase<RegisterData>, conn: DbConn) -> EmptyResult { user.public_key = Some(keys.PublicKey); } - match user.save(&conn) { - Ok(()) => Ok(()), - Err(_) => err!("Failed to save user"), - } + user.save(&conn) } #[get("/accounts/profile")] @@ -164,10 +159,8 @@ fn post_profile(data: JsonUpcase<ProfileData>, headers: Headers, conn: DbConn) - Some(ref h) if h.is_empty() => None, _ => data.MasterPasswordHint, }; - match user.save(&conn) { - Ok(()) => Ok(Json(user.to_json(&conn))), - Err(_) => err!("Failed to save user profile"), - } + user.save(&conn)?; + Ok(Json(user.to_json(&conn))) } #[get("/users/<uuid>/public-key")] @@ -193,10 +186,8 @@ fn post_keys(data: JsonUpcase<KeysData>, headers: Headers, conn: DbConn) -> Json user.private_key = Some(data.EncryptedPrivateKey); user.public_key = Some(data.PublicKey); - match user.save(&conn) { - Ok(()) => Ok(Json(user.to_json(&conn))), - Err(_) => err!("Failed to save the user's keys"), - } + user.save(&conn)?; + Ok(Json(user.to_json(&conn))) } #[derive(Deserialize)] @@ -218,10 +209,7 @@ fn post_password(data: JsonUpcase<ChangePassData>, headers: Headers, conn: DbCon user.set_password(&data.NewMasterPasswordHash); user.key = data.Key; - match user.save(&conn) { - Ok(()) => Ok(()), - Err(_) => err!("Failed to save password"), - } + user.save(&conn) } #[derive(Deserialize)] @@ -248,10 +236,7 @@ fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, conn: DbConn) -> user.client_kdf_type = data.Kdf; user.set_password(&data.NewMasterPasswordHash); user.key = data.Key; - match user.save(&conn) { - Ok(()) => Ok(()), - Err(_) => err!("Failed to save password settings"), - } + user.save(&conn) } #[derive(Deserialize)] @@ -295,9 +280,7 @@ fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, ws: } saved_folder.name = folder_data.Name; - if saved_folder.save(&conn).is_err() { - err!("Failed to save folder") - } + saved_folder.save(&conn)? } // Update cipher data @@ -323,11 +306,7 @@ fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, conn: DbConn, ws: user.private_key = Some(data.PrivateKey); user.reset_security_stamp(); - if user.save(&conn).is_err() { - err!("Failed modify user key"); - } - - Ok(()) + user.save(&conn) } #[post("/accounts/security-stamp", data = "<data>")] @@ -340,10 +319,7 @@ fn post_sstamp(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn) - } user.reset_security_stamp(); - match user.save(&conn) { - Ok(()) => Ok(()), - Err(_) => err!("Failed to reset security stamp"), - } + user.save(&conn) } #[derive(Deserialize)] @@ -398,10 +374,7 @@ fn post_email(data: JsonUpcase<ChangeEmailData>, headers: Headers, conn: DbConn) user.set_password(&data.NewMasterPasswordHash); user.key = data.Key; - match user.save(&conn) { - Ok(()) => Ok(()), - Err(_) => err!("Failed to save email address"), - } + user.save(&conn) } #[post("/accounts/delete", data = "<data>")] @@ -418,10 +391,7 @@ fn delete_account(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn err!("Invalid password") } - match user.delete(&conn) { - Ok(()) => Ok(()), - Err(_) => err!("Failed deleting user account, are you the only owner of some organization?"), - } + user.delete(&conn) } #[get("/accounts/revision-date")] @@ -446,9 +416,7 @@ fn password_hint(data: JsonUpcase<PasswordHintData>, conn: DbConn) -> EmptyResul }; if let Some(ref mail_config) = CONFIG.mail { - if let Err(e) = mail::send_password_hint(&data.Email, hint, mail_config) { - err!(format!("There have been a problem sending the email: {}", e)); - } + mail::send_password_hint(&data.Email, hint, mail_config)?; } else if CONFIG.show_password_hint { if let Some(hint) = hint { err!(format!("Your password hint is: {}", &hint)); diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs @@ -182,10 +182,7 @@ fn post_ciphers_admin(data: JsonUpcase<ShareCipherData>, headers: Headers, conn: let mut cipher = Cipher::new(data.Cipher.Type, data.Cipher.Name.clone()); cipher.user_uuid = Some(headers.user.uuid.clone()); - match cipher.save(&conn) { - Ok(()) => (), - Err(_) => err!("Failed saving cipher") - }; + cipher.save(&conn)?; share_cipher_by_uuid(&cipher.uuid, data, &headers, &conn, &ws) } @@ -248,10 +245,7 @@ pub fn update_cipher_from_data(cipher: &mut Cipher, data: CipherData, headers: & saved_att.key = Some(attachment.Key); saved_att.file_name = attachment.FileName; - match saved_att.save(&conn) { - Ok(()) => (), - Err(_) => err!("Failed to save attachment") - }; + saved_att.save(&conn)?; } } @@ -284,17 +278,11 @@ pub fn update_cipher_from_data(cipher: &mut Cipher, data: CipherData, headers: & cipher.data = type_data.to_string(); cipher.password_history = data.PasswordHistory.map(|f| f.to_string()); - match cipher.save(&conn) { - Ok(()) => (), - Err(_) => err!("Failed to save cipher") - }; - ws.send_cipher_update(ut, &cipher, &cipher.update_users_revision(&conn)); + cipher.save(&conn)?; - if cipher.move_to_folder(data.FolderId, &headers.user.uuid, &conn).is_err() { - err!("Error saving the folder information") - } + ws.send_cipher_update(ut, &cipher, &cipher.update_users_revision(&conn)); - Ok(()) + cipher.move_to_folder(data.FolderId, &headers.user.uuid, &conn) } use super::folders::FolderData; @@ -325,11 +313,9 @@ fn post_ciphers_import(data: JsonUpcase<ImportData>, headers: Headers, conn: DbC let mut folders: Vec<_> = Vec::new(); for folder in data.Folders.into_iter() { let mut new_folder = Folder::new(headers.user.uuid.clone(), folder.Name); - if new_folder.save(&conn).is_err() { - err!("Failed importing folders") - } else { - folders.push(new_folder); - } + new_folder.save(&conn)?; + + folders.push(new_folder); } // Read the relations between folders and ciphers @@ -351,10 +337,7 @@ fn post_ciphers_import(data: JsonUpcase<ImportData>, headers: Headers, conn: DbC } let mut user = headers.user; - match user.update_revision(&conn) { - Ok(()) => Ok(()), - Err(_) => err!("Failed to update the revision, please log out and log back in to finish import.") - } + user.update_revision(&conn) } @@ -429,15 +412,9 @@ fn post_collections_admin(uuid: String, data: JsonUpcase<CollectionsAdminData>, Some(collection) => { if collection.is_writable_by_user(&headers.user.uuid, &conn) { if posted_collections.contains(&collection.uuid) { // Add to collection - match CollectionCipher::save(&cipher.uuid, &collection.uuid, &conn) { - Ok(()) => (), - Err(_) => err!("Failed to add cipher to collection") - }; + CollectionCipher::save(&cipher.uuid, &collection.uuid, &conn)?; } else { // Remove from collection - match CollectionCipher::delete(&cipher.uuid, &collection.uuid, &conn) { - Ok(()) => (), - Err(_) => err!("Failed to remove cipher from collection") - }; + CollectionCipher::delete(&cipher.uuid, &collection.uuid, &conn)?; } } else { err!("No rights to modify the collection") @@ -540,10 +517,7 @@ fn share_cipher_by_uuid(uuid: &str, data: ShareCipherData, headers: &Headers, co None => err!("Invalid collection ID provided"), Some(collection) => { if collection.is_writable_by_user(&headers.user.uuid, &conn) { - match CollectionCipher::save(&cipher.uuid.clone(), &collection.uuid, &conn) { - Ok(()) => (), - Err(_) => err!("Failed to add cipher to collection") - }; + CollectionCipher::save(&cipher.uuid.clone(), &collection.uuid, &conn)?; shared_to_collection = true; } else { err!("No rights to modify the collection") @@ -614,10 +588,7 @@ fn post_attachment(uuid: String, data: Data, content_type: &ContentType, headers let mut attachment = Attachment::new(file_name, cipher.uuid.clone(), name, size); attachment.key = attachment_key.clone(); - match attachment.save(&conn) { - Ok(()) => (), - Err(_) => error!("Failed to save attachment") - }; + attachment.save(&conn).expect("Error saving attachment"); }, _ => error!("Invalid multipart name") } @@ -746,13 +717,9 @@ fn move_cipher_selected(data: JsonUpcase<Value>, headers: Headers, conn: DbConn, } // Move cipher - if cipher.move_to_folder(folder_id.clone(), &headers.user.uuid, &conn).is_err() { - err!("Error saving the folder information") - } - match cipher.save(&conn) { - Ok(()) => (), - Err(_) => err!("Failed to save cipher") - }; + cipher.move_to_folder(folder_id.clone(), &headers.user.uuid, &conn)?; + cipher.save(&conn)?; + ws.send_cipher_update(UpdateType::SyncCipherUpdate, &cipher, &cipher.update_users_revision(&conn)); } @@ -777,21 +744,14 @@ fn delete_all(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbConn, ws // Delete ciphers and their attachments for cipher in Cipher::find_owned_by_user(&user.uuid, &conn) { - if cipher.delete(&conn).is_err() { - err!("Failed deleting cipher") - } - else { - ws.send_cipher_update(UpdateType::SyncCipherDelete, &cipher, &cipher.update_users_revision(&conn)); - } + cipher.delete(&conn)?; + ws.send_cipher_update(UpdateType::SyncCipherDelete, &cipher, &cipher.update_users_revision(&conn)); } // Delete folders for f in Folder::find_by_user(&user.uuid, &conn) { - if f.delete(&conn).is_err() { - err!("Failed deleting folder") - } else { - ws.send_folder_update(UpdateType::SyncFolderCreate, &f); - } + f.delete(&conn)?; + ws.send_folder_update(UpdateType::SyncFolderCreate, &f); } Ok(()) @@ -807,13 +767,9 @@ fn _delete_cipher_by_uuid(uuid: &str, headers: &Headers, conn: &DbConn, ws: &Sta err!("Cipher can't be deleted by user") } - match cipher.delete(&conn) { - Ok(()) => { - ws.send_cipher_update(UpdateType::SyncCipherDelete, &cipher, &cipher.update_users_revision(&conn)); - Ok(()) - } - Err(_) => err!("Failed deleting cipher") - } + cipher.delete(&conn)?; + ws.send_cipher_update(UpdateType::SyncCipherDelete, &cipher, &cipher.update_users_revision(&conn)); + Ok(()) } fn _delete_cipher_attachment_by_id(uuid: &str, attachment_id: &str, headers: &Headers, conn: &DbConn, ws: &State<WebSocketUsers>) -> EmptyResult { @@ -836,11 +792,7 @@ fn _delete_cipher_attachment_by_id(uuid: &str, attachment_id: &str, headers: &He } // Delete attachment - match attachment.delete(&conn) { - Ok(()) => { - ws.send_cipher_update(UpdateType::SyncCipherDelete, &cipher, &cipher.update_users_revision(&conn)); - Ok(()) - } - Err(_) => err!("Deleting attachment failed") - } + attachment.delete(&conn)?; + ws.send_cipher_update(UpdateType::SyncCipherDelete, &cipher, &cipher.update_users_revision(&conn)); + Ok(()) } diff --git a/src/api/core/folders.rs b/src/api/core/folders.rs @@ -62,9 +62,7 @@ fn post_folders(data: JsonUpcase<FolderData>, headers: Headers, conn: DbConn, ws let mut folder = Folder::new(headers.user.uuid.clone(), data.Name); - if folder.save(&conn).is_err() { - err!("Failed to save folder") - } + folder.save(&conn)?; ws.send_folder_update(UpdateType::SyncFolderCreate, &folder); Ok(Json(folder.to_json())) @@ -90,9 +88,7 @@ fn put_folder(uuid: String, data: JsonUpcase<FolderData>, headers: Headers, conn folder.name = data.Name; - if folder.save(&conn).is_err() { - err!("Failed to save folder") - } + folder.save(&conn)?; ws.send_folder_update(UpdateType::SyncFolderUpdate, &folder); Ok(Json(folder.to_json())) @@ -115,11 +111,8 @@ fn delete_folder(uuid: String, headers: Headers, conn: DbConn, ws: State<WebSock } // Delete the actual folder entry - match folder.delete(&conn) { - Ok(()) => { - ws.send_folder_update(UpdateType::SyncFolderDelete, &folder); - Ok(()) - } - Err(_) => err!("Failed deleting folder") - } + folder.delete(&conn)?; + + ws.send_folder_update(UpdateType::SyncFolderDelete, &folder); + Ok(()) } diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs @@ -120,10 +120,9 @@ fn post_eq_domains(data: JsonUpcase<EquivDomainData>, headers: Headers, conn: Db user.excluded_globals = to_string(&excluded_globals).unwrap_or("[]".to_string()); user.equivalent_domains = to_string(&equivalent_domains).unwrap_or("[]".to_string()); - match user.save(&conn) { - Ok(()) => Ok(Json(json!({}))), - Err(_) => err!("Failed to save user"), - } + user.save(&conn)?; + + Ok(Json(json!({}))) } #[put("/settings/domains", data = "<data>")] diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs @@ -93,16 +93,9 @@ fn create_organization(headers: Headers, data: JsonUpcase<OrgData>, conn: DbConn user_org.type_ = UserOrgType::Owner as i32; user_org.status = UserOrgStatus::Confirmed as i32; - if org.save(&conn).is_err() { - err!("Failed creating organization") - } - if user_org.save(&conn).is_err() { - err!("Failed to add user to organization") - } - - if collection.save(&conn).is_err() { - err!("Failed creating Collection"); - } + org.save(&conn)?; + user_org.save(&conn)?; + collection.save(&conn)?; Ok(Json(org.to_json())) } @@ -118,10 +111,7 @@ fn delete_organization(org_id: String, data: JsonUpcase<PasswordData>, headers: match Organization::find_by_uuid(&org_id, &conn) { None => err!("Organization not found"), - Some(org) => match org.delete(&conn) { - Ok(()) => Ok(()), - Err(_) => err!("Failed deleting the organization") - } + Some(org) => org.delete(&conn) } } @@ -145,10 +135,7 @@ fn leave_organization(org_id: String, headers: Headers, conn: DbConn) -> EmptyRe } } - match user_org.delete(&conn) { - Ok(()) => Ok(()), - Err(_) => err!("Failed leaving the organization") - } + user_org.delete(&conn) } } } @@ -178,10 +165,8 @@ fn post_organization(org_id: String, _headers: OwnerHeaders, data: JsonUpcase<Or org.name = data.Name; org.billing_email = data.BillingEmail; - match org.save(&conn) { - Ok(()) => Ok(Json(org.to_json())), - Err(_) => err!("Failed to modify organization") - } + org.save(&conn)?; + Ok(Json(org.to_json())) } // GET /api/collections?writeOnly=false @@ -222,10 +207,7 @@ fn post_organization_collections(org_id: String, _headers: AdminHeaders, data: J }; let mut collection = Collection::new(org.uuid.clone(), data.Name); - - if collection.save(&conn).is_err() { - err!("Failed saving Collection"); - } + collection.save(&conn)?; Ok(Json(collection.to_json())) } @@ -254,9 +236,7 @@ fn post_organization_collection_update(org_id: String, col_id: String, _headers: } collection.name = data.Name.clone(); - if collection.save(&conn).is_err() { - err!("Failed updating Collection"); - } + collection.save(&conn)?; Ok(Json(collection.to_json())) } @@ -279,10 +259,7 @@ fn delete_organization_collection_user(org_id: String, col_id: String, org_user_ match CollectionUser::find_by_collection_and_user(&collection.uuid, &user_org.user_uuid, &conn) { None => err!("User not assigned to collection"), Some(col_user) => { - match col_user.delete(&conn) { - Ok(()) => Ok(()), - Err(_) => err!("Failed removing user from collection") - } + col_user.delete(&conn) } } } @@ -299,10 +276,7 @@ fn delete_organization_collection(org_id: String, col_id: String, _headers: Admi match Collection::find_by_uuid(&col_id, &conn) { None => err!("Collection not found"), Some(collection) => if collection.org_uuid == org_id { - match collection.delete(&conn) { - Ok(()) => Ok(()), - Err(_) => err!("Failed deleting collection") - } + collection.delete(&conn) } else { err!("Collection and Organization id do not match") } @@ -435,18 +409,11 @@ fn send_invite(org_id: String, data: JsonUpcase<InviteData>, headers: AdminHeade let user = match User::find_by_mail(&email, &conn) { None => if CONFIG.invitations_allowed { // Invite user if that's enabled let mut invitation = Invitation::new(email.clone()); - match invitation.save(&conn) { - Ok(()) => { - let mut user = User::new(email.clone()); - if user.save(&conn).is_err() { - err!("Failed to create placeholder for invited user") - } else { - user_org_status = UserOrgStatus::Invited as i32; - user - } - } - Err(_) => err!(format!("Failed to invite: {}", email)) - } + invitation.save(&conn)?; + let mut user = User::new(email.clone()); + user.save(&conn)?; + user_org_status = UserOrgStatus::Invited as i32; + user } else { err!(format!("User email does not exist: {}", email)) @@ -474,17 +441,13 @@ fn send_invite(org_id: String, data: JsonUpcase<InviteData>, headers: AdminHeade match Collection::find_by_uuid_and_org(&col.Id, &org_id, &conn) { None => err!("Collection not found in Organization"), Some(collection) => { - if CollectionUser::save(&user.uuid, &collection.uuid, col.ReadOnly, &conn).is_err() { - err!("Failed saving collection access for user") - } + CollectionUser::save(&user.uuid, &collection.uuid, col.ReadOnly, &conn)?; } } } } - if new_user.save(&conn).is_err() { - err!("Failed to add user to organization") - } + new_user.save(&conn)?; org_user_id = Some(new_user.uuid.clone()); } @@ -627,10 +590,7 @@ fn confirm_invite(org_id: String, org_user_id: String, data: JsonUpcase<Value>, None => err!("Invalid key provided") }; - match user_to_confirm.save(&conn) { - Ok(()) => Ok(()), - Err(_) => err!("Failed to add user to organization") - } + user_to_confirm.save(&conn) } #[get("/organizations/<org_id>/users/<org_user_id>")] @@ -702,9 +662,7 @@ fn edit_user(org_id: String, org_user_id: String, data: JsonUpcase<EditUserData> // Delete all the odd collections for c in CollectionUser::find_by_organization_and_user_uuid(&org_id, &user_to_edit.user_uuid, &conn) { - if c.delete(&conn).is_err() { - err!("Failed deleting old collection assignment") - } + c.delete(&conn)?; } // If no accessAll, add the collections received @@ -713,18 +671,13 @@ fn edit_user(org_id: String, org_user_id: String, data: JsonUpcase<EditUserData> match Collection::find_by_uuid_and_org(&col.Id, &org_id, &conn) { None => err!("Collection not found in Organization"), Some(collection) => { - if CollectionUser::save(&user_to_edit.user_uuid, &collection.uuid, col.ReadOnly, &conn).is_err() { - err!("Failed saving collection access for user") - } + CollectionUser::save(&user_to_edit.user_uuid, &collection.uuid, col.ReadOnly, &conn)?; } } } } - match user_to_edit.save(&conn) { - Ok(()) => Ok(()), - Err(_) => err!("Failed to save user data") - } + user_to_edit.save(&conn) } #[delete("/organizations/<org_id>/users/<org_user_id>")] @@ -736,10 +689,7 @@ fn delete_user(org_id: String, org_user_id: String, headers: AdminHeaders, conn: if user_to_delete.uuid == headers.user.uuid { err!("Delete your account in the account settings") } else { - match user_to_delete.delete(&conn) { - Ok(()) => return Ok(()), - Err(_) => err!("Failed to delete user - likely because it's the only owner of organization") - } + user_to_delete.delete(&conn)?; } }, None => err!("User not found") @@ -767,10 +717,7 @@ fn delete_user(org_id: String, org_user_id: String, headers: AdminHeaders, conn: } } - match user_to_delete.delete(&conn) { - Ok(()) => Ok(()), - Err(_) => err!("Failed deleting user from organization") - } + user_to_delete.delete(&conn) } #[post("/organizations/<org_id>/users/<org_user_id>/delete")] @@ -844,15 +791,9 @@ fn post_org_import(query: Form<OrgIdData>, data: JsonUpcase<ImportData>, headers Err(_) => err!("Failed to assign to collection") }; - match CollectionCipher::save(cipher_id, coll_id, &conn) { - Ok(()) => (), - Err(_) => err!("Failed to add cipher to collection") - }; + CollectionCipher::save(cipher_id, coll_id, &conn)?; } let mut user = headers.user; - match user.update_revision(&conn) { - Ok(()) => Ok(()), - Err(_) => err!("Failed to update the revision, please log out and log back in to finish import.") - } + user.update_revision(&conn) } diff --git a/src/api/core/two_factor.rs b/src/api/core/two_factor.rs @@ -11,7 +11,7 @@ use crate::db::{ use crate::crypto; -use crate::api::{ApiResult, JsonResult, JsonUpcase, NumberOrString, PasswordData}; +use crate::api::{ApiResult, EmptyResult, JsonResult, JsonUpcase, NumberOrString, PasswordData}; use crate::auth::Headers; use rocket::Route; @@ -99,10 +99,8 @@ fn recover(data: JsonUpcase<RecoverTwoFactor>, conn: DbConn) -> JsonResult { // Remove the recovery code, not needed without twofactors user.totp_recover = None; - match user.save(&conn) { - Ok(()) => Ok(Json(json!({}))), - Err(_) => err!("Failed to remove the user's two factor recovery code") - } + user.save(&conn)?; + Ok(Json(json!({}))) } #[derive(Deserialize)] @@ -242,9 +240,7 @@ fn _generate_recover_code(user: &mut User, conn: &DbConn) { if user.totp_recover.is_none() { let totp_recover = BASE32.encode(&crypto::get_random(vec![0u8; 20])); user.totp_recover = Some(totp_recover); - if user.save(conn).is_err() { - error!("Failed to save the user's two factor recovery code") - } + user.save(conn).ok(); } } @@ -349,15 +345,11 @@ fn activate_u2f(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn) ); if let Some(tf_challenge) = tf_challenge { - let challenge: Challenge = serde_json::from_str(&tf_challenge.data) - .expect("Can't parse U2fRegisterChallenge data"); + let challenge: Challenge = serde_json::from_str(&tf_challenge.data)?; - tf_challenge - .delete(&conn) - .expect("Error deleting U2F register challenge"); + tf_challenge.delete(&conn)?; - let response_copy: RegisterResponseCopy = - serde_json::from_str(&data.DeviceResponse).expect("Can't parse RegisterResponse data"); + let response_copy: RegisterResponseCopy = serde_json::from_str(&data.DeviceResponse)?; let error_code = response_copy .error_code @@ -370,40 +362,31 @@ fn activate_u2f(data: JsonUpcase<EnableU2FData>, headers: Headers, conn: DbConn) let response = response_copy.into_response(challenge.challenge.clone()); - match U2F.register_response(challenge.clone(), response) { - Ok(registration) => { - // TODO: Allow more than one U2F device - let mut registrations = Vec::new(); - registrations.push(registration); - - let tf_registration = TwoFactor::new( - headers.user.uuid.clone(), - TwoFactorType::U2f, - serde_json::to_string(&registrations).unwrap(), - ); - tf_registration - .save(&conn) - .expect("Error saving U2F registration"); - - let mut user = headers.user; - _generate_recover_code(&mut user, &conn); - - Ok(Json(json!({ - "Enabled": true, - "Challenge": { - "UserId": user.uuid, - "AppId": APP_ID.to_string(), - "Challenge": challenge, - "Version": U2F_VERSION, - }, - "Object": "twoFactorU2f" - }))) - } - Err(e) => { - error!("{:#?}", e); - err!("Error activating u2f") - } - } + let registration = U2F.register_response(challenge.clone(), response)?; + // TODO: Allow more than one U2F device + let mut registrations = Vec::new(); + registrations.push(registration); + + let tf_registration = TwoFactor::new( + headers.user.uuid.clone(), + TwoFactorType::U2f, + serde_json::to_string(&registrations).unwrap(), + ); + tf_registration.save(&conn)?; + + let mut user = headers.user; + _generate_recover_code(&mut user, &conn); + + Ok(Json(json!({ + "Enabled": true, + "Challenge": { + "UserId": user.uuid, + "AppId": APP_ID.to_string(), + "Challenge": challenge, + "Version": U2F_VERSION, + }, + "Object": "twoFactorU2f" + }))) } else { err!("Can't recover challenge") } @@ -469,7 +452,7 @@ pub fn generate_u2f_login(user_uuid: &str, conn: &DbConn) -> ApiResult<U2fSignRe Ok(signed_request) } -pub fn validate_u2f_login(user_uuid: &str, response: &str, conn: &DbConn) -> ApiResult<()> { +pub fn validate_u2f_login(user_uuid: &str, response: &str, conn: &DbConn) -> EmptyResult { let challenge_type = TwoFactorType::U2fLoginChallenge as i32; let u2f_type = TwoFactorType::U2f as i32; @@ -477,11 +460,8 @@ pub fn validate_u2f_login(user_uuid: &str, response: &str, conn: &DbConn) -> Api let challenge = match tf_challenge { Some(tf_challenge) => { - let challenge: Challenge = serde_json::from_str(&tf_challenge.data) - .expect("Can't parse U2fLoginChallenge data"); - tf_challenge - .delete(&conn) - .expect("Error deleting U2F login challenge"); + let challenge: Challenge = serde_json::from_str(&tf_challenge.data)?; + tf_challenge.delete(&conn)?; challenge } None => err!("Can't recover login challenge"), @@ -494,8 +474,7 @@ pub fn validate_u2f_login(user_uuid: &str, response: &str, conn: &DbConn) -> Api let registrations = _parse_registrations(&twofactor.data); - let response: SignResponse = - serde_json::from_str(response).expect("Can't parse SignResponse data"); + let response: SignResponse = serde_json::from_str(response)?; let mut _counter: u32 = 0; for registration in registrations { @@ -614,8 +593,7 @@ fn generate_yubikey(data: JsonUpcase<PasswordData>, headers: Headers, conn: DbCo let r = TwoFactor::find_by_user_and_type(user_uuid, yubikey_type, &conn); if let Some(r) = r { - let yubikey_metadata: YubikeyMetadata = - serde_json::from_str(&r.data).expect("Can't parse YubikeyMetadata data"); + let yubikey_metadata: YubikeyMetadata = serde_json::from_str(&r.data)?; let mut result = jsonify_yubikeys(yubikey_metadata.Keys); @@ -648,7 +626,7 @@ fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: ); if let Some(yubikey_data) = yubikey_data { - yubikey_data.delete(&conn).expect("Error deleting current Yubikeys"); + yubikey_data.delete(&conn)?; } let yubikeys = parse_yubikeys(&data); @@ -686,8 +664,7 @@ fn activate_yubikey(data: JsonUpcase<EnableYubikeyData>, headers: Headers, conn: TwoFactorType::YubiKey, serde_json::to_string(&yubikey_metadata).unwrap(), ); - yubikey_registration - .save(&conn).expect("Failed to save Yubikey info"); + yubikey_registration.save(&conn)?; let mut result = jsonify_yubikeys(yubikey_metadata.Keys); @@ -703,7 +680,7 @@ fn activate_yubikey_put(data: JsonUpcase<EnableYubikeyData>, headers: Headers, c activate_yubikey(data, headers, conn) } -pub fn validate_yubikey_login(user_uuid: &str, response: &str, conn: &DbConn) -> ApiResult<()> { +pub fn validate_yubikey_login(user_uuid: &str, response: &str, conn: &DbConn) -> EmptyResult { if response.len() != 44 { err!("Invalid Yubikey OTP length"); } diff --git a/src/api/identity.rs b/src/api/identity.rs @@ -61,17 +61,16 @@ fn _refresh_login(data: ConnectData, conn: DbConn) -> JsonResult { let orgs = UserOrganization::find_by_user(&user.uuid, &conn); let (access_token, expires_in) = device.refresh_tokens(&user, orgs); - match device.save(&conn) { - Ok(()) => Ok(Json(json!({ - "access_token": access_token, - "expires_in": expires_in, - "token_type": "Bearer", - "refresh_token": device.refresh_token, - "Key": user.key, - "PrivateKey": user.private_key, - }))), - Err(e) => err!("Failed to add device to user", e), - } + + device.save(&conn)?; + Ok(Json(json!({ + "access_token": access_token, + "expires_in": expires_in, + "token_type": "Bearer", + "refresh_token": device.refresh_token, + "Key": user.key, + "PrivateKey": user.private_key, + }))) } fn _password_login(data: ConnectData, conn: DbConn, ip: ClientIp) -> JsonResult { @@ -85,19 +84,19 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: ClientIp) -> JsonResult let username = data.username.as_ref().unwrap(); let user = match User::find_by_mail(username, &conn) { Some(user) => user, - None => err!(format!( - "Username or password is incorrect. Try again. IP: {}. Username: {}.", - ip.ip, username - )), + None => err!( + "Username or password is incorrect. Try again", + format!("IP: {}. Username: {}.", ip.ip, username) + ), }; // Check password let password = data.password.as_ref().unwrap(); if !user.check_valid_password(password) { - err!(format!( - "Username or password is incorrect. Try again. IP: {}. Username: {}.", - ip.ip, username - )) + err!( + "Username or password is incorrect. Try again", + format!("IP: {}. Username: {}.", ip.ip, username) + ) } // On iOS, device_type sends "iOS", on others it sends a number @@ -126,9 +125,7 @@ fn _password_login(data: ConnectData, conn: DbConn, ip: ClientIp) -> JsonResult let orgs = UserOrganization::find_by_user(&user.uuid, &conn); let (access_token, expires_in) = device.refresh_tokens(&user, orgs); - if let Err(e) = device.save(&conn) { - err!("Failed to add device to user", e) - } + device.save(&conn)?; let mut result = json!({ "access_token": access_token, diff --git a/src/api/mod.rs b/src/api/mod.rs @@ -13,14 +13,13 @@ pub use self::web::routes as web_routes; pub use self::notifications::routes as notifications_routes; pub use self::notifications::{start_notification_server, WebSocketUsers, UpdateType}; -use rocket::response::status::BadRequest; use rocket_contrib::json::Json; use serde_json::Value; // Type aliases for API methods results -type ApiResult<T> = Result<T, BadRequest<Json<Value>>>; -type JsonResult = ApiResult<Json<Value>>; -type EmptyResult = ApiResult<()>; +type ApiResult<T> = Result<T, crate::error::Error>; +pub type JsonResult = ApiResult<Json<Value>>; +pub type EmptyResult = ApiResult<()>; use crate::util; type JsonUpcase<T> = Json<util::UpCase<T>>; diff --git a/src/db/models/attachment.rs b/src/db/models/attachment.rs @@ -12,7 +12,7 @@ pub struct Attachment { pub cipher_uuid: String, pub file_name: String, pub file_size: i32, - pub key: Option<String> + pub key: Option<String>, } /// Local methods @@ -23,7 +23,7 @@ impl Attachment { cipher_uuid, file_name, file_size, - key: None + key: None, } } @@ -54,29 +54,31 @@ use diesel::prelude::*; use crate::db::DbConn; use crate::db::schema::attachments; +use crate::api::EmptyResult; +use crate::error::MapResult; + /// Database methods impl Attachment { - pub fn save(&self, conn: &DbConn) -> QueryResult<()> { + pub fn save(&self, conn: &DbConn) -> EmptyResult { diesel::replace_into(attachments::table) .values(self) .execute(&**conn) - .and(Ok(())) + .map_res("Error saving attachment") } - pub fn delete(self, conn: &DbConn) -> QueryResult<()> { + pub fn delete(self, conn: &DbConn) -> EmptyResult { crate::util::retry( - || { - diesel::delete(attachments::table.filter(attachments::id.eq(&self.id))) - .execute(&**conn) - }, + || diesel::delete(attachments::table.filter(attachments::id.eq(&self.id))) + .execute(&**conn), 10, - )?; - + ) + .map_res("Error deleting attachment")?; + crate::util::delete_file(&self.get_file_path()); Ok(()) } - pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> QueryResult<()> { + pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult { for attachment in Attachment::find_by_cipher(&cipher_uuid, &conn) { attachment.delete(&conn)?; } @@ -84,20 +86,20 @@ impl Attachment { } pub fn find_by_id(id: &str, conn: &DbConn) -> Option<Self> { - attachments::table - .filter(attachments::id.eq(id)) - .first::<Self>(&**conn).ok() + attachments::table.filter(attachments::id.eq(id)).first::<Self>(&**conn).ok() } pub fn find_by_cipher(cipher_uuid: &str, conn: &DbConn) -> Vec<Self> { attachments::table .filter(attachments::cipher_uuid.eq(cipher_uuid)) - .load::<Self>(&**conn).expect("Error loading attachments") + .load::<Self>(&**conn) + .expect("Error loading attachments") } pub fn find_by_ciphers(cipher_uuids: Vec<String>, conn: &DbConn) -> Vec<Self> { attachments::table .filter(attachments::cipher_uuid.eq_any(cipher_uuids)) - .load::<Self>(&**conn).expect("Error loading attachments") + .load::<Self>(&**conn) + .expect("Error loading attachments") } } diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs @@ -1,7 +1,7 @@ use chrono::{NaiveDateTime, Utc}; use serde_json::Value; -use super::{User, Organization, Attachment, FolderCipher, CollectionCipher, UserOrganization, UserOrgType, UserOrgStatus}; +use super::{Attachment, CollectionCipher, FolderCipher, Organization, User, UserOrgStatus, UserOrgType, UserOrganization}; #[derive(Debug, Identifiable, Queryable, Insertable, Associations)] #[table_name = "ciphers"] @@ -59,17 +59,20 @@ impl Cipher { } } +use crate::db::schema::*; +use crate::db::DbConn; use diesel; use diesel::prelude::*; -use crate::db::DbConn; -use crate::db::schema::*; + +use crate::api::EmptyResult; +use crate::error::MapResult; /// Database methods impl Cipher { pub fn to_json(&self, host: &str, user_uuid: &str, conn: &DbConn) -> Value { - use serde_json; - use crate::util::format_date; use super::Attachment; + use crate::util::format_date; + use serde_json; let attachments = Attachment::find_by_cipher(&self.uuid, conn); let attachments_json: Vec<Value> = attachments.iter().map(|c| c.to_json(host)).collect(); @@ -149,56 +152,54 @@ impl Cipher { user_uuids } - pub fn save(&mut self, conn: &DbConn) -> QueryResult<()> { + pub fn save(&mut self, conn: &DbConn) -> EmptyResult { self.update_users_revision(conn); self.updated_at = Utc::now().naive_utc(); diesel::replace_into(ciphers::table) .values(&*self) .execute(&**conn) - .and(Ok(())) + .map_res("Error saving cipher") } - pub fn delete(&self, conn: &DbConn) -> QueryResult<()> { + pub fn delete(&self, conn: &DbConn) -> EmptyResult { self.update_users_revision(conn); FolderCipher::delete_all_by_cipher(&self.uuid, &conn)?; CollectionCipher::delete_all_by_cipher(&self.uuid, &conn)?; Attachment::delete_all_by_cipher(&self.uuid, &conn)?; - diesel::delete( - ciphers::table.filter( - ciphers::uuid.eq(&self.uuid) - ) - ).execute(&**conn).and(Ok(())) + diesel::delete(ciphers::table.filter(ciphers::uuid.eq(&self.uuid))) + .execute(&**conn) + .map_res("Error deleting cipher") } - pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> QueryResult<()> { + pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult { for cipher in Self::find_by_org(org_uuid, &conn) { cipher.delete(&conn)?; } Ok(()) } - pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> QueryResult<()> { + pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { for cipher in Self::find_owned_by_user(user_uuid, &conn) { cipher.delete(&conn)?; } Ok(()) } - pub fn move_to_folder(&self, folder_uuid: Option<String>, user_uuid: &str, conn: &DbConn) -> Result<(), &str> { - match self.get_folder_uuid(&user_uuid, &conn) { + pub fn move_to_folder(&self, folder_uuid: Option<String>, user_uuid: &str, conn: &DbConn) -> EmptyResult { + match self.get_folder_uuid(&user_uuid, &conn) { None => { match folder_uuid { Some(new_folder) => { self.update_users_revision(conn); let folder_cipher = FolderCipher::new(&new_folder, &self.uuid); - folder_cipher.save(&conn).or(Err("Couldn't save folder setting")) - }, - None => Ok(()) //nothing to do + folder_cipher.save(&conn) + } + None => Ok(()), //nothing to do } - }, + } Some(current_folder) => { match folder_uuid { Some(new_folder) => { @@ -206,24 +207,17 @@ impl Cipher { Ok(()) //nothing to do } else { self.update_users_revision(conn); - match FolderCipher::find_by_folder_and_cipher(&current_folder, &self.uuid, &conn) { - Some(current_folder) => { - current_folder.delete(&conn).or(Err("Failed removing old folder mapping")) - }, - None => Ok(()) // Weird, but nothing to do - }.and_then( - |()| FolderCipher::new(&new_folder, &self.uuid) - .save(&conn).or(Err("Couldn't save folder setting")) - ) + if let Some(current_folder) = FolderCipher::find_by_folder_and_cipher(&current_folder, &self.uuid, &conn) { + current_folder.delete(&conn)?; + } + FolderCipher::new(&new_folder, &self.uuid).save(&conn) } - }, + } None => { self.update_users_revision(conn); match FolderCipher::find_by_folder_and_cipher(&current_folder, &self.uuid, &conn) { - Some(current_folder) => { - current_folder.delete(&conn).or(Err("Failed removing old folder mapping")) - }, - None => Err("Couldn't move from previous folder") + Some(current_folder) => current_folder.delete(&conn), + None => err!("Couldn't move from previous folder"), } } } diff --git a/src/db/models/collection.rs b/src/db/models/collection.rs @@ -38,9 +38,12 @@ use diesel::prelude::*; use crate::db::DbConn; use crate::db::schema::*; +use crate::api::EmptyResult; +use crate::error::MapResult; + /// Database methods impl Collection { - pub fn save(&mut self, conn: &DbConn) -> QueryResult<()> { + pub fn save(&mut self, conn: &DbConn) -> EmptyResult { // Update affected users revision UserOrganization::find_by_collection_and_org(&self.uuid, &self.org_uuid, conn) .iter() @@ -51,10 +54,10 @@ impl Collection { diesel::replace_into(collections::table) .values(&*self) .execute(&**conn) - .and(Ok(())) + .map_res("Error saving collection") } - pub fn delete(self, conn: &DbConn) -> QueryResult<()> { + pub fn delete(self, conn: &DbConn) -> EmptyResult { CollectionCipher::delete_all_by_collection(&self.uuid, &conn)?; CollectionUser::delete_all_by_collection(&self.uuid, &conn)?; @@ -62,10 +65,11 @@ impl Collection { collections::table.filter( collections::uuid.eq(self.uuid) ) - ).execute(&**conn).and(Ok(())) + ).execute(&**conn) + .map_res("Error deleting collection") } - pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> QueryResult<()> { + pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult { for collection in Self::find_by_organization(org_uuid, &conn) { collection.delete(&conn)?; } @@ -185,7 +189,7 @@ impl CollectionUser { .load::<Self>(&**conn).expect("Error loading users_collections") } - pub fn save(user_uuid: &str, collection_uuid: &str, read_only:bool, conn: &DbConn) -> QueryResult<()> { + pub fn save(user_uuid: &str, collection_uuid: &str, read_only:bool, conn: &DbConn) -> EmptyResult { User::update_uuid_revision(&user_uuid, conn); diesel::replace_into(users_collections::table) @@ -193,16 +197,18 @@ impl CollectionUser { users_collections::user_uuid.eq(user_uuid), users_collections::collection_uuid.eq(collection_uuid), users_collections::read_only.eq(read_only), - )).execute(&**conn).and(Ok(())) + )).execute(&**conn) + .map_res("Error adding user to collection") } - pub fn delete(self, conn: &DbConn) -> QueryResult<()> { + pub fn delete(self, conn: &DbConn) -> EmptyResult { User::update_uuid_revision(&self.user_uuid, conn); diesel::delete(users_collections::table .filter(users_collections::user_uuid.eq(&self.user_uuid)) .filter(users_collections::collection_uuid.eq(&self.collection_uuid))) - .execute(&**conn).and(Ok(())) + .execute(&**conn) + .map_res("Error removing user from collection") } pub fn find_by_collection(collection_uuid: &str, conn: &DbConn) -> Vec<Self> { @@ -220,7 +226,7 @@ impl CollectionUser { .first::<Self>(&**conn).ok() } - pub fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> QueryResult<()> { + pub fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult { CollectionUser::find_by_collection(&collection_uuid, conn) .iter() .for_each(|collection| { @@ -229,15 +235,17 @@ impl CollectionUser { diesel::delete(users_collections::table .filter(users_collections::collection_uuid.eq(collection_uuid)) - ).execute(&**conn).and(Ok(())) + ).execute(&**conn) + .map_res("Error deleting users from collection") } - pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> QueryResult<()> { + pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { User::update_uuid_revision(&user_uuid, conn); diesel::delete(users_collections::table .filter(users_collections::user_uuid.eq(user_uuid)) - ).execute(&**conn).and(Ok(())) + ).execute(&**conn) + .map_res("Error removing user from collections") } } @@ -255,30 +263,34 @@ pub struct CollectionCipher { /// Database methods impl CollectionCipher { - pub fn save(cipher_uuid: &str, collection_uuid: &str, conn: &DbConn) -> QueryResult<()> { + pub fn save(cipher_uuid: &str, collection_uuid: &str, conn: &DbConn) -> EmptyResult { diesel::replace_into(ciphers_collections::table) .values(( ciphers_collections::cipher_uuid.eq(cipher_uuid), ciphers_collections::collection_uuid.eq(collection_uuid), - )).execute(&**conn).and(Ok(())) + )).execute(&**conn) + .map_res("Error adding cipher to collection") } - pub fn delete(cipher_uuid: &str, collection_uuid: &str, conn: &DbConn) -> QueryResult<()> { + pub fn delete(cipher_uuid: &str, collection_uuid: &str, conn: &DbConn) -> EmptyResult { diesel::delete(ciphers_collections::table .filter(ciphers_collections::cipher_uuid.eq(cipher_uuid)) .filter(ciphers_collections::collection_uuid.eq(collection_uuid))) - .execute(&**conn).and(Ok(())) + .execute(&**conn) + .map_res("Error deleting cipher from collection") } - pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> QueryResult<()> { + pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult { diesel::delete(ciphers_collections::table .filter(ciphers_collections::cipher_uuid.eq(cipher_uuid)) - ).execute(&**conn).and(Ok(())) + ).execute(&**conn) + .map_res("Error removing cipher from collections") } - pub fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> QueryResult<()> { + pub fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult { diesel::delete(ciphers_collections::table .filter(ciphers_collections::collection_uuid.eq(collection_uuid)) - ).execute(&**conn).and(Ok(())) + ).execute(&**conn) + .map_res("Error removing ciphers from collection") } } \ No newline at end of file diff --git a/src/db/models/device.rs b/src/db/models/device.rs @@ -110,9 +110,12 @@ use diesel::prelude::*; use crate::db::DbConn; use crate::db::schema::devices; +use crate::api::EmptyResult; +use crate::error::MapResult; + /// Database methods impl Device { - pub fn save(&mut self, conn: &DbConn) -> QueryResult<()> { + pub fn save(&mut self, conn: &DbConn) -> EmptyResult { self.updated_at = Utc::now().naive_utc(); crate::util::retry( @@ -123,16 +126,17 @@ impl Device { }, 10, ) - .and(Ok(())) + .map_res("Error saving device") } - pub fn delete(self, conn: &DbConn) -> QueryResult<()> { + pub fn delete(self, conn: &DbConn) -> EmptyResult { diesel::delete(devices::table.filter( devices::uuid.eq(self.uuid) - )).execute(&**conn).and(Ok(())) + )).execute(&**conn) + .map_res("Error removing device") } - pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> QueryResult<()> { + pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { for device in Self::find_by_user(user_uuid, &conn) { device.delete(&conn)?; } diff --git a/src/db/models/folder.rs b/src/db/models/folder.rs @@ -66,17 +66,21 @@ use diesel::prelude::*; use crate::db::DbConn; use crate::db::schema::{folders, folders_ciphers}; +use crate::api::EmptyResult; +use crate::error::MapResult; + /// Database methods impl Folder { - pub fn save(&mut self, conn: &DbConn) -> QueryResult<()> { + pub fn save(&mut self, conn: &DbConn) -> EmptyResult { User::update_uuid_revision(&self.user_uuid, conn); self.updated_at = Utc::now().naive_utc(); diesel::replace_into(folders::table) - .values(&*self).execute(&**conn).and(Ok(())) + .values(&*self).execute(&**conn) + .map_res("Error saving folder") } - pub fn delete(&self, conn: &DbConn) -> QueryResult<()> { + pub fn delete(&self, conn: &DbConn) -> EmptyResult { User::update_uuid_revision(&self.user_uuid, conn); FolderCipher::delete_all_by_folder(&self.uuid, &conn)?; @@ -84,10 +88,11 @@ impl Folder { folders::table.filter( folders::uuid.eq(&self.uuid) ) - ).execute(&**conn).and(Ok(())) + ).execute(&**conn) + .map_res("Error deleting folder") } - pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> QueryResult<()> { + pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { for folder in Self::find_by_user(user_uuid, &conn) { folder.delete(&conn)?; } @@ -108,29 +113,33 @@ impl Folder { } impl FolderCipher { - pub fn save(&self, conn: &DbConn) -> QueryResult<()> { + pub fn save(&self, conn: &DbConn) -> EmptyResult { diesel::replace_into(folders_ciphers::table) .values(&*self) - .execute(&**conn).and(Ok(())) + .execute(&**conn) + .map_res("Error adding cipher to folder") } - pub fn delete(self, conn: &DbConn) -> QueryResult<()> { + pub fn delete(self, conn: &DbConn) -> EmptyResult { diesel::delete(folders_ciphers::table .filter(folders_ciphers::cipher_uuid.eq(self.cipher_uuid)) .filter(folders_ciphers::folder_uuid.eq(self.folder_uuid)) - ).execute(&**conn).and(Ok(())) + ).execute(&**conn) + .map_res("Error removing cipher from folder") } - pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> QueryResult<()> { + pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult { diesel::delete(folders_ciphers::table .filter(folders_ciphers::cipher_uuid.eq(cipher_uuid)) - ).execute(&**conn).and(Ok(())) + ).execute(&**conn) + .map_res("Error removing cipher from folders") } - pub fn delete_all_by_folder(folder_uuid: &str, conn: &DbConn) -> QueryResult<()> { + pub fn delete_all_by_folder(folder_uuid: &str, conn: &DbConn) -> EmptyResult { diesel::delete(folders_ciphers::table .filter(folders_ciphers::folder_uuid.eq(folder_uuid)) - ).execute(&**conn).and(Ok(())) + ).execute(&**conn) + .map_res("Error removing ciphers from folder") } pub fn find_by_folder_and_cipher(folder_uuid: &str, cipher_uuid: &str, conn: &DbConn) -> Option<Self> { diff --git a/src/db/models/organization.rs b/src/db/models/organization.rs @@ -238,11 +238,14 @@ use diesel::prelude::*; use crate::db::DbConn; use crate::db::schema::{organizations, users_organizations, users_collections, ciphers_collections}; +use crate::api::EmptyResult; +use crate::error::MapResult; + /// Database methods impl Organization { - pub fn save(&mut self, conn: &DbConn) -> QueryResult<()> { + pub fn save(&mut self, conn: &DbConn) -> EmptyResult { if self.uuid == Organization::VIRTUAL_ID { - return Err(diesel::result::Error::NotFound) + err!("diesel::result::Error::NotFound") } UserOrganization::find_by_org(&self.uuid, conn) @@ -252,14 +255,15 @@ impl Organization { }); diesel::replace_into(organizations::table) - .values(&*self).execute(&**conn).and(Ok(())) + .values(&*self).execute(&**conn) + .map_res("Error saving organization") } - pub fn delete(self, conn: &DbConn) -> QueryResult<()> { + pub fn delete(self, conn: &DbConn) -> EmptyResult { use super::{Cipher, Collection}; if self.uuid == Organization::VIRTUAL_ID { - return Err(diesel::result::Error::NotFound) + err!("diesel::result::Error::NotFound") } Cipher::delete_all_by_organization(&self.uuid, &conn)?; @@ -270,7 +274,8 @@ impl Organization { organizations::table.filter( organizations::uuid.eq(self.uuid) ) - ).execute(&**conn).and(Ok(())) + ).execute(&**conn) + .map_res("Error saving organization") } pub fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { @@ -365,19 +370,20 @@ impl UserOrganization { }) } - pub fn save(&mut self, conn: &DbConn) -> QueryResult<()> { + pub fn save(&mut self, conn: &DbConn) -> EmptyResult { if self.org_uuid == Organization::VIRTUAL_ID { - return Err(diesel::result::Error::NotFound) + err!("diesel::result::Error::NotFound") } User::update_uuid_revision(&self.user_uuid, conn); diesel::replace_into(users_organizations::table) - .values(&*self).execute(&**conn).and(Ok(())) + .values(&*self).execute(&**conn) + .map_res("Error adding user to organization") } - pub fn delete(self, conn: &DbConn) -> QueryResult<()> { + pub fn delete(self, conn: &DbConn) -> EmptyResult { if self.org_uuid == Organization::VIRTUAL_ID { - return Err(diesel::result::Error::NotFound) + err!("diesel::result::Error::NotFound") } User::update_uuid_revision(&self.user_uuid, conn); @@ -387,17 +393,18 @@ impl UserOrganization { users_organizations::table.filter( users_organizations::uuid.eq(self.uuid) ) - ).execute(&**conn).and(Ok(())) + ).execute(&**conn) + .map_res("Error removing user from organization") } - pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> QueryResult<()> { + pub fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult { for user_org in Self::find_by_org(&org_uuid, &conn) { user_org.delete(&conn)?; } Ok(()) } - pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> QueryResult<()> { + pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { for user_org in Self::find_any_state_by_user(&user_uuid, &conn) { user_org.delete(&conn)?; } diff --git a/src/db/models/two_factor.rs b/src/db/models/two_factor.rs @@ -79,20 +79,25 @@ use diesel::prelude::*; use crate::db::DbConn; use crate::db::schema::twofactor; +use crate::api::EmptyResult; +use crate::error::MapResult; + /// Database methods impl TwoFactor { - pub fn save(&self, conn: &DbConn) -> QueryResult<usize> { + pub fn save(&self, conn: &DbConn) -> EmptyResult { diesel::replace_into(twofactor::table) .values(self) .execute(&**conn) + .map_res("Error saving twofactor") } - pub fn delete(self, conn: &DbConn) -> QueryResult<usize> { + pub fn delete(self, conn: &DbConn) -> EmptyResult { diesel::delete( twofactor::table.filter( twofactor::uuid.eq(self.uuid) ) ).execute(&**conn) + .map_res("Error deleting twofactor") } pub fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { @@ -108,11 +113,12 @@ impl TwoFactor { .first::<Self>(&**conn).ok() } - pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> QueryResult<usize> { + pub fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { diesel::delete( twofactor::table.filter( twofactor::user_uuid.eq(user_uuid) ) ).execute(&**conn) + .map_res("Error deleting twofactors") } } diff --git a/src/db/models/user.rs b/src/db/models/user.rs @@ -115,6 +115,9 @@ use crate::db::DbConn; use crate::db::schema::{users, invitations}; use super::{Cipher, Folder, Device, UserOrganization, UserOrgType, TwoFactor}; +use crate::api::EmptyResult; +use crate::error::MapResult; + /// Database methods impl User { pub fn to_json(&self, conn: &DbConn) -> Value { @@ -145,21 +148,22 @@ impl User { } - pub fn save(&mut self, conn: &DbConn) -> QueryResult<()> { + pub fn save(&mut self, conn: &DbConn) -> EmptyResult { self.updated_at = Utc::now().naive_utc(); diesel::replace_into(users::table) // Insert or update - .values(&*self).execute(&**conn).and(Ok(())) + .values(&*self).execute(&**conn) + .map_res("Error saving user") } - pub fn delete(self, conn: &DbConn) -> QueryResult<()> { + pub fn delete(self, conn: &DbConn) -> EmptyResult { for user_org in UserOrganization::find_by_user(&self.uuid, &*conn) { if user_org.type_ == UserOrgType::Owner { if UserOrganization::find_by_org_and_type( &user_org.org_uuid, UserOrgType::Owner as i32, &conn ).len() <= 1 { - return Err(diesel::result::Error::NotFound); + err!("Can't delete last owner") } } } @@ -168,12 +172,13 @@ impl User { Cipher::delete_all_by_user(&self.uuid, &*conn)?; Folder::delete_all_by_user(&self.uuid, &*conn)?; Device::delete_all_by_user(&self.uuid, &*conn)?; - TwoFactor::delete_all_by_user(&self.uuid, &*conn)?; + //TwoFactor::delete_all_by_user(&self.uuid, &*conn)?; Invitation::take(&self.email, &*conn); // Delete invitation if any diesel::delete(users::table.filter( users::uuid.eq(self.uuid))) - .execute(&**conn).and(Ok(())) + .execute(&**conn) + .map_res("Error deleting user") } pub fn update_uuid_revision(uuid: &str, conn: &DbConn) { @@ -184,7 +189,7 @@ impl User { }; } - pub fn update_revision(&mut self, conn: &DbConn) -> QueryResult<()> { + pub fn update_revision(&mut self, conn: &DbConn) -> EmptyResult { self.updated_at = Utc::now().naive_utc(); diesel::update( users::table.filter( @@ -192,7 +197,8 @@ impl User { ) ) .set(users::updated_at.eq(&self.updated_at)) - .execute(&**conn).and(Ok(())) + .execute(&**conn) + .map_res("Error updating user revision") } pub fn find_by_mail(mail: &str, conn: &DbConn) -> Option<Self> { @@ -228,18 +234,18 @@ impl Invitation { } } - pub fn save(&mut self, conn: &DbConn) -> QueryResult<()> { + pub fn save(&mut self, conn: &DbConn) -> EmptyResult { diesel::replace_into(invitations::table) .values(&*self) .execute(&**conn) - .and(Ok(())) + .map_res("Error saving invitation") } - pub fn delete(self, conn: &DbConn) -> QueryResult<()> { + pub fn delete(self, conn: &DbConn) -> EmptyResult { diesel::delete(invitations::table.filter( invitations::email.eq(self.email))) .execute(&**conn) - .and(Ok(())) + .map_res("Error deleting invitation") } pub fn find_by_mail(mail: &str, conn: &DbConn) -> Option<Self> { diff --git a/src/error.rs b/src/error.rs @@ -0,0 +1,156 @@ +// +// Error generator macro +// +macro_rules! make_error { + ( $struct:ident; $( $name:ident ( $ty:ty, _): $show_cause:expr, $usr_msg_fun:expr ),+ $(,)* ) => { + #[derive(Debug)] + #[allow(unused_variables, dead_code)] + pub enum $struct { + $($name( $ty, String )),+ + } + $(impl From<$ty> for $struct { + fn from(err: $ty) -> Self { + $struct::$name(err, String::from(stringify!($name))) + } + })+ + $(impl From<($ty, String)> for $struct { + fn from(err: ($ty, String)) -> Self { + $struct::$name(err.0, err.1) + } + })+ + impl $struct { + pub fn with_msg<M: Into<String>>(self, msg: M) -> Self { + match self {$( + $struct::$name(e, _) => $struct::$name(e, msg.into()), + )+} + } + // First value is log message, second is user message + pub fn display_error(self) -> String { + match &self {$( + $struct::$name(e, s) => { + let log_msg = format!("{}. {}", &s, &e); + + error!("{}", log_msg); + if $show_cause { + error!("[CAUSE] {:?}", e); + } + + $usr_msg_fun(e, s) + }, + )+} + } + } + + }; +} + +use diesel::result::{Error as DieselError, QueryResult}; +use serde_json::{Value, Error as SerError}; +use u2f::u2ferror::U2fError as U2fErr; + +// Error struct +// Each variant has two elements, the first is an error of different types, used for logging purposes +// The second is a String, and it's contents are displayed to the user when the error occurs. Inside the macro, this is represented as _ +// +// After the variant itself, there are two expressions. The first one is a bool to indicate whether the error cause will be printed to the log. +// The second one contains the function used to obtain the response sent to the client +make_error! { + Error; + // Used to represent err! calls + SimpleError(String, _): false, _api_error, + // Used for special return values, like 2FA errors + JsonError(Value, _): false, _serialize, + DbError(DieselError, _): true, _api_error, + U2fError(U2fErr, _): true, _api_error, + SerdeError(SerError, _): true, _api_error, + //WsError(ws::Error, _): true, _api_error, +} + +impl Error { + pub fn new<M: Into<String>, N: Into<String>>(usr_msg: M, log_msg: N) -> Self { + Error::SimpleError(log_msg.into(), usr_msg.into()) + } +} + +pub trait MapResult<S, E> { + fn map_res(self, msg: &str) -> Result<(), E>; +} + +impl MapResult<(), Error> for QueryResult<usize> { + fn map_res(self, msg: &str) -> Result<(), Error> { + self.and(Ok(())).map_err(Error::from).map_err(|e| e.with_msg(msg)) + } +} + +use serde::Serialize; +use std::any::Any; + +fn _serialize(e: &impl Serialize, _: &impl Any) -> String { + serde_json::to_string(e).unwrap() +} + +fn _api_error(_: &impl Any, msg: &str) -> String { + let json = json!({ + "Message": "", + "error": "", + "error_description": "", + "ValidationErrors": {"": [ msg ]}, + "ErrorModel": { + "Message": msg, + "Object": "error" + }, + "Object": "error" + }); + + _serialize(&json, &false) +} + +// +// Rocket responder impl +// +use std::io::Cursor; + +use rocket::http::{ContentType, Status}; +use rocket::request::Request; +use rocket::response::{self, Responder, Response}; + +impl<'r> Responder<'r> for Error { + fn respond_to(self, _: &Request) -> response::Result<'r> { + // TODO: We could put the security headers here + + let usr_msg = self.display_error(); + + Response::build() + .status(Status::BadRequest) + .header(ContentType::JSON) + .sized_body(Cursor::new(usr_msg)) + .ok() + } +} + +/// +/// Error return macros +/// +#[macro_export] +macro_rules! err { + ($msg:expr) => {{ + return Err(crate::error::Error::new($msg, $msg)); + }}; + ($usr_msg:expr, $log_value:expr) => {{ + return Err(crate::error::Error::new($usr_msg, $log_value)); + }}; +} + +#[macro_export] +macro_rules! err_json { + ($expr:expr) => {{ + return Err(crate::error::Error::from($expr)); + }}; +} + +#[macro_export] +macro_rules! err_handler { + ($expr:expr) => {{ + return rocket::Outcome::Failure((rocket::http::Status::Unauthorized, $expr)); + }}; +} diff --git a/src/mail.rs b/src/mail.rs @@ -7,6 +7,9 @@ use lettre_email::EmailBuilder; use crate::MailConfig; use crate::CONFIG; +use crate::api::EmptyResult; +use crate::error::Error; + fn mailer(config: &MailConfig) -> SmtpTransport { let client_security = if config.smtp_ssl { let tls = TlsConnector::builder() @@ -35,7 +38,7 @@ fn mailer(config: &MailConfig) -> SmtpTransport { .transport() } -pub fn send_password_hint(address: &str, hint: Option<String>, config: &MailConfig) -> Result<(), String> { +pub fn send_password_hint(address: &str, hint: Option<String>, config: &MailConfig) -> EmptyResult { let (subject, body) = if let Some(hint) = hint { ("Your master password hint", format!( @@ -54,11 +57,11 @@ pub fn send_password_hint(address: &str, hint: Option<String>, config: &MailConf .subject(subject) .body(body) .build() - .map_err(|e| e.to_string())?; + .map_err(|e| Error::new("Error building hint email", e.to_string()))?; mailer(config) .send(email.into()) - .map_err(|e| e.to_string()) + .map_err(|e| Error::new("Error sending hint email", e.to_string())) .and(Ok(())) } diff --git a/src/main.rs b/src/main.rs @@ -14,9 +14,8 @@ use std::{path::Path, process::{exit, Command}}; use rocket::Rocket; -#[macro_use] +#[macro_use] mod error; mod util; - mod api; mod db; mod crypto; diff --git a/src/static/admin.html b/src/static/admin.html @@ -42,7 +42,7 @@ function updateVis() { setVis("#no-key-form", !key); setVis("#users-block", key); - setVis("#invite-form", key); + setVis("#invite-form-block", key); } function setKey() { @@ -166,7 +166,7 @@ </small> </div> - <div id="invite-form" class="d-none align-items-center p-3 mb-3 text-white-50 bg-secondary rounded shadow"> + <div id="invite-form-block" class="d-none align-items-center p-3 mb-3 text-white-50 bg-secondary rounded shadow"> <div> <h6 class="mb-0 text-white">Invite User</h6> <small>Email:</small> diff --git a/src/util.rs b/src/util.rs @@ -1,51 +1,4 @@ /// -/// Macros -/// -#[macro_export] -macro_rules! _err_object { - ($msg:expr) => {{ - err_json!(json!({ - "Message": "", - "error": "", - "error_description": "", - "ValidationErrors": {"": [ $msg ]}, - "ErrorModel": { - "Message": $msg, - "Object": "error" - }, - "Object": "error" - })) - }}; -} - -#[macro_export] -macro_rules! err { - ($msg:expr) => {{ - error!("{}", $msg); - _err_object!($msg) - }}; - ($usr_msg:expr, $log_value:expr) => {{ - error!("{}: {:#?}", $usr_msg, $log_value); - _err_object!($usr_msg) - }} -} - -#[macro_export] -macro_rules! err_json { - ($expr:expr) => {{ - return Err(rocket::response::status::BadRequest(Some(rocket_contrib::json::Json($expr)))); - }} -} - -#[macro_export] -macro_rules! err_handler { - ($expr:expr) => {{ - error!("{}", $expr); - return rocket::Outcome::Failure((rocket::http::Status::Unauthorized, $expr)); - }} -} - -/// /// File handling ///