accounts.rs (23082B)
1 use crate::{ 2 api::{EmptyResult, JsonResult, PasswordOrOtpData}, 3 auth::{ClientHeaders, Headers, decode_delete}, 4 db::{ 5 DbConn, 6 models::{Cipher, Device, Folder, User, UserKdfType, UserOrganization}, 7 }, 8 error::Error, 9 util::NumberOrString, 10 }; 11 use rocket::serde::json::Json; 12 use rocket::{ 13 http::Status, 14 request::{FromRequest, Outcome, Request}, 15 }; 16 use serde_json::Value; 17 18 pub fn routes() -> Vec<rocket::Route> { 19 routes![ 20 api_key, 21 delete_account, 22 get_auth_request, 23 get_auth_request_response, 24 get_auth_requests, 25 get_known_device, 26 get_public_keys, 27 password_hint, 28 post_auth_request, 29 post_clear_device_token, 30 post_delete_account, 31 post_delete_recover, 32 post_delete_recover_token, 33 post_device_token, 34 post_email, 35 post_email_token, 36 post_kdf, 37 post_keys, 38 post_password, 39 post_profile, 40 post_rotatekey, 41 post_sstamp, 42 post_verify_email, 43 post_verify_email_token, 44 prelogin, 45 profile, 46 put_auth_request, 47 put_avatar, 48 put_clear_device_token, 49 put_device_token, 50 put_profile, 51 register, 52 revision_date, 53 rotate_api_key, 54 verify_password, 55 ] 56 } 57 58 #[derive(Debug, Deserialize)] 59 #[serde(rename_all = "camelCase")] 60 pub struct RegisterData { 61 #[allow(dead_code)] 62 email: String, 63 #[allow(dead_code)] 64 kdf: Option<i32>, 65 #[allow(dead_code)] 66 kdf_iterations: Option<i32>, 67 #[allow(dead_code)] 68 kdf_memory: Option<i32>, 69 #[allow(dead_code)] 70 kdf_parallelism: Option<i32>, 71 #[allow(dead_code)] 72 key: String, 73 #[allow(dead_code)] 74 keys: Option<KeysData>, 75 #[allow(dead_code)] 76 master_password_hash: String, 77 #[allow(dead_code)] 78 master_password_hint: Option<String>, 79 #[allow(dead_code)] 80 name: Option<String>, 81 #[allow(dead_code)] 82 token: Option<String>, 83 #[allow(dead_code)] 84 organization_user_id: Option<String>, 85 } 86 87 #[derive(Debug, Deserialize)] 88 #[serde(rename_all = "camelCase")] 89 struct KeysData { 90 encrypted_private_key: String, 91 public_key: String, 92 } 93 94 /// Trims whitespace from password hints, and converts blank password hints to `None`. 95 fn clean_password_hint(password_hint: Option<&str>) -> Option<String> { 96 password_hint.and_then(|h| match h.trim() { 97 "" => None, 98 ht => Some(ht.to_owned()), 99 }) 100 } 101 102 fn enforce_password_hint_setting(password_hint: Option<&str>) -> EmptyResult { 103 if password_hint.is_some() { 104 err!( 105 "Password hints have been disabled by the administrator. Remove the hint and try again." 106 ); 107 } 108 Ok(()) 109 } 110 111 #[allow(unused_variables, clippy::needless_pass_by_value)] 112 #[post("/accounts/register", data = "<data>")] 113 fn register(data: Json<RegisterData>) -> Error { 114 const MSG: &str = "Registration is permanently disabled."; 115 Error::new(MSG, MSG) 116 } 117 #[get("/accounts/profile")] 118 async fn profile(headers: Headers, conn: DbConn) -> Json<Value> { 119 Json(headers.user.to_json(&conn).await) 120 } 121 122 #[derive(Deserialize)] 123 #[serde(rename_all = "camelCase")] 124 struct ProfileData { 125 // Culture: String, // Ignored, always use en-US 126 // MasterPasswordHint: Option<String>, // Ignored, has been moved to ChangePassData 127 name: String, 128 } 129 130 #[put("/accounts/profile", data = "<data>")] 131 async fn put_profile(data: Json<ProfileData>, headers: Headers, conn: DbConn) -> JsonResult { 132 post_profile(data, headers, conn).await 133 } 134 135 #[post("/accounts/profile", data = "<data>")] 136 async fn post_profile(data: Json<ProfileData>, headers: Headers, conn: DbConn) -> JsonResult { 137 let prof_data: ProfileData = data.into_inner(); 138 // Check if the length of the username exceeds 50 characters (Same is Upstream Bitwarden) 139 // This also prevents issues with very long usernames causing to large JWT's. See #2419 140 if prof_data.name.len() > 50 { 141 err!("The field Name must be a string with a maximum length of 50."); 142 } 143 let mut user = headers.user; 144 user.name = prof_data.name; 145 user.save(&conn).await?; 146 Ok(Json(user.to_json(&conn).await)) 147 } 148 149 #[derive(Deserialize)] 150 #[serde(rename_all = "camelCase")] 151 struct AvatarData { 152 avatar_color: Option<String>, 153 } 154 155 #[put("/accounts/avatar", data = "<data>")] 156 async fn put_avatar(data: Json<AvatarData>, headers: Headers, conn: DbConn) -> JsonResult { 157 let av_data: AvatarData = data.into_inner(); 158 // It looks like it only supports the 6 hex color format. 159 // If you try to add the short value it will not show that color. 160 // Check and force 7 chars, including the #. 161 if let Some(ref color) = av_data.avatar_color { 162 if color.len() != 7 { 163 err!( 164 "The field AvatarColor must be a HTML/Hex color code with a length of 7 characters" 165 ) 166 } 167 } 168 let mut user = headers.user; 169 user.avatar_color = av_data.avatar_color; 170 user.save(&conn).await?; 171 Ok(Json(user.to_json(&conn).await)) 172 } 173 174 #[get("/users/<uuid>/public-key")] 175 async fn get_public_keys(uuid: &str, _headers: Headers, conn: DbConn) -> JsonResult { 176 let Some(user) = User::find_by_uuid(uuid, &conn).await else { 177 err!("User doesn't exist") 178 }; 179 Ok(Json(json!({ 180 "userId": user.uuid, 181 "publicKey": user.public_key, 182 "object":"userKey" 183 }))) 184 } 185 186 #[post("/accounts/keys", data = "<data>")] 187 async fn post_keys(data: Json<KeysData>, headers: Headers, conn: DbConn) -> JsonResult { 188 let key_data: KeysData = data.into_inner(); 189 let mut user = headers.user; 190 user.private_key = Some(key_data.encrypted_private_key); 191 user.public_key = Some(key_data.public_key); 192 user.save(&conn).await?; 193 Ok(Json(json!({ 194 "privateKey": user.private_key, 195 "publicKey": user.public_key, 196 "object":"keys" 197 }))) 198 } 199 200 #[derive(Deserialize)] 201 #[serde(rename_all = "camelCase")] 202 struct ChangePassData { 203 master_password_hash: String, 204 new_master_password_hash: String, 205 master_password_hint: Option<String>, 206 key: String, 207 } 208 209 #[post("/accounts/password", data = "<data>")] 210 async fn post_password(data: Json<ChangePassData>, headers: Headers, conn: DbConn) -> EmptyResult { 211 let pass_data: ChangePassData = data.into_inner(); 212 let mut user = headers.user; 213 if !user.check_valid_password(&pass_data.master_password_hash) { 214 err!("Invalid password") 215 } 216 user.password_hint = clean_password_hint(pass_data.master_password_hint.as_deref()); 217 enforce_password_hint_setting(user.password_hint.as_deref())?; 218 user.set_password( 219 &pass_data.new_master_password_hash, 220 Some(pass_data.key), 221 true, 222 Some(vec![ 223 String::from("post_rotatekey"), 224 String::from("get_contacts"), 225 String::from("get_public_keys"), 226 ]), 227 ); 228 user.save(&conn).await 229 } 230 231 #[derive(Deserialize)] 232 #[serde(rename_all = "camelCase")] 233 struct ChangeKdfData { 234 kdf: i32, 235 kdf_iterations: u32, 236 kdf_memory: Option<u32>, 237 kdf_parallelism: Option<u32>, 238 master_password_hash: String, 239 new_master_password_hash: String, 240 key: String, 241 } 242 243 #[post("/accounts/kdf", data = "<data>")] 244 async fn post_kdf(data: Json<ChangeKdfData>, headers: Headers, conn: DbConn) -> EmptyResult { 245 let kdf_data: ChangeKdfData = data.into_inner(); 246 let mut user = headers.user; 247 if !user.check_valid_password(&kdf_data.master_password_hash) { 248 err!("Invalid password") 249 } 250 if kdf_data.kdf == i32::from(UserKdfType::Pbkdf2) && kdf_data.kdf_iterations < 100_000u32 { 251 err!("PBKDF2 KDF iterations must be at least 100000.") 252 } 253 if kdf_data.kdf == i32::from(UserKdfType::Argon2id) { 254 if kdf_data.kdf_iterations < 1u32 { 255 err!("Argon2 KDF iterations must be at least 1.") 256 } 257 if let Some(m) = kdf_data.kdf_memory { 258 if !(15u32..=1024u32).contains(&m) { 259 err!("Argon2 memory must be between 15 MB and 1024 MB.") 260 } 261 user.set_client_kdf_memory(kdf_data.kdf_memory); 262 } else { 263 err!("Argon2 memory parameter is required.") 264 } 265 if let Some(p) = kdf_data.kdf_parallelism { 266 if !(1u32..=16u32).contains(&p) { 267 err!("Argon2 parallelism must be between 1 and 16.") 268 } 269 user.set_client_kdf_parallelism(kdf_data.kdf_parallelism); 270 } else { 271 err!("Argon2 parallelism parameter is required.") 272 } 273 } else { 274 user.set_client_kdf_memory(None); 275 user.set_client_kdf_parallelism(None); 276 } 277 user.set_client_kdf_iter(kdf_data.kdf_iterations); 278 user.client_kdf_type = kdf_data.kdf; 279 user.set_password( 280 &kdf_data.new_master_password_hash, 281 Some(kdf_data.key), 282 true, 283 None, 284 ); 285 user.save(&conn).await 286 } 287 288 #[derive(Deserialize)] 289 #[serde(rename_all = "camelCase")] 290 struct UpdateFolderData { 291 id: Option<String>, 292 name: String, 293 } 294 295 #[derive(Deserialize)] 296 #[serde(rename_all = "camelCase")] 297 struct UpdateResetPasswordData { 298 organization_id: String, 299 reset_password_key: String, 300 } 301 302 use super::ciphers::CipherData; 303 304 #[derive(Deserialize)] 305 #[serde(rename_all = "camelCase")] 306 struct KeyData { 307 ciphers: Vec<CipherData>, 308 folders: Vec<UpdateFolderData>, 309 reset_password_keys: Vec<UpdateResetPasswordData>, 310 key: String, 311 master_password_hash: String, 312 private_key: String, 313 } 314 315 #[post("/accounts/key", data = "<data>")] 316 async fn post_rotatekey(data: Json<KeyData>, headers: Headers, conn: DbConn) -> EmptyResult { 317 let key_data: KeyData = data.into_inner(); 318 if !headers 319 .user 320 .check_valid_password(&key_data.master_password_hash) 321 { 322 err!("Invalid password") 323 } 324 // Validate the import before continuing 325 // Bitwarden does not process the import if there is one item invalid. 326 // Since we check for the size of the encrypted note length, we need to do that here to pre-validate it. 327 // TODO: See if we can optimize the whole cipher adding/importing and prevent duplicate code and checks. 328 Cipher::validate_cipher_data(&key_data.ciphers)?; 329 let user_uuid = &headers.user.uuid; 330 // Update folder data 331 for folder_data in key_data.folders { 332 if let Some(folder_id) = folder_data.id { 333 let Some(mut saved_folder) = Folder::find_by_uuid(&folder_id, &conn).await else { 334 err!("Folder doesn't exist") 335 }; 336 if &saved_folder.user_uuid != user_uuid { 337 err!("The folder is not owned by the user") 338 } 339 340 saved_folder.name = folder_data.name; 341 saved_folder.save(&conn).await?; 342 } 343 } 344 for reset_password_data in key_data.reset_password_keys { 345 let Some(mut user_org) = UserOrganization::find_by_user_and_org( 346 user_uuid, 347 &reset_password_data.organization_id, 348 &conn, 349 ) 350 .await 351 else { 352 err!("Reset password doesn't exist") 353 }; 354 user_org.reset_password_key = Some(reset_password_data.reset_password_key); 355 user_org.save(&conn).await?; 356 } 357 // Update cipher data 358 use super::ciphers::update_cipher_from_data; 359 for cipher_data in key_data.ciphers { 360 if cipher_data.organization_id.is_none() { 361 let Some(mut saved_cipher) = 362 Cipher::find_by_uuid(cipher_data.id.as_ref().unwrap(), &conn).await 363 else { 364 err!("Cipher doesn't exist") 365 }; 366 if saved_cipher.user_uuid.as_ref().unwrap() != user_uuid { 367 err!("The cipher is not owned by the user") 368 } 369 update_cipher_from_data(&mut saved_cipher, cipher_data, &headers, None, &conn, true) 370 .await?; 371 } 372 } 373 374 // Update user data 375 let mut user = headers.user; 376 user.akey = key_data.key; 377 user.private_key = Some(key_data.private_key); 378 user.reset_security_stamp(); 379 user.save(&conn).await 380 } 381 382 #[post("/accounts/security-stamp", data = "<data>")] 383 async fn post_sstamp(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> EmptyResult { 384 let otp_data: PasswordOrOtpData = data.into_inner(); 385 let mut user = headers.user; 386 otp_data.validate(&user)?; 387 Device::delete_all_by_user(&user.uuid, &conn).await?; 388 user.reset_security_stamp(); 389 user.save(&conn).await 390 } 391 392 #[derive(Deserialize)] 393 #[serde(rename_all = "camelCase")] 394 struct EmailTokenData { 395 #[allow(dead_code)] 396 master_password_hash: String, 397 #[allow(dead_code)] 398 new_email: String, 399 } 400 401 #[allow(unused_variables, clippy::needless_pass_by_value)] 402 #[post("/accounts/email-token", data = "<data>")] 403 fn post_email_token(data: Json<EmailTokenData>, _headers: Headers) -> Error { 404 const MSG: &str = "E-mail change is not allowed."; 405 Error::new(MSG, MSG) 406 } 407 408 #[derive(Deserialize)] 409 #[serde(rename_all = "camelCase")] 410 struct ChangeEmailData { 411 #[allow(dead_code)] 412 master_password_hash: String, 413 #[allow(dead_code)] 414 new_email: String, 415 #[allow(dead_code)] 416 key: String, 417 #[allow(dead_code)] 418 new_master_password_hash: String, 419 #[allow(dead_code)] 420 token: NumberOrString, 421 } 422 423 #[allow(unused_variables, clippy::needless_pass_by_value)] 424 #[post("/accounts/email", data = "<data>")] 425 fn post_email(data: Json<ChangeEmailData>, _headers: Headers) -> Error { 426 const MSG: &str = "E-mail change is not allowed."; 427 Error::new(MSG, MSG) 428 } 429 430 #[allow(clippy::needless_pass_by_value)] 431 #[post("/accounts/verify-email")] 432 fn post_verify_email(_headers: Headers) -> Error { 433 const MSG: &str = "E-mail is disabled."; 434 Error::new(MSG, MSG) 435 } 436 437 #[derive(Deserialize)] 438 #[serde(rename_all = "camelCase")] 439 struct VerifyEmailTokenData { 440 #[allow(dead_code)] 441 user_id: String, 442 #[allow(dead_code)] 443 token: String, 444 } 445 446 #[allow(unused_variables, clippy::needless_pass_by_value)] 447 #[post("/accounts/verify-email-token", data = "<data>")] 448 fn post_verify_email_token(data: Json<VerifyEmailTokenData>) -> Error { 449 const MSG: &str = "E-mail is disabled."; 450 Error::new(MSG, MSG) 451 } 452 453 #[derive(Deserialize)] 454 #[serde(rename_all = "camelCase")] 455 struct DeleteRecoverData { 456 #[allow(dead_code)] 457 email: String, 458 } 459 460 #[allow(unused_variables, clippy::needless_pass_by_value)] 461 #[post("/accounts/delete-recover", data = "<data>")] 462 fn post_delete_recover(data: Json<DeleteRecoverData>) -> Error { 463 const MSG: &str = "Account deletion is disabled with at this endpoint."; 464 Error::new(MSG, MSG) 465 } 466 467 #[derive(Deserialize)] 468 #[serde(rename_all = "camelCase")] 469 struct DeleteRecoverTokenData { 470 user_id: String, 471 token: String, 472 } 473 474 #[post("/accounts/delete-recover-token", data = "<data>")] 475 async fn post_delete_recover_token( 476 data: Json<DeleteRecoverTokenData>, 477 conn: DbConn, 478 ) -> EmptyResult { 479 let token_data: DeleteRecoverTokenData = data.into_inner(); 480 let Some(user) = User::find_by_uuid(&token_data.user_id, &conn).await else { 481 err!("User doesn't exist") 482 }; 483 let Ok(claims) = decode_delete(&token_data.token) else { 484 err!("Invalid claim") 485 }; 486 if claims.sub != user.uuid { 487 err!("Invalid claim"); 488 } 489 user.delete(&conn).await 490 } 491 492 #[post("/accounts/delete", data = "<data>")] 493 async fn post_delete_account( 494 data: Json<PasswordOrOtpData>, 495 headers: Headers, 496 conn: DbConn, 497 ) -> EmptyResult { 498 delete_account(data, headers, conn).await 499 } 500 501 #[delete("/accounts", data = "<data>")] 502 async fn delete_account( 503 data: Json<PasswordOrOtpData>, 504 headers: Headers, 505 conn: DbConn, 506 ) -> EmptyResult { 507 let otp_data: PasswordOrOtpData = data.into_inner(); 508 let user = headers.user; 509 otp_data.validate(&user)?; 510 user.delete(&conn).await 511 } 512 #[allow(clippy::needless_pass_by_value)] 513 #[get("/accounts/revision-date")] 514 fn revision_date(headers: Headers) -> Json<Value> { 515 Json(json!(headers.user.updated_at.and_utc().timestamp_millis())) 516 } 517 518 #[derive(Deserialize)] 519 #[serde(rename_all = "camelCase")] 520 struct PasswordHintData { 521 #[allow(dead_code)] 522 email: String, 523 } 524 525 #[allow(unused_variables, clippy::needless_pass_by_value)] 526 #[post("/accounts/password-hint", data = "<data>")] 527 fn password_hint(data: Json<PasswordHintData>) -> Error { 528 const MSG: &str = "Password hints are disabled."; 529 Error::new(MSG, MSG) 530 } 531 532 #[derive(Deserialize)] 533 #[serde(rename_all = "camelCase")] 534 pub struct PreloginData { 535 email: String, 536 } 537 538 #[post("/accounts/prelogin", data = "<data>")] 539 async fn prelogin(data: Json<PreloginData>, conn: DbConn) -> Json<Value> { 540 _prelogin(data, conn).await 541 } 542 543 pub async fn _prelogin(data: Json<PreloginData>, conn: DbConn) -> Json<Value> { 544 let login_data: PreloginData = data.into_inner(); 545 let (kdf_type, kdf_iter, kdf_mem, kdf_para) = 546 match User::find_by_mail(&login_data.email, &conn).await { 547 Some(user) => ( 548 user.client_kdf_type, 549 user.client_kdf_iter(), 550 user.client_kdf_memory(), 551 user.client_kdf_parallelism(), 552 ), 553 None => ( 554 User::client_kdf_type_default(), 555 User::CLIENT_KDF_ITER_DEFAULT, 556 None, 557 None, 558 ), 559 }; 560 let result = json!({ 561 "kdf": kdf_type, 562 "kdfIterations": kdf_iter, 563 "kdfMemory": kdf_mem, 564 "kdfParallelism": kdf_para, 565 }); 566 Json(result) 567 } 568 569 // https://github.com/bitwarden/server/blob/master/src/Api/Models/Request/Accounts/SecretVerificationRequestModel.cs 570 #[derive(Deserialize)] 571 #[serde(rename_all = "camelCase")] 572 struct SecretVerificationRequest { 573 master_password_hash: String, 574 } 575 576 #[post("/accounts/verify-password", data = "<data>")] 577 fn verify_password(data: Json<SecretVerificationRequest>, headers: Headers) -> EmptyResult { 578 let req: SecretVerificationRequest = data.into_inner(); 579 let user = headers.user; 580 if !user.check_valid_password(&req.master_password_hash) { 581 err!("Invalid password") 582 } 583 Ok(()) 584 } 585 586 const API_DISABLED_MSG: &str = "API access is disabled."; 587 #[allow(unused_variables, clippy::needless_pass_by_value)] 588 #[post("/accounts/api-key", data = "<data>")] 589 fn api_key(data: Json<PasswordOrOtpData>, _headers: Headers) -> Error { 590 Error::new(API_DISABLED_MSG, API_DISABLED_MSG) 591 } 592 593 #[allow(unused_variables, clippy::needless_pass_by_value)] 594 #[post("/accounts/rotate-api-key", data = "<data>")] 595 fn rotate_api_key(data: Json<PasswordOrOtpData>, _headers: Headers) -> Error { 596 Error::new(API_DISABLED_MSG, API_DISABLED_MSG) 597 } 598 599 #[get("/devices/knowndevice")] 600 async fn get_known_device(device: KnownDevice, conn: DbConn) -> JsonResult { 601 let mut result = false; 602 if let Some(user) = User::find_by_mail(&device.email, &conn).await { 603 result = Device::find_by_uuid_and_user(&device.uuid, &user.uuid, &conn) 604 .await 605 .is_some(); 606 } 607 Ok(Json(json!(result))) 608 } 609 610 struct KnownDevice { 611 email: String, 612 uuid: String, 613 } 614 615 #[rocket::async_trait] 616 impl<'r> FromRequest<'r> for KnownDevice { 617 type Error = &'static str; 618 async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> { 619 let email = if let Some(email_b64) = request.headers().get_one("X-Request-Email") { 620 let Ok(email_bytes) = data_encoding::BASE64URL_NOPAD.decode(email_b64.as_bytes()) 621 else { 622 return Outcome::Error(( 623 Status::BadRequest, 624 "X-Request-Email value failed to decode as base64url", 625 )); 626 }; 627 match String::from_utf8(email_bytes) { 628 Ok(email) => email, 629 Err(_) => { 630 return Outcome::Error(( 631 Status::BadRequest, 632 "X-Request-Email value failed to decode as UTF-8", 633 )); 634 } 635 } 636 } else { 637 return Outcome::Error((Status::BadRequest, "X-Request-Email value is required")); 638 }; 639 640 let uuid = if let Some(uuid) = request.headers().get_one("X-Device-Identifier") { 641 uuid.to_owned() 642 } else { 643 return Outcome::Error((Status::BadRequest, "X-Device-Identifier value is required")); 644 }; 645 Outcome::Success(Self { email, uuid }) 646 } 647 } 648 649 #[derive(Deserialize)] 650 #[serde(rename_all = "camelCase")] 651 struct PushToken { 652 #[allow(dead_code)] 653 push_token: String, 654 } 655 656 #[allow(unused_variables, clippy::needless_pass_by_value)] 657 #[post("/devices/identifier/<uuid>/token", data = "<data>")] 658 fn post_device_token(uuid: &str, data: Json<PushToken>, _headers: Headers) {} 659 #[allow(unused_variables, clippy::needless_pass_by_value)] 660 #[put("/devices/identifier/<uuid>/token", data = "<data>")] 661 fn put_device_token(uuid: &str, data: Json<PushToken>, _headers: Headers) {} 662 #[allow(unused_variables)] 663 #[put("/devices/identifier/<uuid>/clear-token")] 664 const fn put_clear_device_token(uuid: &str) {} 665 666 // On upstream server, both PUT and POST are declared. Implementing the POST method in case it would be useful somewhere 667 #[allow(unused_variables)] 668 #[post("/devices/identifier/<uuid>/clear-token")] 669 const fn post_clear_device_token(uuid: &str) {} 670 671 #[derive(Deserialize)] 672 #[serde(rename_all = "camelCase")] 673 struct AuthRequestRequest { 674 #[allow(dead_code)] 675 access_code: String, 676 #[allow(dead_code)] 677 device_identifier: String, 678 #[allow(dead_code)] 679 email: String, 680 #[allow(dead_code)] 681 public_key: String, 682 #[serde(alias = "type")] 683 _type: i32, 684 } 685 const AUTH_DISABLED_MSG: &str = "Auth requests and log in via device are disabled."; 686 #[allow(unused_variables, clippy::needless_pass_by_value)] 687 #[post("/auth-requests", data = "<data>")] 688 fn post_auth_request(data: Json<AuthRequestRequest>, _headers: ClientHeaders) -> Error { 689 Error::new(AUTH_DISABLED_MSG, AUTH_DISABLED_MSG) 690 } 691 692 #[allow(unused_variables)] 693 #[get("/auth-requests/<uuid>")] 694 fn get_auth_request(uuid: &str) -> Error { 695 Error::new(AUTH_DISABLED_MSG, AUTH_DISABLED_MSG) 696 } 697 698 #[derive(Deserialize)] 699 #[serde(rename_all = "camelCase")] 700 struct AuthResponseRequest { 701 #[allow(dead_code)] 702 device_identifier: String, 703 #[allow(dead_code)] 704 key: String, 705 #[allow(dead_code)] 706 master_password_hash: Option<String>, 707 #[allow(dead_code)] 708 request_approved: bool, 709 } 710 711 #[allow(unused_variables, clippy::needless_pass_by_value)] 712 #[put("/auth-requests/<uuid>", data = "<data>")] 713 fn put_auth_request(uuid: &str, data: Json<AuthResponseRequest>) -> Error { 714 Error::new(AUTH_DISABLED_MSG, AUTH_DISABLED_MSG) 715 } 716 717 #[allow(unused_variables)] 718 #[get("/auth-requests/<uuid>/response?<code>")] 719 fn get_auth_request_response(uuid: &str, code: &str) -> Error { 720 Error::new(AUTH_DISABLED_MSG, AUTH_DISABLED_MSG) 721 } 722 723 #[allow(unused_variables, clippy::needless_pass_by_value)] 724 #[get("/auth-requests")] 725 fn get_auth_requests(headers: Headers) -> Json<Value> { 726 Json(json!({ 727 "data": Vec::<Value>::new(), 728 "continuationToken": null, 729 "object": "list" 730 })) 731 }