@ -1,27 +1,28 @@
use std ::collections ::{ HashMap , HashSet } ;
use num_traits ::FromPrimitive ;
use num_traits ::FromPrimitive ;
use rocket ::serde ::json ::Json ;
use rocket ::{ Route , serde ::json ::Json } ;
use rocket ::Route ;
use serde_json ::Value ;
use serde_json ::Value ;
use std ::collections ::{ HashMap , HashSet } ;
use crate ::api ::admin ::FAKE_ADMIN_UUID ;
use crate ::{
use crate ::{
CONFIG ,
api ::admin ::FAKE_ADMIN_UUID ,
api ::{
api ::{
core ::{ accept_org_invite , log_event , two_factor , CipherSyncData , CipherSyncType } ,
EmptyResult , JsonResult , Notify , PasswordOrOtpData , UpdateType ,
EmptyResult , JsonResult , Notify , PasswordOrOtpData , UpdateType ,
core ::{ CipherSyncData , CipherSyncType , accept_org_invite , log_event , two_factor } ,
} ,
} ,
auth ::{ decode_invite , AdminHeaders , Headers , ManagerHeaders , ManagerHeadersLoose , OrgMemberHeaders , OwnerHeaders } ,
auth ::{ AdminHeaders , Headers , ManagerHeaders , ManagerHeadersLoose , OrgMemberHeaders , OwnerHeaders , decode_invite } ,
db ::{
db ::{
DbConn ,
models ::{
models ::{
Cipher , CipherId , Collection , CollectionCipher , CollectionGroup , CollectionId , CollectionUser , EventType ,
Cipher , CipherId , Collection , CollectionCipher , CollectionGroup , CollectionId , CollectionUser , EventType ,
Group , GroupId , GroupUser , Invitation , Membership , MembershipId , MembershipStatus , MembershipType ,
Group , GroupId , GroupUser , Invitation , Membership , MembershipId , MembershipStatus , MembershipType ,
OrgPolicy , OrgPolicyType , Organization , OrganizationApiKey , OrganizationId , User , UserId ,
OrgPolicy , OrgPolicyType , Organization , OrganizationApiKey , OrganizationId , User , UserId ,
} ,
} ,
DbConn ,
} ,
} ,
mail ,
mail ,
util ::{ convert_json_key_lcase_first , get_uuid , NumberOrString } ,
sso ::FAKE_SSO_IDENTIFIER ,
CONFIG ,
util ::{ NumberOrString , convert_json_key_lcase_first } ,
} ;
} ;
pub fn routes ( ) -> Vec < Route > {
pub fn routes ( ) -> Vec < Route > {
@ -64,6 +65,7 @@ pub fn routes() -> Vec<Route> {
post_org_import ,
post_org_import ,
list_policies ,
list_policies ,
list_policies_token ,
list_policies_token ,
get_dummy_master_password_policy ,
get_master_password_policy ,
get_master_password_policy ,
get_policy ,
get_policy ,
put_policy ,
put_policy ,
@ -76,6 +78,7 @@ pub fn routes() -> Vec<Route> {
revoke_member ,
revoke_member ,
bulk_revoke_members ,
bulk_revoke_members ,
restore_member ,
restore_member ,
restore_member_vnext ,
bulk_restore_members ,
bulk_restore_members ,
get_groups ,
get_groups ,
get_groups_details ,
get_groups_details ,
@ -94,11 +97,12 @@ pub fn routes() -> Vec<Route> {
get_reset_password_details ,
get_reset_password_details ,
put_reset_password ,
put_reset_password ,
get_org_export ,
get_org_export ,
api_key ,
post_ api_key,
rotate_api_key ,
rotate_api_key ,
get_billing_metadata ,
get_billing_metadata ,
get_billing_warnings ,
get_billing_warnings ,
get_auto_enroll_status ,
get_auto_enroll_status ,
get_self_host_billing_metadata ,
]
]
}
}
@ -282,9 +286,10 @@ async fn get_organization(org_id: OrganizationId, headers: OwnerHeaders, conn: D
if org_id ! = headers . org_id {
if org_id ! = headers . org_id {
err ! ( "Organization not found" , "Organization id's do not match" ) ;
err ! ( "Organization not found" , "Organization id's do not match" ) ;
}
}
match Organization ::find_by_uuid ( & org_id , & conn ) . await {
if let Some ( organization ) = Organization ::find_by_uuid ( & org_id , & conn ) . await {
Some ( organization ) = > Ok ( Json ( organization . to_json ( ) ) ) ,
Ok ( Json ( organization . to_json ( ) ) )
None = > err ! ( "Can't find organization details" ) ,
} else {
err ! ( "Can't find organization details" )
}
}
}
}
@ -353,7 +358,7 @@ async fn get_user_collections(headers: Headers, conn: DbConn) -> Json<Value> {
// The returned `Id` will then be passed to `get_master_password_policy` which will mainly ignore it
// The returned `Id` will then be passed to `get_master_password_policy` which will mainly ignore it
#[ get( " /organizations/<identifier>/auto-enroll-status " ) ]
#[ get( " /organizations/<identifier>/auto-enroll-status " ) ]
async fn get_auto_enroll_status ( identifier : & str , headers : Headers , conn : DbConn ) -> JsonResult {
async fn get_auto_enroll_status ( identifier : & str , headers : Headers , conn : DbConn ) -> JsonResult {
let org = if identifier = = crate ::sso ::FAKE_IDENTIFIER {
let org = if identifier = = FAKE_SSO _IDENTIFIER {
match Membership ::find_main_user_org ( & headers . user . uuid , & conn ) . await {
match Membership ::find_main_user_org ( & headers . user . uuid , & conn ) . await {
Some ( member ) = > Organization ::find_by_uuid ( & member . org_uuid , & conn ) . await ,
Some ( member ) = > Organization ::find_by_uuid ( & member . org_uuid , & conn ) . await ,
None = > None ,
None = > None ,
@ -363,7 +368,7 @@ async fn get_auto_enroll_status(identifier: &str, headers: Headers, conn: DbConn
} ;
} ;
let ( id , identifier , rp_auto_enroll ) = match org {
let ( id , identifier , rp_auto_enroll ) = match org {
None = > ( get_uui d( ) , identifier . to_string ( ) , false ) ,
None = > ( identifier . to_owne d( ) , identifier . to_owned ( ) , false ) ,
Some ( org ) = > (
Some ( org ) = > (
org . uuid . to_string ( ) ,
org . uuid . to_string ( ) ,
org . uuid . to_string ( ) ,
org . uuid . to_string ( ) ,
@ -389,7 +394,7 @@ async fn get_org_collections(org_id: OrganizationId, headers: ManagerHeadersLoos
}
}
Ok ( Json ( json ! ( {
Ok ( Json ( json ! ( {
"data" : _ get_org_collections( & org_id , & conn ) . await ,
"data" : get_org_collections_impl ( & org_id , & conn ) . await ,
"object" : "list" ,
"object" : "list" ,
"continuationToken" : null ,
"continuationToken" : null ,
} ) ) )
} ) ) )
@ -461,7 +466,7 @@ async fn get_org_collections_details(org_id: OrganizationId, headers: ManagerHea
CollectionGroup ::find_by_collection ( & col . uuid , & conn )
CollectionGroup ::find_by_collection ( & col . uuid , & conn )
. await
. await
. iter ( )
. iter ( )
. map ( | collection_group | collection_group . to_json_details_for_group ( ) )
. map ( CollectionGroup ::to_json_details_for_group )
. collect ( )
. collect ( )
} else {
} else {
Vec ::with_capacity ( 0 )
Vec ::with_capacity ( 0 )
@ -473,7 +478,7 @@ async fn get_org_collections_details(org_id: OrganizationId, headers: ManagerHea
json_object [ "groups" ] = json ! ( groups ) ;
json_object [ "groups" ] = json ! ( groups ) ;
json_object [ "object" ] = json ! ( "collectionAccessDetails" ) ;
json_object [ "object" ] = json ! ( "collectionAccessDetails" ) ;
json_object [ "unmanaged" ] = json ! ( false ) ;
json_object [ "unmanaged" ] = json ! ( false ) ;
data . push ( json_object )
data . push ( json_object ) ;
}
}
Ok ( Json ( json ! ( {
Ok ( Json ( json ! ( {
@ -483,7 +488,7 @@ async fn get_org_collections_details(org_id: OrganizationId, headers: ManagerHea
} ) ) )
} ) ) )
}
}
async fn _ get_org_collections( org_id : & OrganizationId , conn : & DbConn ) -> Value {
async fn get_org_collections_impl ( org_id : & OrganizationId , conn : & DbConn ) -> Value {
Collection ::find_by_organization ( org_id , conn ) . await . iter ( ) . map ( Collection ::to_json ) . collect ::< Value > ( )
Collection ::find_by_organization ( org_id , conn ) . await . iter ( ) . map ( Collection ::to_json ) . collect ::< Value > ( )
}
}
@ -569,7 +574,7 @@ async fn post_bulk_access_collections(
if Organization ::find_by_uuid ( & org_id , & conn ) . await . is_none ( ) {
if Organization ::find_by_uuid ( & org_id , & conn ) . await . is_none ( ) {
err ! ( "Can't find organization details" )
err ! ( "Can't find organization details" )
} ;
}
for col_id in data . collection_ids {
for col_id in data . collection_ids {
let Some ( collection ) = Collection ::find_by_uuid_and_org ( & col_id , & org_id , & conn ) . await else {
let Some ( collection ) = Collection ::find_by_uuid_and_org ( & col_id , & org_id , & conn ) . await else {
@ -646,7 +651,7 @@ async fn post_organization_collection_update(
if Organization ::find_by_uuid ( & org_id , & conn ) . await . is_none ( ) {
if Organization ::find_by_uuid ( & org_id , & conn ) . await . is_none ( ) {
err ! ( "Can't find organization details" )
err ! ( "Can't find organization details" )
} ;
}
let Some ( mut collection ) = Collection ::find_by_uuid_and_org ( & col_id , & org_id , & conn ) . await else {
let Some ( mut collection ) = Collection ::find_by_uuid_and_org ( & col_id , & org_id , & conn ) . await else {
err ! ( "Collection not found" )
err ! ( "Collection not found" )
@ -697,7 +702,7 @@ async fn post_organization_collection_update(
Ok ( Json ( collection . to_json_details ( & headers . user . uuid , None , & conn ) . await ) )
Ok ( Json ( collection . to_json_details ( & headers . user . uuid , None , & conn ) . await ) )
}
}
async fn _ delete_organization_collection(
async fn delete_organization_collection_impl (
org_id : & OrganizationId ,
org_id : & OrganizationId ,
col_id : & CollectionId ,
col_id : & CollectionId ,
headers : & ManagerHeaders ,
headers : & ManagerHeaders ,
@ -729,7 +734,7 @@ async fn delete_organization_collection(
headers : ManagerHeaders ,
headers : ManagerHeaders ,
conn : DbConn ,
conn : DbConn ,
) -> EmptyResult {
) -> EmptyResult {
_ delete_organization_collection( & org_id , & col_id , & headers , & conn ) . await
delete_organization_collection_impl ( & org_id , & col_id , & headers , & conn ) . await
}
}
#[ post( " /organizations/<org_id>/collections/<col_id>/delete " ) ]
#[ post( " /organizations/<org_id>/collections/<col_id>/delete " ) ]
@ -739,7 +744,7 @@ async fn post_organization_collection_delete(
headers : ManagerHeaders ,
headers : ManagerHeaders ,
conn : DbConn ,
conn : DbConn ,
) -> EmptyResult {
) -> EmptyResult {
_ delete_organization_collection( & org_id , & col_id , & headers , & conn ) . await
delete_organization_collection_impl ( & org_id , & col_id , & headers , & conn ) . await
}
}
#[ derive(Deserialize, Debug) ]
#[ derive(Deserialize, Debug) ]
@ -765,7 +770,7 @@ async fn bulk_delete_organization_collections(
let headers = ManagerHeaders ::from_loose ( headers , & collections , & conn ) . await ? ;
let headers = ManagerHeaders ::from_loose ( headers , & collections , & conn ) . await ? ;
for col_id in collections {
for col_id in collections {
_ delete_organization_collection( & org_id , & col_id , & headers , & conn ) . await ?
delete_organization_collection_impl ( & org_id , & col_id , & headers , & conn ) . await ? ;
}
}
Ok ( ( ) )
Ok ( ( ) )
}
}
@ -795,7 +800,7 @@ async fn get_org_collection_detail(
CollectionGroup ::find_by_collection ( & collection . uuid , & conn )
CollectionGroup ::find_by_collection ( & collection . uuid , & conn )
. await
. await
. iter ( )
. iter ( )
. map ( | collection_group | collection_group . to_json_details_for_group ( ) )
. map ( CollectionGroup ::to_json_details_for_group )
. collect ( )
. collect ( )
} else {
} else {
// The Bitwarden clients seem to call this API regardless of whether groups are enabled,
// The Bitwarden clients seem to call this API regardless of whether groups are enabled,
@ -882,13 +887,13 @@ async fn get_org_details(data: OrgIdData, headers: ManagerHeadersLoose, conn: Db
}
}
Ok ( Json ( json ! ( {
Ok ( Json ( json ! ( {
"data" : _ get_org_details( & data . organization_id , & headers . host , & headers . user . uuid , & conn ) . await ? ,
"data" : get_org_details_impl ( & data . organization_id , & headers . host , & headers . user . uuid , & conn ) . await ? ,
"object" : "list" ,
"object" : "list" ,
"continuationToken" : null ,
"continuationToken" : null ,
} ) ) )
} ) ) )
}
}
async fn _ get_org_details(
async fn get_org_details_impl (
org_id : & OrganizationId ,
org_id : & OrganizationId ,
host : & str ,
host : & str ,
user_id : & UserId ,
user_id : & UserId ,
@ -904,36 +909,21 @@ async fn _get_org_details(
Ok ( json ! ( ciphers_json ) )
Ok ( json ! ( ciphers_json ) )
}
}
#[ derive(Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
struct OrgDomainDetails {
email : String ,
}
// Returning a Domain/Organization here allow to prefill it and prevent prompting the user
// Returning a Domain/Organization here allow to prefill it and prevent prompting the user
// So we either return an Org name associated to the user or a dummy value.
// So we return a dummy value, since we only support a single SSO integration, and do not use the response anywhere
// In use since `v2025.6.0`, appears to use only the first `organizationIdentifier`
// In use since `v2025.6.0`, appears to use only the first `organizationIdentifier`
#[ post( " /organizations/domain/sso/verified " , data = " <data> " ) ]
#[ post( " /organizations/domain/sso/verified " ) ]
async fn get_org_domain_sso_verified ( data : Json < OrgDomainDetails > , conn : DbConn ) -> JsonResult {
fn get_org_domain_sso_verified ( ) -> JsonResult {
let data : OrgDomainDetails = data . into_inner ( ) ;
// Always return a dummy value, no matter if SSO is enabled or not
let identifiers = match Organization ::find_org_user_email ( & data . email , & conn )
. await
. into_iter ( )
. map ( | o | ( o . name , o . uuid . to_string ( ) ) )
. collect ::< Vec < ( String , String ) > > ( )
{
v if ! v . is_empty ( ) = > v ,
_ = > vec ! [ ( crate ::sso ::FAKE_IDENTIFIER . to_string ( ) , crate ::sso ::FAKE_IDENTIFIER . to_string ( ) ) ] ,
} ;
Ok ( Json ( json ! ( {
Ok ( Json ( json ! ( {
"object" : "list" ,
"object" : "list" ,
"data" : identifiers . into_iter ( ) . map ( | ( name , identifier ) | json ! ( {
"data" : [ {
"organizationName" : name , // appear unused
"organizationIdentifier" : FAKE_SSO_IDENTIFIER ,
"organizationIdentifier" : identifier ,
// These appear to be unused
"domainName" : CONFIG . domain ( ) , // appear unused
"organizationName" : FAKE_SSO_IDENTIFIER ,
} ) ) . collect ::< Vec < Value > > ( )
"domainName" : CONFIG . domain ( )
} ] ,
"continuationToken" : null
} ) ) )
} ) ) )
}
}
@ -986,14 +976,13 @@ async fn post_org_keys(
}
}
let data : OrgKeyData = data . into_inner ( ) ;
let data : OrgKeyData = data . into_inner ( ) ;
let mut org = match Organization ::find_by_uuid ( & org_id , & conn ) . await {
let mut org = if let Some ( organization ) = Organization ::find_by_uuid ( & org_id , & conn ) . await {
Some ( organization ) = > {
if organization . private_key . is_some ( ) & & organization . public_key . is_some ( ) {
if organization . private_key . is_some ( ) & & organization . public_key . is_some ( ) {
err ! ( "Organization Keys already exist" )
err ! ( "Organization Keys already exist" )
}
organization
}
}
None = > err ! ( "Can't find organization details" ) ,
organization
} else {
err ! ( "Can't find organization details" )
} ;
} ;
org . private_key = Some ( data . encrypted_private_key ) ;
org . private_key = Some ( data . encrypted_private_key ) ;
@ -1054,9 +1043,10 @@ async fn send_invite(
// The from_str() will convert the custom role type into a manager role type
// The from_str() will convert the custom role type into a manager role type
let raw_type = & data . r#type . into_string ( ) ;
let raw_type = & data . r#type . into_string ( ) ;
// Membership::from_str will convert custom (4) to manager (3)
// Membership::from_str will convert custom (4) to manager (3)
let new_type = match MembershipType ::from_str ( raw_type ) {
let new_type = if let Some ( new_type ) = MembershipType ::from_str ( raw_type ) {
Some ( new_type ) = > new_type as i32 ,
new_type as i32
None = > err ! ( "Invalid type" ) ,
} else {
err ! ( "Invalid type" )
} ;
} ;
if new_type ! = MembershipType ::User & & headers . membership_type ! = MembershipType ::Owner {
if new_type ! = MembershipType ::User & & headers . membership_type ! = MembershipType ::Owner {
@ -1073,7 +1063,7 @@ async fn send_invite(
& & data . permissions . get ( "createNewCollections" ) = = Some ( & json ! ( true ) ) ) ;
& & data . permissions . get ( "createNewCollections" ) = = Some ( & json ! ( true ) ) ) ;
let mut user_created : bool = false ;
let mut user_created : bool = false ;
for email in data . emails . iter ( ) {
for email in & data . emails {
let mut member_status = MembershipStatus ::Invited as i32 ;
let mut member_status = MembershipStatus ::Invited as i32 ;
let user = match User ::find_by_mail ( email , & conn ) . await {
let user = match User ::find_by_mail ( email , & conn ) . await {
None = > {
None = > {
@ -1097,13 +1087,13 @@ async fn send_invite(
Some ( user ) = > {
Some ( user ) = > {
if Membership ::find_by_user_and_org ( & user . uuid , & org_id , & conn ) . await . is_some ( ) {
if Membership ::find_by_user_and_org ( & user . uuid , & org_id , & conn ) . await . is_some ( ) {
err ! ( format ! ( "User already in organization: {email}" ) )
err ! ( format ! ( "User already in organization: {email}" ) )
} else {
// automatically accept existing users if mail is disabled
if ! CONFIG . mail_enabled ( ) & & ! user . password_hash . is_empty ( ) {
member_status = MembershipStatus ::Accepted as i32 ;
}
user
}
}
// automatically accept existing users if mail is disabled
if ! CONFIG . mail_enabled ( ) & & ! user . password_hash . is_empty ( ) {
member_status = MembershipStatus ::Accepted as i32 ;
}
user
}
}
} ;
} ;
@ -1114,9 +1104,10 @@ async fn send_invite(
new_member . save ( & conn ) . await ? ;
new_member . save ( & conn ) . await ? ;
if CONFIG . mail_enabled ( ) {
if CONFIG . mail_enabled ( ) {
let org_name = match Organization ::find_by_uuid ( & org_id , & conn ) . await {
let org_name = if let Some ( org ) = Organization ::find_by_uuid ( & org_id , & conn ) . await {
Some ( org ) = > org . name ,
org . name
None = > err ! ( "Error looking up organization" ) ,
} else {
err ! ( "Error looking up organization" )
} ;
} ;
if let Err ( e ) = mail ::send_invite (
if let Err ( e ) = mail ::send_invite (
@ -1170,7 +1161,7 @@ async fn send_invite(
}
}
}
}
for group_id in data . groups . iter ( ) {
for group_id in & data . groups {
let mut group_entry = GroupUser ::new ( group_id . clone ( ) , new_member . uuid . clone ( ) ) ;
let mut group_entry = GroupUser ::new ( group_id . clone ( ) , new_member . uuid . clone ( ) ) ;
group_entry . save ( & conn ) . await ? ;
group_entry . save ( & conn ) . await ? ;
}
}
@ -1193,8 +1184,8 @@ async fn bulk_reinvite_members(
let mut bulk_response = Vec ::new ( ) ;
let mut bulk_response = Vec ::new ( ) ;
for member_id in data . ids {
for member_id in data . ids {
let err_msg = match _ reinvite_member( & org_id , & member_id , & headers . user . email , & conn ) . await {
let err_msg = match reinvite_member_impl ( & org_id , & member_id , & headers . user . email , & conn ) . await {
Ok ( _ ) = > String ::new ( ) ,
Ok ( ( ) ) = > String ::new ( ) ,
Err ( e ) = > format ! ( "{e:?}" ) ,
Err ( e ) = > format ! ( "{e:?}" ) ,
} ;
} ;
@ -1204,7 +1195,7 @@ async fn bulk_reinvite_members(
"id" : member_id ,
"id" : member_id ,
"error" : err_msg
"error" : err_msg
}
}
) )
) ) ;
}
}
Ok ( Json ( json ! ( {
Ok ( Json ( json ! ( {
@ -1224,10 +1215,10 @@ async fn reinvite_member(
if org_id ! = headers . org_id {
if org_id ! = headers . org_id {
err ! ( "Organization not found" , "Organization id's do not match" ) ;
err ! ( "Organization not found" , "Organization id's do not match" ) ;
}
}
_ reinvite_member( & org_id , & member_id , & headers . user . email , & conn ) . await
reinvite_member_impl ( & org_id , & member_id , & headers . user . email , & conn ) . await
}
}
async fn _ reinvite_member(
async fn reinvite_member_impl (
org_id : & OrganizationId ,
org_id : & OrganizationId ,
member_id : & MembershipId ,
member_id : & MembershipId ,
invited_by_email : & str ,
invited_by_email : & str ,
@ -1249,13 +1240,14 @@ async fn _reinvite_member(
err ! ( "Invitations are not allowed." )
err ! ( "Invitations are not allowed." )
}
}
let org_name = match Organization ::find_by_uuid ( org_id , conn ) . await {
let org_name = if let Some ( org ) = Organization ::find_by_uuid ( org_id , conn ) . await {
Some ( org ) = > org . name ,
org . name
None = > err ! ( "Error looking up organization." ) ,
} else {
err ! ( "Error looking up organization." )
} ;
} ;
if CONFIG . mail_enabled ( ) {
if CONFIG . mail_enabled ( ) {
mail ::send_invite ( & user , org_id . clone ( ) , 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_owned ( ) ) ) . await ? ;
} else if user . password_hash . is_empty ( ) {
} else if user . password_hash . is_empty ( ) {
let invitation = Invitation ::new ( & user . email ) ;
let invitation = Invitation ::new ( & user . email ) ;
invitation . save ( conn ) . await ? ;
invitation . save ( conn ) . await ? ;
@ -1363,8 +1355,8 @@ async fn bulk_confirm_invite(
for invite in keys {
for invite in keys {
let member_id = invite . id . unwrap ( ) ;
let member_id = invite . id . unwrap ( ) ;
let user_key = invite . key . unwrap_or_default ( ) ;
let user_key = invite . key . unwrap_or_default ( ) ;
let err_msg = match _ confirm_invite( & org_id , & member_id , & user_key , & headers , & conn , & nt ) . await {
let err_msg = match confirm_invite_impl ( & org_id , & member_id , & user_key , & headers , & conn , & nt ) . await {
Ok ( _ ) = > String ::new ( ) ,
Ok ( ( ) ) = > String ::new ( ) ,
Err ( e ) = > format ! ( "{e:?}" ) ,
Err ( e ) = > format ! ( "{e:?}" ) ,
} ;
} ;
@ -1398,10 +1390,10 @@ async fn confirm_invite(
) -> EmptyResult {
) -> EmptyResult {
let data = data . into_inner ( ) ;
let data = data . into_inner ( ) ;
let user_key = data . key . unwrap_or_default ( ) ;
let user_key = data . key . unwrap_or_default ( ) ;
_ confirm_invite( & org_id , & member_id , & user_key , & headers , & conn , & nt ) . await
confirm_invite_impl ( & org_id , & member_id , & user_key , & headers , & conn , & nt ) . await
}
}
async fn _ confirm_invite(
async fn confirm_invite_impl (
org_id : & OrganizationId ,
org_id : & OrganizationId ,
member_id : & MembershipId ,
member_id : & MembershipId ,
key : & str ,
key : & str ,
@ -1429,7 +1421,7 @@ async fn _confirm_invite(
}
}
member_to_confirm . status = MembershipStatus ::Confirmed as i32 ;
member_to_confirm . status = MembershipStatus ::Confirmed as i32 ;
member_to_confirm . akey = key . to_string ( ) ;
member_to_confirm . akey = key . to_owned ( ) ;
// This check is also done at accept_invite, _confirm_invite, _activate_member, edit_member, admin::update_membership_type
// This check is also done at accept_invite, _confirm_invite, _activate_member, edit_member, admin::update_membership_type
OrgPolicy ::check_user_allowed ( & member_to_confirm , "confirm" , conn ) . await ? ;
OrgPolicy ::check_user_allowed ( & member_to_confirm , "confirm" , conn ) . await ? ;
@ -1446,13 +1438,15 @@ async fn _confirm_invite(
. await ;
. await ;
if CONFIG . mail_enabled ( ) {
if CONFIG . mail_enabled ( ) {
let org_name = match Organization ::find_by_uuid ( org_id , conn ) . await {
let org_name = if let Some ( org ) = Organization ::find_by_uuid ( org_id , conn ) . await {
Some ( org ) = > org . name ,
org . name
None = > err ! ( "Error looking up organization." ) ,
} else {
err ! ( "Error looking up organization." )
} ;
} ;
let address = match User ::find_by_uuid ( & member_to_confirm . user_uuid , conn ) . await {
let address = if let Some ( user ) = User ::find_by_uuid ( & member_to_confirm . user_uuid , conn ) . await {
Some ( user ) = > user . email ,
user . email
None = > err ! ( "Error looking up user." ) ,
} else {
err ! ( "Error looking up user." )
} ;
} ;
mail ::send_invite_confirmed ( & address , & org_name ) . await ? ;
mail ::send_invite_confirmed ( & address , & org_name ) . await ? ;
}
}
@ -1460,7 +1454,7 @@ async fn _confirm_invite(
let save_result = member_to_confirm . save ( conn ) . await ;
let save_result = member_to_confirm . save ( conn ) . await ;
if let Some ( user ) = User ::find_by_uuid ( & member_to_confirm . user_uuid , conn ) . await {
if let Some ( user ) = User ::find_by_uuid ( & member_to_confirm . user_uuid , conn ) . await {
nt . send_user_update ( UpdateType ::SyncOrgKeys , & user , & headers . device . push_uuid , conn ) . await ;
nt . send_user_update ( UpdateType ::SyncOrgKeys , & user , headers . device . push_uuid . as_ref ( ) , conn ) . await ;
}
}
save_result
save_result
@ -1648,8 +1642,8 @@ async fn bulk_delete_member(
let mut bulk_response = Vec ::new ( ) ;
let mut bulk_response = Vec ::new ( ) ;
for member_id in data . ids {
for member_id in data . ids {
let err_msg = match _ delete_member( & org_id , & member_id , & headers , & conn , & nt ) . await {
let err_msg = match delete_member_impl ( & org_id , & member_id , & headers , & conn , & nt ) . await {
Ok ( _ ) = > String ::new ( ) ,
Ok ( ( ) ) = > String ::new ( ) ,
Err ( e ) = > format ! ( "{e:?}" ) ,
Err ( e ) = > format ! ( "{e:?}" ) ,
} ;
} ;
@ -1659,7 +1653,7 @@ async fn bulk_delete_member(
"id" : member_id ,
"id" : member_id ,
"error" : err_msg
"error" : err_msg
}
}
) )
) ) ;
}
}
Ok ( Json ( json ! ( {
Ok ( Json ( json ! ( {
@ -1677,10 +1671,10 @@ async fn delete_member(
conn : DbConn ,
conn : DbConn ,
nt : Notify < '_ > ,
nt : Notify < '_ > ,
) -> EmptyResult {
) -> EmptyResult {
_ delete_member( & org_id , & member_id , & headers , & conn , & nt ) . await
delete_member_impl ( & org_id , & member_id , & headers , & conn , & nt ) . await
}
}
async fn _ delete_member(
async fn delete_member_impl (
org_id : & OrganizationId ,
org_id : & OrganizationId ,
member_id : & MembershipId ,
member_id : & MembershipId ,
headers : & AdminHeaders ,
headers : & AdminHeaders ,
@ -1718,7 +1712,7 @@ async fn _delete_member(
. await ;
. await ;
if let Some ( user ) = User ::find_by_uuid ( & member_to_delete . user_uuid , conn ) . await {
if let Some ( user ) = User ::find_by_uuid ( & member_to_delete . user_uuid , conn ) . await {
nt . send_user_update ( UpdateType ::SyncOrgKeys , & user , & headers . device . push_uuid , conn ) . await ;
nt . send_user_update ( UpdateType ::SyncOrgKeys , & user , headers . device . push_uuid . as_ref ( ) , conn ) . await ;
}
}
member_to_delete . delete ( conn ) . await
member_to_delete . delete ( conn ) . await
@ -1764,8 +1758,8 @@ async fn bulk_public_keys(
} ) ) )
} ) ) )
}
}
use super ::ciphers ::update_cipher_from_data ;
use super ::ciphers ::CipherData ;
use super ::ciphers ::CipherData ;
use super ::ciphers ::update_cipher_from_data ;
#[ derive(Deserialize) ]
#[ derive(Deserialize) ]
#[ serde(rename_all = " camelCase " ) ]
#[ serde(rename_all = " camelCase " ) ]
@ -1913,24 +1907,24 @@ async fn post_bulk_collections(data: Json<BulkCollectionsData>, headers: Headers
}
}
}
}
for cipher_id in data . cipher_ids . iter ( ) {
for cipher_id in & data . cipher_ids {
// Only act on existing cipher uuid's
// Only act on existing cipher uuid's
// Do not abort the operation just ignore it, it could be a cipher was just deleted for example
// Do not abort the operation just ignore it, it could be a cipher was just deleted for example
if let Some ( cipher ) = Cipher ::find_by_uuid_and_org ( cipher_id , & data . organization_id , & conn ) . await {
if let Some ( cipher ) = Cipher ::find_by_uuid_and_org ( cipher_id , & data . organization_id , & conn ) . await
if cipher . is_write_accessible_to_user ( & headers . user . uuid , & conn ) . await {
& & cipher . is_write_accessible_to_user ( & headers . user . uuid , & conn ) . await
// When selecting a specific collection from the left filter list, and use the bulk option, you can remove an item from that collection
{
// In these cases the client will call this endpoint twice, once for adding the new collections and a second for deleting.
// When selecting a specific collection from the left filter list, and use the bulk option, you can remove an item from that collection
if data . remove_collections {
// In these cases the client will call this endpoint twice, once for adding the new collections and a second for deleting.
for collection in & data . collection_id s {
i f data . remove_ collections {
CollectionCipher ::delete ( & cipher . uuid , collection , & conn ) . await ? ;
for collection in & data . collection_ids {
}
CollectionCipher ::delete ( & cipher . uuid , collection , & conn ) . await ? ;
} else {
}
for collection in & data . collection_ids {
} else {
CollectionCipher ::save ( & cipher . uuid , collection , & conn ) . await ? ;
for collection in & data . collection_ids {
}
CollectionCipher ::save ( & cipher . uuid , collection , & conn ) . await ? ;
}
}
}
}
} ;
}
}
}
Ok ( ( ) )
Ok ( ( ) )
@ -1975,15 +1969,25 @@ async fn list_policies_token(org_id: OrganizationId, token: &str, conn: DbConn)
} ) ) )
} ) ) )
}
}
// Called during the SSO enrollment.
// Called during the SSO enrollment return the default policy
// Return the org policy if it exists, otherwise use the default one.
#[ get( " /organizations/00000000-01DC-01DC-01DC-000000000000/policies/master-password " , rank = 1) ]
#[ get( " /organizations/<org_id>/policies/master-password " , rank = 1) ]
fn get_dummy_master_password_policy ( ) -> JsonResult {
let ( enabled , data ) = match CONFIG . sso_master_password_policy_value ( ) {
Some ( policy ) if CONFIG . sso_enabled ( ) = > ( true , policy . to_string ( ) ) ,
_ = > ( false , "null" . to_owned ( ) ) ,
} ;
let policy = OrgPolicy ::new ( FAKE_SSO_IDENTIFIER . into ( ) , OrgPolicyType ::MasterPassword , enabled , data ) ;
Ok ( Json ( policy . to_json ( ) ) )
}
// Called during the SSO enrollment return the org policy if it exists
#[ get( " /organizations/<org_id>/policies/master-password " , rank = 2) ]
async fn get_master_password_policy ( org_id : OrganizationId , _headers : OrgMemberHeaders , conn : DbConn ) -> JsonResult {
async fn get_master_password_policy ( org_id : OrganizationId , _headers : OrgMemberHeaders , conn : DbConn ) -> JsonResult {
let policy =
let policy =
OrgPolicy ::find_by_org_and_type ( & org_id , OrgPolicyType ::MasterPassword , & conn ) . await . unwrap_or_else ( | | {
OrgPolicy ::find_by_org_and_type ( & org_id , OrgPolicyType ::MasterPassword , & conn ) . await . unwrap_or_else ( | | {
let ( enabled , data ) = match CONFIG . sso_master_password_policy_value ( ) {
let ( enabled , data ) = match CONFIG . sso_master_password_policy_value ( ) {
Some ( policy ) if CONFIG . sso_enabled ( ) = > ( true , policy . to_string ( ) ) ,
Some ( policy ) if CONFIG . sso_enabled ( ) = > ( true , policy . to_string ( ) ) ,
_ = > ( false , "null" . to_string ( ) ) ,
_ = > ( false , "null" . to_owned ( ) ) ,
} ;
} ;
OrgPolicy ::new ( org_id , OrgPolicyType ::MasterPassword , enabled , data )
OrgPolicy ::new ( org_id , OrgPolicyType ::MasterPassword , enabled , data )
@ -1992,7 +1996,7 @@ async fn get_master_password_policy(org_id: OrganizationId, _headers: OrgMemberH
Ok ( Json ( policy . to_json ( ) ) )
Ok ( Json ( policy . to_json ( ) ) )
}
}
#[ get( " /organizations/<org_id>/policies/<pol_type> " , rank = 2 ) ]
#[ get( " /organizations/<org_id>/policies/<pol_type> " , rank = 3 ) ]
async fn get_policy ( org_id : OrganizationId , pol_type : i32 , headers : AdminHeaders , conn : DbConn ) -> JsonResult {
async fn get_policy ( org_id : OrganizationId , pol_type : i32 , headers : AdminHeaders , conn : DbConn ) -> JsonResult {
if org_id ! = headers . org_id {
if org_id ! = headers . org_id {
err ! ( "Organization not found" , "Organization id's do not match" ) ;
err ! ( "Organization not found" , "Organization id's do not match" ) ;
@ -2004,7 +2008,7 @@ async fn get_policy(org_id: OrganizationId, pol_type: i32, headers: AdminHeaders
let policy = match OrgPolicy ::find_by_org_and_type ( & org_id , pol_type_enum , & conn ) . await {
let policy = match OrgPolicy ::find_by_org_and_type ( & org_id , pol_type_enum , & conn ) . await {
Some ( p ) = > p ,
Some ( p ) = > p ,
None = > OrgPolicy ::new ( org_id . clone ( ) , pol_type_enum , false , "null" . to_string ( ) ) ,
None = > OrgPolicy ::new ( org_id . clone ( ) , pol_type_enum , false , "null" . to_owned ( ) ) ,
} ;
} ;
Ok ( Json ( policy . to_json ( ) ) )
Ok ( Json ( policy . to_json ( ) ) )
@ -2079,7 +2083,7 @@ async fn put_policy(
// When enabling the SingleOrg policy, remove this org's members that are members of other orgs
// When enabling the SingleOrg policy, remove this org's members that are members of other orgs
if pol_type_enum = = OrgPolicyType ::SingleOrg & & data . enabled {
if pol_type_enum = = OrgPolicyType ::SingleOrg & & data . enabled {
for mut member in Membership ::find_by_org ( & org_id , & conn ) . await . into_iter ( ) {
for mut member in Membership ::find_by_org ( & org_id , & conn ) . await {
// Policy only applies to non-Owner/non-Admin members who have accepted joining the org
// Policy only applies to non-Owner/non-Admin members who have accepted joining the org
// Exclude invited and revoked users when checking for this policy.
// Exclude invited and revoked users when checking for this policy.
// Those users will not be allowed to accept or be activated because of the policy checks done there.
// Those users will not be allowed to accept or be activated because of the policy checks done there.
@ -2114,7 +2118,7 @@ async fn put_policy(
let mut policy = match OrgPolicy ::find_by_org_and_type ( & org_id , pol_type_enum , & conn ) . await {
let mut policy = match OrgPolicy ::find_by_org_and_type ( & org_id , pol_type_enum , & conn ) . await {
Some ( p ) = > p ,
Some ( p ) = > p ,
None = > OrgPolicy ::new ( org_id . clone ( ) , pol_type_enum , false , "{}" . to_string ( ) ) ,
None = > OrgPolicy ::new ( org_id . clone ( ) , pol_type_enum , false , "{}" . to_owned ( ) ) ,
} ;
} ;
policy . enabled = data . enabled ;
policy . enabled = data . enabled ;
@ -2188,7 +2192,7 @@ fn get_plans() -> Json<Value> {
#[ get( " /organizations/<_org_id>/billing/metadata " ) ]
#[ get( " /organizations/<_org_id>/billing/metadata " ) ]
fn get_billing_metadata ( _org_id : OrganizationId , _headers : OrgMemberHeaders ) -> Json < Value > {
fn get_billing_metadata ( _org_id : OrganizationId , _headers : OrgMemberHeaders ) -> Json < Value > {
// Prevent a 404 error, which also causes Javascript errors.
// Prevent a 404 error, which also causes Javascript errors.
Json ( _ empty_data_json( ) )
Json ( empty_data_json ( ) )
}
}
#[ get( " /organizations/<_org_id>/billing/vnext/warnings " ) ]
#[ get( " /organizations/<_org_id>/billing/vnext/warnings " ) ]
@ -2201,7 +2205,16 @@ fn get_billing_warnings(_org_id: OrganizationId, _headers: OrgMemberHeaders) ->
} ) )
} ) )
}
}
fn _empty_data_json ( ) -> Value {
#[ get( " /organizations/<_org_id>/billing/vnext/self-host/metadata " ) ]
fn get_self_host_billing_metadata ( _org_id : OrganizationId , _headers : OrgMemberHeaders ) -> Json < Value > {
// Prevent a 404 error, which also causes Javascript errors.
Json ( json ! ( {
"isOnSecretsManagerStandalone" : false , // Secrets Manager is not supported by Vaultwarden
"organizationOccupiedSeats" : 0 // Vaultwarden does not count seats
} ) )
}
fn empty_data_json ( ) -> Value {
json ! ( {
json ! ( {
"object" : "list" ,
"object" : "list" ,
"data" : [ ] ,
"data" : [ ] ,
@ -2222,7 +2235,7 @@ async fn revoke_member(
headers : AdminHeaders ,
headers : AdminHeaders ,
conn : DbConn ,
conn : DbConn ,
) -> EmptyResult {
) -> EmptyResult {
_ revoke_member( & org_id , & member_id , & headers , & conn ) . await
revoke_member_impl ( & org_id , & member_id , & headers , & conn ) . await
}
}
#[ put( " /organizations/<org_id>/users/revoke " , data = " <data> " ) ]
#[ put( " /organizations/<org_id>/users/revoke " , data = " <data> " ) ]
@ -2241,8 +2254,8 @@ async fn bulk_revoke_members(
match data . ids {
match data . ids {
Some ( members ) = > {
Some ( members ) = > {
for member_id in members {
for member_id in members {
let err_msg = match _ revoke_member( & org_id , & member_id , & headers , & conn ) . await {
let err_msg = match revoke_member_impl ( & org_id , & member_id , & headers , & conn ) . await {
Ok ( _ ) = > String ::new ( ) ,
Ok ( ( ) ) = > String ::new ( ) ,
Err ( e ) = > format ! ( "{e:?}" ) ,
Err ( e ) = > format ! ( "{e:?}" ) ,
} ;
} ;
@ -2265,7 +2278,7 @@ async fn bulk_revoke_members(
} ) ) )
} ) ) )
}
}
async fn _ revoke_member(
async fn revoke_member_impl (
org_id : & OrganizationId ,
org_id : & OrganizationId ,
member_id : & MembershipId ,
member_id : & MembershipId ,
headers : & AdminHeaders ,
headers : & AdminHeaders ,
@ -2308,6 +2321,18 @@ async fn _revoke_member(
Ok ( ( ) )
Ok ( ( ) )
}
}
#[ put( " /organizations/<org_id>/users/<member_id>/restore/vnext " ) ]
async fn restore_member_vnext (
org_id : OrganizationId ,
member_id : MembershipId ,
headers : AdminHeaders ,
conn : DbConn ,
) -> EmptyResult {
// Vaultwarden does not (yet) support the per User Collection linked to the `Enforce organization data ownership` policy.
// Therefor we ignore the `defaultUserCollectionName` data sent and just call restore_member
restore_member_impl ( & org_id , & member_id , & headers , & conn ) . await
}
#[ put( " /organizations/<org_id>/users/<member_id>/restore " ) ]
#[ put( " /organizations/<org_id>/users/<member_id>/restore " ) ]
async fn restore_member (
async fn restore_member (
org_id : OrganizationId ,
org_id : OrganizationId ,
@ -2315,7 +2340,7 @@ async fn restore_member(
headers : AdminHeaders ,
headers : AdminHeaders ,
conn : DbConn ,
conn : DbConn ,
) -> EmptyResult {
) -> EmptyResult {
_ restore_member( & org_id , & member_id , & headers , & conn ) . await
restore_member_impl ( & org_id , & member_id , & headers , & conn ) . await
}
}
#[ put( " /organizations/<org_id>/users/restore " , data = " <data> " ) ]
#[ put( " /organizations/<org_id>/users/restore " , data = " <data> " ) ]
@ -2332,8 +2357,8 @@ async fn bulk_restore_members(
let mut bulk_response = Vec ::new ( ) ;
let mut bulk_response = Vec ::new ( ) ;
for member_id in data . ids {
for member_id in data . ids {
let err_msg = match _ restore_member( & org_id , & member_id , & headers , & conn ) . await {
let err_msg = match restore_member_impl ( & org_id , & member_id , & headers , & conn ) . await {
Ok ( _ ) = > String ::new ( ) ,
Ok ( ( ) ) = > String ::new ( ) ,
Err ( e ) = > format ! ( "{e:?}" ) ,
Err ( e ) = > format ! ( "{e:?}" ) ,
} ;
} ;
@ -2353,7 +2378,7 @@ async fn bulk_restore_members(
} ) ) )
} ) ) )
}
}
async fn _ restore_member(
async fn restore_member_impl (
org_id : & OrganizationId ,
org_id : & OrganizationId ,
member_id : & MembershipId ,
member_id : & MembershipId ,
headers : & AdminHeaders ,
headers : & AdminHeaders ,
@ -2409,11 +2434,11 @@ async fn get_groups_data(
if details {
if details {
for g in groups {
for g in groups {
groups_json . push ( g . to_json_details ( & conn ) . await )
groups_json . push ( g . to_json_details ( & conn ) . await ) ;
}
}
} else {
} else {
for g in groups {
for g in groups {
groups_json . push ( g . to_json ( ) )
groups_json . push ( g . to_json ( ) ) ;
}
}
}
}
groups_json
groups_json
@ -2652,15 +2677,15 @@ async fn post_delete_group(
headers : AdminHeaders ,
headers : AdminHeaders ,
conn : DbConn ,
conn : DbConn ,
) -> EmptyResult {
) -> EmptyResult {
_ delete_group( & org_id , & group_id , & headers , & conn ) . await
delete_group_impl ( & org_id , & group_id , & headers , & conn ) . await
}
}
#[ delete( " /organizations/<org_id>/groups/<group_id> " ) ]
#[ delete( " /organizations/<org_id>/groups/<group_id> " ) ]
async fn delete_group ( org_id : OrganizationId , group_id : GroupId , headers : AdminHeaders , conn : DbConn ) -> EmptyResult {
async fn delete_group ( org_id : OrganizationId , group_id : GroupId , headers : AdminHeaders , conn : DbConn ) -> EmptyResult {
_ delete_group( & org_id , & group_id , & headers , & conn ) . await
delete_group_impl ( & org_id , & group_id , & headers , & conn ) . await
}
}
async fn _ delete_group(
async fn delete_group_impl (
org_id : & OrganizationId ,
org_id : & OrganizationId ,
group_id : & GroupId ,
group_id : & GroupId ,
headers : & AdminHeaders ,
headers : & AdminHeaders ,
@ -2708,7 +2733,7 @@ async fn bulk_delete_groups(
let data : BulkGroupIds = data . into_inner ( ) ;
let data : BulkGroupIds = data . into_inner ( ) ;
for group_id in data . ids {
for group_id in data . ids {
_ delete_group( & org_id , & group_id , & headers , & conn ) . await ?
delete_group_impl ( & org_id , & group_id , & headers , & conn ) . await ? ;
}
}
Ok ( ( ) )
Ok ( ( ) )
}
}
@ -2745,7 +2770,7 @@ async fn get_group_members(
if Group ::find_by_uuid_and_org ( & group_id , & org_id , & conn ) . await . is_none ( ) {
if Group ::find_by_uuid_and_org ( & group_id , & org_id , & conn ) . await . is_none ( ) {
err ! ( "Group could not be found!" , "Group uuid is invalid or does not belong to the organization" )
err ! ( "Group could not be found!" , "Group uuid is invalid or does not belong to the organization" )
} ;
}
let group_members : Vec < MembershipId > = GroupUser ::find_by_group ( & group_id , & org_id , & conn )
let group_members : Vec < MembershipId > = GroupUser ::find_by_group ( & group_id , & org_id , & conn )
. await
. await
@ -2773,7 +2798,7 @@ async fn put_group_members(
if Group ::find_by_uuid_and_org ( & group_id , & org_id , & conn ) . await . is_none ( ) {
if Group ::find_by_uuid_and_org ( & group_id , & org_id , & conn ) . await . is_none ( ) {
err ! ( "Group could not be found!" , "Group uuid is invalid or does not belong to the organization" )
err ! ( "Group could not be found!" , "Group uuid is invalid or does not belong to the organization" )
} ;
}
let assigned_members = data . into_inner ( ) ;
let assigned_members = data . into_inner ( ) ;
@ -3027,10 +3052,7 @@ async fn put_reset_password_enrollment(
err ! ( "User to enroll isn't member of required organization" , "The user_id and acting user do not match" ) ;
err ! ( "User to enroll isn't member of required organization" , "The user_id and acting user do not match" ) ;
}
}
let Some ( mut membership ) = Membership ::find_confirmed_by_user_and_org ( & headers . user . uuid , & org_id , & conn ) . await
let mut membership = headers . membership ;
else {
err ! ( "User to enroll isn't member of required organization" )
} ;
check_reset_password_applicable ( & org_id , & conn ) . await ? ;
check_reset_password_applicable ( & org_id , & conn ) . await ? ;
@ -3083,12 +3105,12 @@ async fn get_org_export(org_id: OrganizationId, headers: AdminHeaders, conn: DbC
}
}
Ok ( Json ( json ! ( {
Ok ( Json ( json ! ( {
"collections" : convert_json_key_lcase_first ( _ get_org_collections( & org_id , & conn ) . await ) ,
"collections" : convert_json_key_lcase_first ( get_org_collections_impl ( & org_id , & conn ) . await ) ,
"ciphers" : convert_json_key_lcase_first ( _ get_org_details( & org_id , & headers . host , & headers . user . uuid , & conn ) . await ? ) ,
"ciphers" : convert_json_key_lcase_first ( get_org_details_impl ( & org_id , & headers . host , & headers . user . uuid , & conn ) . await ? ) ,
} ) ) )
} ) ) )
}
}
async fn _ api_key(
async fn api_key (
org_id : & OrganizationId ,
org_id : & OrganizationId ,
data : Json < PasswordOrOtpData > ,
data : Json < PasswordOrOtpData > ,
rotate : bool ,
rotate : bool ,
@ -3104,21 +3126,18 @@ async fn _api_key(
// Validate the admin users password/otp
// Validate the admin users password/otp
data . validate ( & user , true , & conn ) . await ? ;
data . validate ( & user , true , & conn ) . await ? ;
let org_api_key = match OrganizationApiKey ::find_by_org_uuid ( org_id , & conn ) . await {
let org_api_key = if let Some ( mut org_api_key ) = OrganizationApiKey ::find_by_org_uuid ( org_id , & conn ) . await {
Some ( mut org_api_key ) = > {
if rotate {
if rotate {
org_api_key . api_key = crate ::crypto ::generate_api_key ( ) ;
org_api_key . api_key = crate ::crypto ::generate_api_key ( ) ;
org_api_key . revision_date = chrono ::Utc ::now ( ) . naive_utc ( ) ;
org_api_key . revision_date = chrono ::Utc ::now ( ) . naive_utc ( ) ;
org_api_key . save ( & conn ) . await . expect ( "Error rotating organization API Key" ) ;
org_api_key . save ( & conn ) . await . expect ( "Error rotating organization API Key" ) ;
}
org_api_key
}
None = > {
let api_key = crate ::crypto ::generate_api_key ( ) ;
let new_org_api_key = OrganizationApiKey ::new ( org_id . clone ( ) , api_key ) ;
new_org_api_key . save ( & conn ) . await . expect ( "Error creating organization API Key" ) ;
new_org_api_key
}
}
org_api_key
} else {
let api_key = crate ::crypto ::generate_api_key ( ) ;
let new_org_api_key = OrganizationApiKey ::new ( org_id . clone ( ) , api_key ) ;
new_org_api_key . save ( & conn ) . await . expect ( "Error creating organization API Key" ) ;
new_org_api_key
} ;
} ;
Ok ( Json ( json ! ( {
Ok ( Json ( json ! ( {
@ -3129,13 +3148,13 @@ async fn _api_key(
}
}
#[ post( " /organizations/<org_id>/api-key " , data = " <data> " ) ]
#[ post( " /organizations/<org_id>/api-key " , data = " <data> " ) ]
async fn api_key (
async fn post_ api_key(
org_id : OrganizationId ,
org_id : OrganizationId ,
data : Json < PasswordOrOtpData > ,
data : Json < PasswordOrOtpData > ,
headers : AdminHeaders ,
headers : AdminHeaders ,
conn : DbConn ,
conn : DbConn ,
) -> JsonResult {
) -> JsonResult {
_ api_key( & org_id , data , false , headers , conn ) . await
api_key ( & org_id , data , false , headers , conn ) . await
}
}
#[ post( " /organizations/<org_id>/rotate-api-key " , data = " <data> " ) ]
#[ post( " /organizations/<org_id>/rotate-api-key " , data = " <data> " ) ]
@ -3145,5 +3164,5 @@ async fn rotate_api_key(
headers : AdminHeaders ,
headers : AdminHeaders ,
conn : DbConn ,
conn : DbConn ,
) -> JsonResult {
) -> JsonResult {
_ api_key( & org_id , data , true , headers , conn ) . await
api_key ( & org_id , data , true , headers , conn ) . await
}
}