@ -5,8 +5,9 @@ use serde_json::Value;
use crate ::{
api ::{
core ::log_user_event , register_push_device , unregister_push_device , AnonymousNotify , EmptyResult , JsonResult ,
JsonUpcase , Notify , PasswordOrOtpData , UpdateType ,
core ::{ log_user_event , two_factor ::email } ,
register_push_device , unregister_push_device , AnonymousNotify , EmptyResult , JsonResult , Notify ,
PasswordOrOtpData , UpdateType ,
} ,
auth ::{ decode_delete , decode_invite , decode_verify_email , ClientHeaders , Headers } ,
crypto ,
@ -62,29 +63,29 @@ pub fn routes() -> Vec<rocket::Route> {
]
}
#[ derive(De serialize, Debug )]
#[ allow(non_snake_case )]
#[ derive(De bug, De serialize)]
#[ serde(rename_all = " camelCase " )]
pub struct RegisterData {
E mail: String ,
K df: Option < i32 > ,
KdfI terations: Option < i32 > ,
KdfM emory: Option < i32 > ,
KdfP arallelism: Option < i32 > ,
K ey: String ,
K eys: Option < KeysData > ,
MasterPasswordH ash: String ,
MasterPasswordH int: Option < String > ,
N ame: Option < String > ,
T oken: Option < String > ,
e mail: String ,
k df: Option < i32 > ,
kdf_i terations: Option < i32 > ,
kdf_m emory: Option < i32 > ,
kdf_p arallelism: Option < i32 > ,
k ey: String ,
k eys: Option < KeysData > ,
master_password_h ash: String ,
master_password_h int: Option < String > ,
n ame: Option < String > ,
t oken: Option < String > ,
#[ allow(dead_code) ]
OrganizationUserI d: Option < String > ,
organization_user_i d: Option < String > ,
}
#[ derive(De serialize, Debug )]
#[ allow(non_snake_case )]
#[ derive(De bug, De serialize)]
#[ serde(rename_all = " camelCase " )]
struct KeysData {
EncryptedPrivateK ey: String ,
PublicK ey: String ,
encrypted_private_k ey: String ,
public_k ey: String ,
}
/// Trims whitespace from password hints, and converts blank password hints to `None`.
@ -104,19 +105,32 @@ fn enforce_password_hint_setting(password_hint: &Option<String>) -> EmptyResult
}
Ok ( ( ) )
}
async fn is_email_2fa_required ( org_user_uuid : Option < String > , conn : & mut DbConn ) -> bool {
if ! CONFIG . _enable_email_2fa ( ) {
return false ;
}
if CONFIG . email_2fa_enforce_on_verified_invite ( ) {
return true ;
}
if org_user_uuid . is_some ( ) {
return OrgPolicy ::is_enabled_by_org ( & org_user_uuid . unwrap ( ) , OrgPolicyType ::TwoFactorAuthentication , conn )
. await ;
}
false
}
#[ post( " /accounts/register " , data = " <data> " ) ]
async fn register ( data : JsonUpcase < RegisterData > , conn : DbConn ) -> JsonResult {
async fn register ( data : Json < RegisterData > , conn : DbConn ) -> JsonResult {
_register ( data , conn ) . await
}
pub async fn _register ( data : JsonUpcase < RegisterData > , mut conn : DbConn ) -> JsonResult {
let data : RegisterData = data . into_inner ( ) . data ;
let email = data . Email . to_lowercase ( ) ;
pub async fn _register ( data : Json < RegisterData > , mut conn : DbConn ) -> JsonResult {
let data : RegisterData = data . into_inner ( ) ;
let email = data . e mail. to_lowercase ( ) ;
// Check if the length of the username exceeds 50 characters (Same is Upstream Bitwarden)
// This also prevents issues with very long usernames causing to large JWT's. See #2419
if let Some ( ref name ) = data . Name {
if let Some ( ref name ) = data . n ame {
if name . len ( ) > 50 {
err ! ( "The field Name must be a string with a maximum length of 50." ) ;
}
@ -124,7 +138,7 @@ pub async fn _register(data: JsonUpcase<RegisterData>, mut conn: DbConn) -> Json
// Check against the password hint setting here so if it fails, the user
// can retry without losing their invitation below.
let password_hint = clean_password_hint ( & data . MasterPasswordH int) ;
let password_hint = clean_password_hint ( & data . master_password_h int) ;
enforce_password_hint_setting ( & password_hint ) ? ;
let mut verified_by_invite = false ;
@ -135,7 +149,7 @@ pub async fn _register(data: JsonUpcase<RegisterData>, mut conn: DbConn) -> Json
err ! ( "Registration not allowed or user already exists" )
}
if let Some ( token ) = data . T oken {
if let Some ( token ) = data . t oken {
let claims = decode_invite ( & token ) ? ;
if claims . email = = email {
// Verify the email address when signing up via a valid invite token
@ -174,28 +188,28 @@ pub async fn _register(data: JsonUpcase<RegisterData>, mut conn: DbConn) -> Json
// Make sure we don't leave a lingering invitation.
Invitation ::take ( & email , & mut conn ) . await ;
if let Some ( client_kdf_type ) = data . K df {
if let Some ( client_kdf_type ) = data . k df {
user . client_kdf_type = client_kdf_type ;
}
if let Some ( client_kdf_iter ) = data . KdfI terations {
if let Some ( client_kdf_iter ) = data . kdf_i terations {
user . client_kdf_iter = client_kdf_iter ;
}
user . client_kdf_memory = data . KdfM emory;
user . client_kdf_parallelism = data . KdfP arallelism;
user . client_kdf_memory = data . kdf_m emory;
user . client_kdf_parallelism = data . kdf_p arallelism;
user . set_password ( & data . MasterPasswordHash, Some ( data . K ey) , true , None ) ;
user . set_password ( & data . master_password_hash, Some ( data . k ey) , true , None ) ;
user . password_hint = password_hint ;
// Add extra fields if present
if let Some ( name ) = data . N ame {
if let Some ( name ) = data . n ame {
user . name = name ;
}
if let Some ( keys ) = data . K eys {
user . private_key = Some ( keys . EncryptedPrivateK ey) ;
user . public_key = Some ( keys . PublicK ey) ;
if let Some ( keys ) = data . k eys {
user . private_key = Some ( keys . encrypted_private_k ey) ;
user . public_key = Some ( keys . public_k ey) ;
}
if CONFIG . mail_enabled ( ) {
@ -208,12 +222,16 @@ pub async fn _register(data: JsonUpcase<RegisterData>, mut conn: DbConn) -> Json
} else if let Err ( e ) = mail ::send_welcome ( & user . email ) . await {
error ! ( "Error sending welcome email: {:#?}" , e ) ;
}
if verified_by_invite & & is_email_2fa_required ( data . organization_user_id , & mut conn ) . await {
let _ = email ::activate_email_2fa ( & user , & mut conn ) . await ;
}
}
user . save ( & mut conn ) . await ? ;
Ok ( Json ( json ! ( {
" O bject": "register" ,
" C aptchaBypassToken": "" ,
" o bject": "register" ,
" c aptchaBypassToken": "" ,
} ) ) )
}
@ -222,57 +240,57 @@ async fn profile(headers: Headers, mut conn: DbConn) -> Json<Value> {
Json ( headers . user . to_json ( & mut conn ) . await )
}
#[ derive(De serialize, Debug )]
#[ allow(non_snake_case )]
#[ derive(De bug, De serialize)]
#[ serde(rename_all = " camelCase " )]
struct ProfileData {
// C ulture: String, // Ignored, always use en-US
// M asterPasswordHint: Option<String>, // Ignored, has been moved to ChangePassData
N ame: String ,
// c ulture: String, // Ignored, always use en-US
// m asterPasswordHint: Option<String>, // Ignored, has been moved to ChangePassData
n ame: String ,
}
#[ put( " /accounts/profile " , data = " <data> " ) ]
async fn put_profile ( data : Json Upcase < ProfileData > , headers : Headers , conn : DbConn ) -> JsonResult {
async fn put_profile ( data : Json < ProfileData > , headers : Headers , conn : DbConn ) -> JsonResult {
post_profile ( data , headers , conn ) . await
}
#[ post( " /accounts/profile " , data = " <data> " ) ]
async fn post_profile ( data : Json Upcase < ProfileData > , headers : Headers , mut conn : DbConn ) -> JsonResult {
let data : ProfileData = data . into_inner ( ) .data ;
async fn post_profile ( data : Json < ProfileData > , headers : Headers , mut conn : DbConn ) -> JsonResult {
let data : ProfileData = data . into_inner ( ) ;
// Check if the length of the username exceeds 50 characters (Same is Upstream Bitwarden)
// This also prevents issues with very long usernames causing to large JWT's. See #2419
if data . N ame. len ( ) > 50 {
if data . n ame. len ( ) > 50 {
err ! ( "The field Name must be a string with a maximum length of 50." ) ;
}
let mut user = headers . user ;
user . name = data . N ame;
user . name = data . n ame;
user . save ( & mut conn ) . await ? ;
Ok ( Json ( user . to_json ( & mut conn ) . await ) )
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case )]
#[ serde(rename_all = " camelCase " )]
struct AvatarData {
AvatarC olor: Option < String > ,
avatar_c olor: Option < String > ,
}
#[ put( " /accounts/avatar " , data = " <data> " ) ]
async fn put_avatar ( data : Json Upcase < AvatarData > , headers : Headers , mut conn : DbConn ) -> JsonResult {
let data : AvatarData = data . into_inner ( ) .data ;
async fn put_avatar ( data : Json < AvatarData > , headers : Headers , mut conn : DbConn ) -> JsonResult {
let data : AvatarData = data . into_inner ( ) ;
// It looks like it only supports the 6 hex color format.
// If you try to add the short value it will not show that color.
// Check and force 7 chars, including the #.
if let Some ( color ) = & data . AvatarC olor {
if let Some ( color ) = & data . avatar_c olor {
if color . len ( ) ! = 7 {
err ! ( "The field AvatarColor must be a HTML/Hex color code with a length of 7 characters" )
}
}
let mut user = headers . user ;
user . avatar_color = data . AvatarC olor;
user . avatar_color = data . avatar_c olor;
user . save ( & mut conn ) . await ? ;
Ok ( Json ( user . to_json ( & mut conn ) . await ) )
@ -287,62 +305,57 @@ async fn get_public_keys(uuid: &str, _headers: Headers, mut conn: DbConn) -> Jso
} ;
Ok ( Json ( json ! ( {
" U serId": user . uuid ,
" P ublicKey": user . public_key ,
" O bject":"userKey"
" u serId": user . uuid ,
" p ublicKey": user . public_key ,
" o bject":"userKey"
} ) ) )
}
#[ post( " /accounts/keys " , data = " <data> " ) ]
async fn post_keys ( data : Json Upcase < KeysData > , headers : Headers , mut conn : DbConn ) -> JsonResult {
let data : KeysData = data . into_inner ( ) .data ;
async fn post_keys ( data : Json < KeysData > , headers : Headers , mut conn : DbConn ) -> JsonResult {
let data : KeysData = data . into_inner ( ) ;
let mut user = headers . user ;
user . private_key = Some ( data . EncryptedPrivateK ey) ;
user . public_key = Some ( data . PublicK ey) ;
user . private_key = Some ( data . encrypted_private_k ey) ;
user . public_key = Some ( data . public_k ey) ;
user . save ( & mut conn ) . await ? ;
Ok ( Json ( json ! ( {
" P rivateKey": user . private_key ,
" P ublicKey": user . public_key ,
" O bject":"keys"
" p rivateKey": user . private_key ,
" p ublicKey": user . public_key ,
" o bject":"keys"
} ) ) )
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case )]
#[ serde(rename_all = " camelCase " )]
struct ChangePassData {
MasterPasswordH ash: String ,
NewMasterPasswordH ash: String ,
MasterPasswordH int: Option < String > ,
K ey: String ,
master_password_h ash: String ,
new_master_password_h ash: String ,
master_password_h int: Option < String > ,
k ey: String ,
}
#[ post( " /accounts/password " , data = " <data> " ) ]
async fn post_password (
data : JsonUpcase < ChangePassData > ,
headers : Headers ,
mut conn : DbConn ,
nt : Notify < ' _ > ,
) -> EmptyResult {
let data : ChangePassData = data . into_inner ( ) . data ;
async fn post_password ( data : Json < ChangePassData > , headers : Headers , mut conn : DbConn , nt : Notify < ' _ > ) -> EmptyResult {
let data : ChangePassData = data . into_inner ( ) ;
let mut user = headers . user ;
if ! user . check_valid_password ( & data . MasterPasswordH ash) {
if ! user . check_valid_password ( & data . master_password_hash ) {
err ! ( "Invalid password" )
}
user . password_hint = clean_password_hint ( & data . MasterPasswordH int) ;
user . password_hint = clean_password_hint ( & data . master_password_h int) ;
enforce_password_hint_setting ( & user . password_hint ) ? ;
log_user_event ( EventType ::UserChangedPassword as i32 , & user . uuid , headers . device . atype , & headers . ip . ip , & mut conn )
. await ;
user . set_password (
& data . NewMasterPasswordH ash,
Some ( data . K ey) ,
& data . new_master_password_h ash,
Some ( data . k ey) ,
true ,
Some ( vec! [ String ::from ( "post_rotatekey" ) , String ::from ( "get_contacts" ) , String ::from ( "get_public_keys" ) ] ) ,
) ;
@ -358,48 +371,48 @@ async fn post_password(
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case )]
#[ serde(rename_all = " camelCase " )]
struct ChangeKdfData {
K df: i32 ,
KdfI terations: i32 ,
KdfM emory: Option < i32 > ,
KdfP arallelism: Option < i32 > ,
k df: i32 ,
kdf_i terations: i32 ,
kdf_m emory: Option < i32 > ,
kdf_p arallelism: Option < i32 > ,
MasterPasswordH ash: String ,
NewMasterPasswordH ash: String ,
K ey: String ,
master_password_h ash: String ,
new_master_password_h ash: String ,
k ey: String ,
}
#[ post( " /accounts/kdf " , data = " <data> " ) ]
async fn post_kdf ( data : Json Upcase < ChangeKdfData > , headers : Headers , mut conn : DbConn , nt : Notify < ' _ > ) -> EmptyResult {
let data : ChangeKdfData = data . into_inner ( ) .data ;
async fn post_kdf ( data : Json < ChangeKdfData > , headers : Headers , mut conn : DbConn , nt : Notify < ' _ > ) -> EmptyResult {
let data : ChangeKdfData = data . into_inner ( ) ;
let mut user = headers . user ;
if ! user . check_valid_password ( & data . MasterPasswordH ash) {
if ! user . check_valid_password ( & data . master_password_h ash) {
err ! ( "Invalid password" )
}
if data . K df = = UserKdfType ::Pbkdf2 as i32 & & data . KdfI terations < 100_000 {
if data . k df = = UserKdfType ::Pbkdf2 as i32 & & data . kdf_i terations < 100_000 {
err ! ( "PBKDF2 KDF iterations must be at least 100000." )
}
if data . K df = = UserKdfType ::Argon2id as i32 {
if data . KdfI terations < 1 {
if data . k df = = UserKdfType ::Argon2id as i32 {
if data . kdf_i terations < 1 {
err ! ( "Argon2 KDF iterations must be at least 1." )
}
if let Some ( m ) = data . KdfM emory {
if let Some ( m ) = data . kdf_m emory {
if ! ( 15 ..= 1024 ) . contains ( & m ) {
err ! ( "Argon2 memory must be between 15 MB and 1024 MB." )
}
user . client_kdf_memory = data . KdfM emory;
user . client_kdf_memory = data . kdf_m emory;
} else {
err ! ( "Argon2 memory parameter is required." )
}
if let Some ( p ) = data . KdfP arallelism {
if let Some ( p ) = data . kdf_p arallelism {
if ! ( 1 ..= 16 ) . contains ( & p ) {
err ! ( "Argon2 parallelism must be between 1 and 16." )
}
user . client_kdf_parallelism = data . KdfP arallelism;
user . client_kdf_parallelism = data . kdf_p arallelism;
} else {
err ! ( "Argon2 parallelism parameter is required." )
}
@ -407,9 +420,9 @@ async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, mut conn: D
user . client_kdf_memory = None ;
user . client_kdf_parallelism = None ;
}
user . client_kdf_iter = data . KdfI terations;
user . client_kdf_type = data . K df;
user . set_password ( & data . NewMasterPasswordHash, Some ( data . K ey) , true , None ) ;
user . client_kdf_iter = data . kdf_i terations;
user . client_kdf_type = data . k df;
user . set_password ( & data . new_master_password_hash, Some ( data . k ey) , true , None ) ;
let save_result = user . save ( & mut conn ) . await ;
nt . send_logout ( & user , Some ( headers . device . uuid ) ) . await ;
@ -418,29 +431,29 @@ async fn post_kdf(data: JsonUpcase<ChangeKdfData>, headers: Headers, mut conn: D
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case )]
#[ serde(rename_all = " camelCase " )]
struct UpdateFolderData {
I d: String ,
N ame: String ,
i d: String ,
n ame: String ,
}
use super ::ciphers ::CipherData ;
#[ derive(Deserialize) ]
#[ allow(non_snake_case )]
#[ serde(rename_all = " camelCase " )]
struct KeyData {
C iphers: Vec < CipherData > ,
F olders: Vec < UpdateFolderData > ,
K ey: String ,
PrivateK ey: String ,
MasterPasswordH ash: String ,
c iphers: Vec < CipherData > ,
f olders: Vec < UpdateFolderData > ,
k ey: String ,
private_k ey: String ,
master_password_h ash: String ,
}
#[ post( " /accounts/key " , data = " <data> " ) ]
async fn post_rotatekey ( data : Json Upcase < KeyData > , headers : Headers , mut conn : DbConn , nt : Notify < ' _ > ) -> EmptyResult {
let data : KeyData = data . into_inner ( ) .data ;
async fn post_rotatekey ( data : Json < KeyData > , headers : Headers , mut conn : DbConn , nt : Notify < ' _ > ) -> EmptyResult {
let data : KeyData = data . into_inner ( ) ;
if ! headers . user . check_valid_password ( & data . MasterPasswordH ash) {
if ! headers . user . check_valid_password ( & data . master_password_h ash) {
err ! ( "Invalid password" )
}
@ -448,13 +461,13 @@ async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, mut conn: D
// Bitwarden does not process the import if there is one item invalid.
// Since we check for the size of the encrypted note length, we need to do that here to pre-validate it.
// TODO: See if we can optimize the whole cipher adding/importing and prevent duplicate code and checks.
Cipher ::validate_notes ( & data . C iphers) ? ;
Cipher ::validate_notes ( & data . c iphers) ? ;
let user_uuid = & headers . user . uuid ;
// Update folder data
for folder_data in data . F olders {
let mut saved_folder = match Folder ::find_by_uuid ( & folder_data . I d, & mut conn ) . await {
for folder_data in data . f olders {
let mut saved_folder = match Folder ::find_by_uuid ( & folder_data . i d, & mut conn ) . await {
Some ( folder ) = > folder ,
None = > err ! ( "Folder doesn't exist" ) ,
} ;
@ -463,15 +476,15 @@ async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, mut conn: D
err ! ( "The folder is not owned by the user" )
}
saved_folder . name = folder_data . N ame;
saved_folder . name = folder_data . n ame;
saved_folder . save ( & mut conn ) . await ?
}
// Update cipher data
use super ::ciphers ::update_cipher_from_data ;
for cipher_data in data . C iphers {
let mut saved_cipher = match Cipher ::find_by_uuid ( cipher_data . I d. as_ref ( ) . unwrap ( ) , & mut conn ) . await {
for cipher_data in data . c iphers {
let mut saved_cipher = match Cipher ::find_by_uuid ( cipher_data . i d. as_ref ( ) . unwrap ( ) , & mut conn ) . await {
Some ( cipher ) = > cipher ,
None = > err ! ( "Cipher doesn't exist" ) ,
} ;
@ -490,8 +503,8 @@ async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, mut conn: D
// Update user data
let mut user = headers . user ;
user . akey = data . K ey;
user . private_key = Some ( data . PrivateK ey) ;
user . akey = data . k ey;
user . private_key = Some ( data . private_k ey) ;
user . reset_security_stamp ( ) ;
let save_result = user . save ( & mut conn ) . await ;
@ -505,13 +518,8 @@ async fn post_rotatekey(data: JsonUpcase<KeyData>, headers: Headers, mut conn: D
}
#[ post( " /accounts/security-stamp " , data = " <data> " ) ]
async fn post_sstamp (
data : JsonUpcase < PasswordOrOtpData > ,
headers : Headers ,
mut conn : DbConn ,
nt : Notify < ' _ > ,
) -> EmptyResult {
let data : PasswordOrOtpData = data . into_inner ( ) . data ;
async fn post_sstamp ( data : Json < PasswordOrOtpData > , headers : Headers , mut conn : DbConn , nt : Notify < ' _ > ) -> EmptyResult {
let data : PasswordOrOtpData = data . into_inner ( ) ;
let mut user = headers . user ;
data . validate ( & user , true , & mut conn ) . await ? ;
@ -526,82 +534,79 @@ async fn post_sstamp(
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case )]
#[ serde(rename_all = " camelCase " )]
struct EmailTokenData {
MasterPasswordH ash: String ,
NewE mail: String ,
master_password_h ash: String ,
new_e mail: String ,
}
#[ post( " /accounts/email-token " , data = " <data> " ) ]
async fn post_email_token ( data : Json Upcase < EmailTokenData > , headers : Headers , mut conn : DbConn ) -> EmptyResult {
async fn post_email_token ( data : Json < EmailTokenData > , headers : Headers , mut conn : DbConn ) -> EmptyResult {
if ! CONFIG . email_change_allowed ( ) {
err ! ( "Email change is not allowed." ) ;
}
let data : EmailTokenData = data . into_inner ( ) .data ;
let data : EmailTokenData = data . into_inner ( ) ;
let mut user = headers . user ;
if ! user . check_valid_password ( & data . MasterPasswordH ash) {
if ! user . check_valid_password ( & data . master_password_h ash) {
err ! ( "Invalid password" )
}
if User ::find_by_mail ( & data . NewE mail, & mut conn ) . await . is_some ( ) {
if User ::find_by_mail ( & data . new_e mail, & mut conn ) . await . is_some ( ) {
err ! ( "Email already in use" ) ;
}
if ! CONFIG . is_email_domain_allowed ( & data . NewE mail) {
if ! CONFIG . is_email_domain_allowed ( & data . new_e mail) {
err ! ( "Email domain not allowed" ) ;
}
let token = crypto ::generate_email_token ( 6 ) ;
if CONFIG . mail_enabled ( ) {
if let Err ( e ) = mail ::send_change_email ( & data . NewE mail, & token ) . await {
if let Err ( e ) = mail ::send_change_email ( & data . new_e mail, & token ) . await {
error ! ( "Error sending change-email email: {:#?}" , e ) ;
}
} else {
debug ! ( "Email change request for user ({}) to email ({}) with token ({})" , user . uuid , data . new_email , token ) ;
}
user . email_new = Some ( data . NewE mail) ;
user . email_new = Some ( data . new_e mail) ;
user . email_new_token = Some ( token ) ;
user . save ( & mut conn ) . await
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case )]
#[ serde(rename_all = " camelCase " )]
struct ChangeEmailData {
MasterPasswordH ash: String ,
NewE mail: String ,
master_password_h ash: String ,
new_e mail: String ,
K ey: String ,
NewMasterPasswordH ash: String ,
T oken: NumberOrString ,
k ey: String ,
new_master_password_h ash: String ,
t oken: NumberOrString ,
}
#[ post( " /accounts/email " , data = " <data> " ) ]
async fn post_email (
data : JsonUpcase < ChangeEmailData > ,
headers : Headers ,
mut conn : DbConn ,
nt : Notify < ' _ > ,
) -> EmptyResult {
async fn post_email ( data : Json < ChangeEmailData > , headers : Headers , mut conn : DbConn , nt : Notify < ' _ > ) -> EmptyResult {
if ! CONFIG . email_change_allowed ( ) {
err ! ( "Email change is not allowed." ) ;
}
let data : ChangeEmailData = data . into_inner ( ) .data ;
let data : ChangeEmailData = data . into_inner ( ) ;
let mut user = headers . user ;
if ! user . check_valid_password ( & data . MasterPasswordH ash) {
if ! user . check_valid_password ( & data . master_password_h ash) {
err ! ( "Invalid password" )
}
if User ::find_by_mail ( & data . NewE mail, & mut conn ) . await . is_some ( ) {
if User ::find_by_mail ( & data . new_e mail, & mut conn ) . await . is_some ( ) {
err ! ( "Email already in use" ) ;
}
match user . email_new {
Some ( ref val ) = > {
if val ! = & data . NewE mail {
if val ! = & data . new_e mail {
err ! ( "Email change mismatch" ) ;
}
}
@ -612,7 +617,7 @@ async fn post_email(
// Only check the token if we sent out an email...
match user . email_new_token {
Some ( ref val ) = > {
if * val ! = data . T oken. into_string ( ) {
if * val ! = data . t oken. into_string ( ) {
err ! ( "Token mismatch" ) ;
}
}
@ -623,11 +628,11 @@ async fn post_email(
user . verified_at = None ;
}
user . email = data . NewE mail;
user . email = data . new_e mail;
user . email_new = None ;
user . email_new_token = None ;
user . set_password ( & data . NewMasterPasswordHash, Some ( data . K ey) , true , None ) ;
user . set_password ( & data . new_master_password_hash, Some ( data . k ey) , true , None ) ;
let save_result = user . save ( & mut conn ) . await ;
@ -652,22 +657,22 @@ async fn post_verify_email(headers: Headers) -> EmptyResult {
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case )]
#[ serde(rename_all = " camelCase " )]
struct VerifyEmailTokenData {
UserI d: String ,
T oken: String ,
user_i d: String ,
t oken: String ,
}
#[ post( " /accounts/verify-email-token " , data = " <data> " ) ]
async fn post_verify_email_token ( data : Json Upcase < VerifyEmailTokenData > , mut conn : DbConn ) -> EmptyResult {
let data : VerifyEmailTokenData = data . into_inner ( ) .data ;
async fn post_verify_email_token ( data : Json < VerifyEmailTokenData > , mut conn : DbConn ) -> EmptyResult {
let data : VerifyEmailTokenData = data . into_inner ( ) ;
let mut user = match User ::find_by_uuid ( & data . UserI d, & mut conn ) . await {
let mut user = match User ::find_by_uuid ( & data . user_i d, & mut conn ) . await {
Some ( user ) = > user ,
None = > err ! ( "User doesn't exist" ) ,
} ;
let claims = match decode_verify_email ( & data . T oken) {
let claims = match decode_verify_email ( & data . t oken) {
Ok ( claims ) = > claims ,
Err ( _ ) = > err ! ( "Invalid claim" ) ,
} ;
@ -685,17 +690,17 @@ async fn post_verify_email_token(data: JsonUpcase<VerifyEmailTokenData>, mut con
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case )]
#[ serde(rename_all = " camelCase " )]
struct DeleteRecoverData {
E mail: String ,
e mail: String ,
}
#[ post( " /accounts/delete-recover " , data = " <data> " ) ]
async fn post_delete_recover ( data : Json Upcase < DeleteRecoverData > , mut conn : DbConn ) -> EmptyResult {
let data : DeleteRecoverData = data . into_inner ( ) .data ;
async fn post_delete_recover ( data : Json < DeleteRecoverData > , mut conn : DbConn ) -> EmptyResult {
let data : DeleteRecoverData = data . into_inner ( ) ;
if CONFIG . mail_enabled ( ) {
if let Some ( user ) = User ::find_by_mail ( & data . E mail, & mut conn ) . await {
if let Some ( user ) = User ::find_by_mail ( & data . e mail, & mut conn ) . await {
if let Err ( e ) = mail ::send_delete_account ( & user . email , & user . uuid ) . await {
error ! ( "Error sending delete account email: {:#?}" , e ) ;
}
@ -711,22 +716,22 @@ async fn post_delete_recover(data: JsonUpcase<DeleteRecoverData>, mut conn: DbCo
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case )]
#[ serde(rename_all = " camelCase " )]
struct DeleteRecoverTokenData {
UserI d: String ,
T oken: String ,
user_i d: String ,
t oken: String ,
}
#[ post( " /accounts/delete-recover-token " , data = " <data> " ) ]
async fn post_delete_recover_token ( data : Json Upcase < DeleteRecoverTokenData > , mut conn : DbConn ) -> EmptyResult {
let data : DeleteRecoverTokenData = data . into_inner ( ) .data ;
async fn post_delete_recover_token ( data : Json < DeleteRecoverTokenData > , mut conn : DbConn ) -> EmptyResult {
let data : DeleteRecoverTokenData = data . into_inner ( ) ;
let user = match User ::find_by_uuid ( & data . UserI d, & mut conn ) . await {
let user = match User ::find_by_uuid ( & data . user_i d, & mut conn ) . await {
Some ( user ) = > user ,
None = > err ! ( "User doesn't exist" ) ,
} ;
let claims = match decode_delete ( & data . T oken) {
let claims = match decode_delete ( & data . t oken) {
Ok ( claims ) = > claims ,
Err ( _ ) = > err ! ( "Invalid claim" ) ,
} ;
@ -737,13 +742,13 @@ async fn post_delete_recover_token(data: JsonUpcase<DeleteRecoverTokenData>, mut
}
#[ post( " /accounts/delete " , data = " <data> " ) ]
async fn post_delete_account ( data : Json Upcase < PasswordOrOtpData > , headers : Headers , conn : DbConn ) -> EmptyResult {
async fn post_delete_account ( data : Json < PasswordOrOtpData > , headers : Headers , conn : DbConn ) -> EmptyResult {
delete_account ( data , headers , conn ) . await
}
#[ delete( " /accounts " , data = " <data> " ) ]
async fn delete_account ( data : Json Upcase < PasswordOrOtpData > , headers : Headers , mut conn : DbConn ) -> EmptyResult {
let data : PasswordOrOtpData = data . into_inner ( ) .data ;
async fn delete_account ( data : Json < PasswordOrOtpData > , headers : Headers , mut conn : DbConn ) -> EmptyResult {
let data : PasswordOrOtpData = data . into_inner ( ) ;
let user = headers . user ;
data . validate ( & user , true , & mut conn ) . await ? ;
@ -753,26 +758,26 @@ async fn delete_account(data: JsonUpcase<PasswordOrOtpData>, headers: Headers, m
#[ get( " /accounts/revision-date " ) ]
fn revision_date ( headers : Headers ) -> JsonResult {
let revision_date = headers . user . updated_at . timestamp_millis( ) ;
let revision_date = headers . user . updated_at . and_utc( ) . timestamp_millis( ) ;
Ok ( Json ( json ! ( revision_date ) ) )
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case )]
#[ serde(rename_all = " camelCase " )]
struct PasswordHintData {
E mail: String ,
e mail: String ,
}
#[ post( " /accounts/password-hint " , data = " <data> " ) ]
async fn password_hint ( data : Json Upcase < PasswordHintData > , mut conn : DbConn ) -> EmptyResult {
async fn password_hint ( data : Json < PasswordHintData > , mut conn : DbConn ) -> EmptyResult {
if ! CONFIG . mail_enabled ( ) & & ! CONFIG . show_password_hint ( ) {
err ! ( "This server is not configured to provide password hints." ) ;
}
const NO_HINT : & str = "Sorry, you have no password hint..." ;
let data : PasswordHintData = data . into_inner ( ) .data ;
let email = & data . E mail;
let data : PasswordHintData = data . into_inner ( ) ;
let email = & data . e mail;
match User ::find_by_mail ( email , & mut conn ) . await {
None = > {
@ -806,29 +811,29 @@ async fn password_hint(data: JsonUpcase<PasswordHintData>, mut conn: DbConn) ->
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case )]
#[ serde(rename_all = " camelCase " )]
pub struct PreloginData {
E mail: String ,
e mail: String ,
}
#[ post( " /accounts/prelogin " , data = " <data> " ) ]
async fn prelogin ( data : Json Upcase < PreloginData > , conn : DbConn ) -> Json < Value > {
async fn prelogin ( data : Json < PreloginData > , conn : DbConn ) -> Json < Value > {
_prelogin ( data , conn ) . await
}
pub async fn _prelogin ( data : Json Upcase < PreloginData > , mut conn : DbConn ) -> Json < Value > {
let data : PreloginData = data . into_inner ( ) .data ;
pub async fn _prelogin ( data : Json < PreloginData > , mut conn : DbConn ) -> Json < Value > {
let data : PreloginData = data . into_inner ( ) ;
let ( kdf_type , kdf_iter , kdf_mem , kdf_para ) = match User ::find_by_mail ( & data . E mail, & mut conn ) . await {
let ( kdf_type , kdf_iter , kdf_mem , kdf_para ) = match User ::find_by_mail ( & data . e mail, & mut conn ) . await {
Some ( user ) = > ( user . client_kdf_type , user . client_kdf_iter , user . client_kdf_memory , user . client_kdf_parallelism ) ,
None = > ( User ::CLIENT_KDF_TYPE_DEFAULT , User ::CLIENT_KDF_ITER_DEFAULT , None , None ) ,
} ;
let result = json ! ( {
" K df": kdf_type ,
" K dfIterations": kdf_iter ,
" K dfMemory": kdf_mem ,
" K dfParallelism": kdf_para ,
" k df": kdf_type ,
" k dfIterations": kdf_iter ,
" k dfMemory": kdf_mem ,
" k dfParallelism": kdf_para ,
} ) ;
Json ( result )
@ -836,27 +841,27 @@ pub async fn _prelogin(data: JsonUpcase<PreloginData>, mut conn: DbConn) -> Json
// https://github.com/bitwarden/server/blob/master/src/Api/Models/Request/Accounts/SecretVerificationRequestModel.cs
#[ derive(Deserialize) ]
#[ allow(non_snake_case )]
#[ serde(rename_all = " camelCase " )]
struct SecretVerificationRequest {
MasterPasswordH ash: String ,
master_password_h ash: String ,
}
#[ post( " /accounts/verify-password " , data = " <data> " ) ]
fn verify_password ( data : Json Upcase < SecretVerificationRequest > , headers : Headers ) -> EmptyResult {
let data : SecretVerificationRequest = data . into_inner ( ) .data ;
fn verify_password ( data : Json < SecretVerificationRequest > , headers : Headers ) -> EmptyResult {
let data : SecretVerificationRequest = data . into_inner ( ) ;
let user = headers . user ;
if ! user . check_valid_password ( & data . MasterPasswordH ash) {
if ! user . check_valid_password ( & data . master_password_h ash) {
err ! ( "Invalid password" )
}
Ok ( ( ) )
}
async fn _api_key ( data : Json Upcase < PasswordOrOtpData > , rotate : bool , headers : Headers , mut conn : DbConn ) -> JsonResult {
async fn _api_key ( data : Json < PasswordOrOtpData > , rotate : bool , headers : Headers , mut conn : DbConn ) -> JsonResult {
use crate ::util ::format_date ;
let data : PasswordOrOtpData = data . into_inner ( ) .data ;
let data : PasswordOrOtpData = data . into_inner ( ) ;
let mut user = headers . user ;
data . validate ( & user , true , & mut conn ) . await ? ;
@ -867,19 +872,19 @@ async fn _api_key(data: JsonUpcase<PasswordOrOtpData>, rotate: bool, headers: He
}
Ok ( Json ( json ! ( {
" A piKey": user . api_key ,
" R evisionDate": format_date ( & user . updated_at ) ,
" O bject": "apiKey" ,
" a piKey": user . api_key ,
" r evisionDate": format_date ( & user . updated_at ) ,
" o bject": "apiKey" ,
} ) ) )
}
#[ post( " /accounts/api-key " , data = " <data> " ) ]
async fn api_key ( data : Json Upcase < PasswordOrOtpData > , headers : Headers , conn : DbConn ) -> JsonResult {
async fn api_key ( data : Json < PasswordOrOtpData > , headers : Headers , conn : DbConn ) -> JsonResult {
_api_key ( data , false , headers , conn ) . await
}
#[ post( " /accounts/rotate-api-key " , data = " <data> " ) ]
async fn rotate_api_key ( data : Json Upcase < PasswordOrOtpData > , headers : Headers , conn : DbConn ) -> JsonResult {
async fn rotate_api_key ( data : Json < PasswordOrOtpData > , headers : Headers , conn : DbConn ) -> JsonResult {
_api_key ( data , true , headers , conn ) . await
}
@ -940,20 +945,20 @@ impl<'r> FromRequest<'r> for KnownDevice {
}
#[ derive(Deserialize) ]
#[ allow(non_snake_case )]
#[ serde(rename_all = " camelCase " )]
struct PushToken {
PushT oken: String ,
push_t oken: String ,
}
#[ post( " /devices/identifier/<uuid>/token " , data = " <data> " ) ]
async fn post_device_token ( uuid : & str , data : Json Upcase < PushToken > , headers : Headers , conn : DbConn ) -> EmptyResult {
async fn post_device_token ( uuid : & str , data : Json < PushToken > , headers : Headers , conn : DbConn ) -> EmptyResult {
put_device_token ( uuid , data , headers , conn ) . await
}
#[ put( " /devices/identifier/<uuid>/token " , data = " <data> " ) ]
async fn put_device_token ( uuid : & str , data : Json Upcase < PushToken > , headers : Headers , mut conn : DbConn ) -> EmptyResult {
let data = data . into_inner ( ) .data ;
let token = data . PushT oken;
async fn put_device_token ( uuid : & str , data : Json < PushToken > , headers : Headers , mut conn : DbConn ) -> EmptyResult {
let data = data . into_inner ( ) ;
let token = data . push_t oken;
let mut device = match Device ::find_by_uuid_and_user ( & headers . device . uuid , & headers . user . uuid , & mut conn ) . await {
Some ( device ) = > device ,
@ -1008,12 +1013,12 @@ async fn post_clear_device_token(uuid: &str, conn: DbConn) -> EmptyResult {
}
#[ derive(Debug, Deserialize) ]
#[ allow(non_snake_case )]
#[ serde(rename_all = " camelCase " )]
struct AuthRequestRequest {
access C ode: String ,
device I dentifier: String ,
access _c ode: String ,
device _i dentifier: String ,
email : String ,
public K ey: String ,
public _k ey: String ,
#[ serde(alias = " type " ) ]
_type : i32 ,
}
@ -1036,15 +1041,15 @@ async fn post_auth_request(
let mut auth_request = AuthRequest ::new (
user . uuid . clone ( ) ,
data . device I dentifier. clone ( ) ,
data . device _i dentifier. clone ( ) ,
headers . device_type ,
headers . ip . ip . to_string ( ) ,
data . access C ode,
data . public K ey,
data . access _c ode,
data . public _k ey,
) ;
auth_request . save ( & mut conn ) . await ? ;
nt . send_auth_request ( & user . uuid , & auth_request . uuid , & data . device I dentifier, & mut conn ) . await ;
nt . send_auth_request ( & user . uuid , & auth_request . uuid , & data . device _i dentifier, & mut conn ) . await ;
Ok ( Json ( json ! ( {
"id" : auth_request . uuid ,
@ -1090,12 +1095,12 @@ async fn get_auth_request(uuid: &str, mut conn: DbConn) -> JsonResult {
}
#[ derive(Debug, Deserialize) ]
#[ allow(non_snake_case )]
#[ serde(rename_all = " camelCase " )]
struct AuthResponseRequest {
device I dentifier: String ,
device _i dentifier: String ,
key : String ,
master PasswordH ash: Option < String > ,
request A pproved: bool ,
master _password_h ash: Option < String > ,
request _a pproved: bool ,
}
#[ put( " /auth-requests/<uuid> " , data = " <data> " ) ]
@ -1114,15 +1119,15 @@ async fn put_auth_request(
}
} ;
auth_request . approved = Some ( data . request A pproved) ;
auth_request . approved = Some ( data . request _a pproved) ;
auth_request . enc_key = Some ( data . key ) ;
auth_request . master_password_hash = data . master PasswordH ash;
auth_request . response_device_id = Some ( data . device I dentifier. clone ( ) ) ;
auth_request . master_password_hash = data . master _password_h ash;
auth_request . response_device_id = Some ( data . device _i dentifier. clone ( ) ) ;
auth_request . save ( & mut conn ) . await ? ;
if auth_request . approved . unwrap_or ( false ) {
ant . send_auth_response ( & auth_request . user_uuid , & auth_request . uuid ) . await ;
nt . send_auth_response ( & auth_request . user_uuid , & auth_request . uuid , data . device I dentifier, & mut conn ) . await ;
nt . send_auth_response ( & auth_request . user_uuid , & auth_request . uuid , data . device _i dentifier, & mut conn ) . await ;
}
let response_date_utc = auth_request . response_date . map ( | response_date | response_date . and_utc ( ) ) ;