|  |  | @ -4,6 +4,7 @@ use rocket::Route; | 
			
		
	
		
			
				
					|  |  |  | use serde_json::Value; | 
			
		
	
		
			
				
					|  |  |  | use std::collections::{HashMap, HashSet}; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | use crate::api::admin::FAKE_ADMIN_UUID; | 
			
		
	
		
			
				
					|  |  |  | use crate::{ | 
			
		
	
		
			
				
					|  |  |  |     api::{ | 
			
		
	
		
			
				
					|  |  |  |         core::{log_event, two_factor, CipherSyncData, CipherSyncType}, | 
			
		
	
	
		
			
				
					|  |  | @ -971,8 +972,8 @@ async fn send_invite( | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |             if let Err(e) = mail::send_invite( | 
			
		
	
		
			
				
					|  |  |  |                 &user, | 
			
		
	
		
			
				
					|  |  |  |                 Some(org_id.clone()), | 
			
		
	
		
			
				
					|  |  |  |                 Some(new_member.uuid.clone()), | 
			
		
	
		
			
				
					|  |  |  |                 org_id.clone(), | 
			
		
	
		
			
				
					|  |  |  |                 new_member.uuid.clone(), | 
			
		
	
		
			
				
					|  |  |  |                 &org_name, | 
			
		
	
		
			
				
					|  |  |  |                 Some(headers.user.email.clone()), | 
			
		
	
		
			
				
					|  |  |  |             ) | 
			
		
	
	
		
			
				
					|  |  | @ -1098,14 +1099,7 @@ async fn _reinvite_member( | 
			
		
	
		
			
				
					|  |  |  |     }; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     if CONFIG.mail_enabled() { | 
			
		
	
		
			
				
					|  |  |  |         mail::send_invite( | 
			
		
	
		
			
				
					|  |  |  |             &user, | 
			
		
	
		
			
				
					|  |  |  |             Some(org_id.clone()), | 
			
		
	
		
			
				
					|  |  |  |             Some(member.uuid), | 
			
		
	
		
			
				
					|  |  |  |             &org_name, | 
			
		
	
		
			
				
					|  |  |  |             Some(invited_by_email.to_string()), | 
			
		
	
		
			
				
					|  |  |  |         ) | 
			
		
	
		
			
				
					|  |  |  |         .await?; | 
			
		
	
		
			
				
					|  |  |  |         mail::send_invite(&user, org_id.clone(), member.uuid, &org_name, Some(invited_by_email.to_string())).await?; | 
			
		
	
		
			
				
					|  |  |  |     } else if user.password_hash.is_empty() { | 
			
		
	
		
			
				
					|  |  |  |         let invitation = Invitation::new(&user.email); | 
			
		
	
		
			
				
					|  |  |  |         invitation.save(conn).await?; | 
			
		
	
	
		
			
				
					|  |  | @ -1131,23 +1125,30 @@ async fn accept_invite( | 
			
		
	
		
			
				
					|  |  |  |     org_id: OrganizationId, | 
			
		
	
		
			
				
					|  |  |  |     member_id: MembershipId, | 
			
		
	
		
			
				
					|  |  |  |     data: Json<AcceptData>, | 
			
		
	
		
			
				
					|  |  |  |     headers: Headers, | 
			
		
	
		
			
				
					|  |  |  |     mut conn: DbConn, | 
			
		
	
		
			
				
					|  |  |  | ) -> EmptyResult { | 
			
		
	
		
			
				
					|  |  |  |     // The web-vault passes org_id and member_id in the URL, but we are just reading them from the JWT instead
 | 
			
		
	
		
			
				
					|  |  |  |     let data: AcceptData = data.into_inner(); | 
			
		
	
		
			
				
					|  |  |  |     let claims = decode_invite(&data.token)?; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     // Don't allow other users from accepting an invitation.
 | 
			
		
	
		
			
				
					|  |  |  |     if !claims.email.eq(&headers.user.email) { | 
			
		
	
		
			
				
					|  |  |  |         err!("Invitation was issued to a different account", "Claim does not match user_id") | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     // If a claim does not have a member_id or it does not match the one in from the URI, something is wrong.
 | 
			
		
	
		
			
				
					|  |  |  |     match &claims.member_id { | 
			
		
	
		
			
				
					|  |  |  |         Some(ou_id) if ou_id.eq(&member_id) => {} | 
			
		
	
		
			
				
					|  |  |  |         _ => err!("Error accepting the invitation", "Claim does not match the member_id"), | 
			
		
	
		
			
				
					|  |  |  |     if !claims.member_id.eq(&member_id) { | 
			
		
	
		
			
				
					|  |  |  |         err!("Error accepting the invitation", "Claim does not match the member_id") | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     match User::find_by_mail(&claims.email, &mut conn).await { | 
			
		
	
		
			
				
					|  |  |  |         Some(user) => { | 
			
		
	
		
			
				
					|  |  |  |     let member = &claims.member_id; | 
			
		
	
		
			
				
					|  |  |  |     let org = &claims.org_id; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     Invitation::take(&claims.email, &mut conn).await; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |             if let (Some(member), Some(org)) = (&claims.member_id, &claims.org_id) { | 
			
		
	
		
			
				
					|  |  |  |     // skip invitation logic when we were invited via the /admin panel
 | 
			
		
	
		
			
				
					|  |  |  |     if **member != FAKE_ADMIN_UUID { | 
			
		
	
		
			
				
					|  |  |  |         let Some(mut member) = Membership::find_by_uuid_and_org(member, org, &mut conn).await else { | 
			
		
	
		
			
				
					|  |  |  |             err!("Error accepting the invitation") | 
			
		
	
		
			
				
					|  |  |  |         }; | 
			
		
	
	
		
			
				
					|  |  | @ -1168,7 +1169,7 @@ async fn accept_invite( | 
			
		
	
		
			
				
					|  |  |  |                 Ok(_) => {} | 
			
		
	
		
			
				
					|  |  |  |                 Err(OrgPolicyErr::TwoFactorMissing) => { | 
			
		
	
		
			
				
					|  |  |  |                     if CONFIG.email_2fa_auto_fallback() { | 
			
		
	
		
			
				
					|  |  |  |                                 two_factor::email::activate_email_2fa(&user, &mut conn).await?; | 
			
		
	
		
			
				
					|  |  |  |                         two_factor::email::activate_email_2fa(&headers.user, &mut conn).await?; | 
			
		
	
		
			
				
					|  |  |  |                     } else { | 
			
		
	
		
			
				
					|  |  |  |                         err!("You cannot join this organization until you enable two-step login on your user account"); | 
			
		
	
		
			
				
					|  |  |  |                     } | 
			
		
	
	
		
			
				
					|  |  | @ -1187,23 +1188,18 @@ async fn accept_invite( | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |         member.save(&mut conn).await?; | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |         None => err!("Invited user not found"), | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     if CONFIG.mail_enabled() { | 
			
		
	
		
			
				
					|  |  |  |         let mut org_name = CONFIG.invitation_org_name(); | 
			
		
	
		
			
				
					|  |  |  |         if let Some(org_id) = &claims.org_id { | 
			
		
	
		
			
				
					|  |  |  |             org_name = match Organization::find_by_uuid(org_id, &mut conn).await { | 
			
		
	
		
			
				
					|  |  |  |         if let Some(invited_by_email) = &claims.invited_by_email { | 
			
		
	
		
			
				
					|  |  |  |             let org_name = match Organization::find_by_uuid(&claims.org_id, &mut conn).await { | 
			
		
	
		
			
				
					|  |  |  |                 Some(org) => org.name, | 
			
		
	
		
			
				
					|  |  |  |                 None => err!("Organization not found."), | 
			
		
	
		
			
				
					|  |  |  |             }; | 
			
		
	
		
			
				
					|  |  |  |         }; | 
			
		
	
		
			
				
					|  |  |  |         if let Some(invited_by_email) = &claims.invited_by_email { | 
			
		
	
		
			
				
					|  |  |  |             // User was invited to an organization, so they must be confirmed manually after acceptance
 | 
			
		
	
		
			
				
					|  |  |  |             mail::send_invite_accepted(&claims.email, invited_by_email, &org_name).await?; | 
			
		
	
		
			
				
					|  |  |  |         } else { | 
			
		
	
		
			
				
					|  |  |  |             // User was invited from /admin, so they are automatically confirmed
 | 
			
		
	
		
			
				
					|  |  |  |             let org_name = CONFIG.invitation_org_name(); | 
			
		
	
		
			
				
					|  |  |  |             mail::send_invite_confirmed(&claims.email, &org_name).await?; | 
			
		
	
		
			
				
					|  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
	
		
			
				
					|  |  | @ -1825,23 +1821,17 @@ async fn list_policies(org_id: OrganizationId, _headers: AdminHeaders, mut conn: | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | #[get("/organizations/<org_id>/policies/token?<token>")] | 
			
		
	
		
			
				
					|  |  |  | async fn list_policies_token(org_id: OrganizationId, token: &str, mut conn: DbConn) -> JsonResult { | 
			
		
	
		
			
				
					|  |  |  |     // web-vault 2024.6.2 seems to send these values and cause logs to output errors
 | 
			
		
	
		
			
				
					|  |  |  |     // Catch this and prevent errors in the logs
 | 
			
		
	
		
			
				
					|  |  |  |     // TODO: CleanUp after 2024.6.x is not used anymore.
 | 
			
		
	
		
			
				
					|  |  |  |     if org_id.as_ref() == "undefined" && token == "undefined" { | 
			
		
	
		
			
				
					|  |  |  |         return Ok(Json(json!({}))); | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     let invite = decode_invite(token)?; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     let Some(invite_org_id) = invite.org_id else { | 
			
		
	
		
			
				
					|  |  |  |         err!("Invalid token") | 
			
		
	
		
			
				
					|  |  |  |     }; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     if invite_org_id != org_id { | 
			
		
	
		
			
				
					|  |  |  |     if invite.org_id != org_id { | 
			
		
	
		
			
				
					|  |  |  |         err!("Token doesn't match request organization"); | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     // exit early when we have been invited via /admin panel
 | 
			
		
	
		
			
				
					|  |  |  |     if org_id.as_ref() == FAKE_ADMIN_UUID { | 
			
		
	
		
			
				
					|  |  |  |         return Ok(Json(json!({}))); | 
			
		
	
		
			
				
					|  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |     // TODO: We receive the invite token as ?token=<>, validate it contains the org id
 | 
			
		
	
		
			
				
					|  |  |  |     let policies = OrgPolicy::find_by_org(&org_id, &mut conn).await; | 
			
		
	
		
			
				
					|  |  |  |     let policies_json: Vec<Value> = policies.iter().map(OrgPolicy::to_json).collect(); | 
			
		
	
	
		
			
				
					|  |  | @ -2141,8 +2131,8 @@ async fn import(org_id: OrganizationId, data: Json<OrgImportData>, headers: Head | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |                     mail::send_invite( | 
			
		
	
		
			
				
					|  |  |  |                         &user, | 
			
		
	
		
			
				
					|  |  |  |                         Some(org_id.clone()), | 
			
		
	
		
			
				
					|  |  |  |                         Some(new_member.uuid.clone()), | 
			
		
	
		
			
				
					|  |  |  |                         org_id.clone(), | 
			
		
	
		
			
				
					|  |  |  |                         new_member.uuid.clone(), | 
			
		
	
		
			
				
					|  |  |  |                         &org_name, | 
			
		
	
		
			
				
					|  |  |  |                         Some(headers.user.email.clone()), | 
			
		
	
		
			
				
					|  |  |  |                     ) | 
			
		
	
	
		
			
				
					|  |  | 
 |