diff --git a/native/native_rust_library/src/identity.rs b/native/native_rust_library/src/identity.rs index 0e85a6fc9..45669cb59 100644 --- a/native/native_rust_library/src/identity.rs +++ b/native/native_rust_library/src/identity.rs @@ -1,131 +1,133 @@ use grpc_clients::identity::get_unauthenticated_client; use grpc_clients::identity::protos::unauth::{AuthResponse, Empty}; use serde::Serialize; use crate::utils::jsi_callbacks::{ handle_bool_result_as_callback, handle_string_result_as_callback, }; use crate::{Error, RUNTIME}; use crate::{CODE_VERSION, DEVICE_TYPE, IDENTITY_SOCKET_ADDR}; pub mod exact_user_search; pub mod farcaster; pub mod login; pub mod registration; +pub mod x3dh; pub mod ffi { use super::*; pub use exact_user_search::ffi::*; pub use farcaster::ffi::*; pub use login::ffi::*; pub use registration::ffi::*; + pub use x3dh::ffi::*; pub fn generate_nonce(promise_id: u32) { RUNTIME.spawn(async move { let result = fetch_nonce().await; handle_string_result_as_callback(result, promise_id); }); } pub fn version_supported(promise_id: u32) { RUNTIME.spawn(async move { let result = version_supported_helper().await; handle_bool_result_as_callback(result, promise_id); }); } } // helper structs pub struct AuthInfo { pub user_id: String, pub device_id: String, pub access_token: String, } pub struct PasswordUserInfo { pub username: String, pub password: String, pub key_payload: String, pub key_payload_signature: String, pub content_prekey: String, pub content_prekey_signature: String, pub notif_prekey: String, pub notif_prekey_signature: String, pub content_one_time_keys: Vec, pub notif_one_time_keys: Vec, pub farcaster_id: Option, } pub struct WalletUserInfo { pub siwe_message: String, pub siwe_signature: String, pub key_payload: String, pub key_payload_signature: String, pub content_prekey: String, pub content_prekey_signature: String, pub notif_prekey: String, pub notif_prekey_signature: String, pub content_one_time_keys: Vec, pub notif_one_time_keys: Vec, pub farcaster_id: Option, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct UserIDAndDeviceAccessToken { #[serde(rename = "userID")] user_id: String, access_token: String, } impl From for UserIDAndDeviceAccessToken { fn from(value: AuthResponse) -> Self { let AuthResponse { user_id, access_token, } = value; Self { user_id, access_token, } } } // API implementation helpers async fn fetch_nonce() -> Result { let mut identity_client = get_unauthenticated_client( IDENTITY_SOCKET_ADDR, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let nonce = identity_client .generate_nonce(Empty {}) .await? .into_inner() .nonce; Ok(nonce) } async fn version_supported_helper() -> Result { let mut identity_client = get_unauthenticated_client( IDENTITY_SOCKET_ADDR, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let response = identity_client.ping(Empty {}).await; match response { Ok(_) => Ok(true), Err(e) => { if grpc_clients::error::is_version_unsupported(&e) { Ok(false) } else { Err(e.into()) } } } } diff --git a/native/native_rust_library/src/identity/x3dh.rs b/native/native_rust_library/src/identity/x3dh.rs new file mode 100644 index 000000000..da6dbdaaf --- /dev/null +++ b/native/native_rust_library/src/identity/x3dh.rs @@ -0,0 +1,400 @@ +use grpc_clients::identity::get_auth_client; +use grpc_clients::identity::protos::authenticated::{ + InboundKeyInfo, InboundKeysForUserRequest, KeyserverKeysResponse, + OutboundKeyInfo, OutboundKeysForUserRequest, RefreshUserPrekeysRequest, + UploadOneTimeKeysRequest, +}; +use grpc_clients::identity::protos::unauth::{IdentityKeyInfo, Prekey}; +use serde::Serialize; +use tracing::instrument; + +use crate::identity::AuthInfo; +use crate::utils::jsi_callbacks::{ + handle_string_result_as_callback, handle_void_result_as_callback, +}; +use crate::{Error, CODE_VERSION, DEVICE_TYPE, IDENTITY_SOCKET_ADDR, RUNTIME}; + +pub mod ffi { + use super::*; + + pub fn get_outbound_keys_for_user( + auth_user_id: String, + auth_device_id: String, + auth_access_token: String, + user_id: String, + promise_id: u32, + ) { + RUNTIME.spawn(async move { + let get_outbound_keys_request_info = + GetOutboundKeysRequestInfo { user_id }; + let auth_info = AuthInfo { + access_token: auth_access_token, + user_id: auth_user_id, + device_id: auth_device_id, + }; + let result = get_outbound_keys_for_user_helper( + get_outbound_keys_request_info, + auth_info, + ) + .await; + handle_string_result_as_callback(result, promise_id); + }); + } + + pub fn get_inbound_keys_for_user( + auth_user_id: String, + auth_device_id: String, + auth_access_token: String, + user_id: String, + promise_id: u32, + ) { + RUNTIME.spawn(async move { + let get_inbound_keys_request_info = GetInboundKeysRequestInfo { user_id }; + let auth_info = AuthInfo { + access_token: auth_access_token, + user_id: auth_user_id, + device_id: auth_device_id, + }; + let result = get_inbound_keys_for_user_helper( + get_inbound_keys_request_info, + auth_info, + ) + .await; + handle_string_result_as_callback(result, promise_id); + }); + } + + pub fn get_keyserver_keys( + user_id: String, + device_id: String, + access_token: String, + keyserver_id: String, + promise_id: u32, + ) { + RUNTIME.spawn(async move { + let get_keyserver_keys_request = OutboundKeysForUserRequest { + user_id: keyserver_id, + }; + let auth_info = AuthInfo { + access_token, + user_id, + device_id, + }; + let result = + get_keyserver_keys_helper(get_keyserver_keys_request, auth_info).await; + handle_string_result_as_callback(result, promise_id); + }); + } + + pub fn refresh_user_prekeys( + auth_user_id: String, + auth_device_id: String, + auth_access_token: String, + content_prekey: String, + content_prekey_signature: String, + notif_prekey: String, + notif_prekey_signature: String, + promise_id: u32, + ) { + RUNTIME.spawn(async move { + let refresh_request = RefreshUserPrekeysRequest { + new_content_prekeys: Some(Prekey { + prekey: content_prekey, + prekey_signature: content_prekey_signature, + }), + new_notif_prekeys: Some(Prekey { + prekey: notif_prekey, + prekey_signature: notif_prekey_signature, + }), + }; + + let auth_info = AuthInfo { + access_token: auth_access_token, + user_id: auth_user_id, + device_id: auth_device_id, + }; + let result = + refresh_user_prekeys_helper(refresh_request, auth_info).await; + handle_void_result_as_callback(result, promise_id); + }); + } + + #[instrument] + pub fn upload_one_time_keys( + auth_user_id: String, + auth_device_id: String, + auth_access_token: String, + content_one_time_keys: Vec, + notif_one_time_keys: Vec, + promise_id: u32, + ) { + RUNTIME.spawn(async move { + let upload_request = UploadOneTimeKeysRequest { + content_one_time_prekeys: content_one_time_keys, + notif_one_time_prekeys: notif_one_time_keys, + }; + let auth_info = AuthInfo { + access_token: auth_access_token, + user_id: auth_user_id, + device_id: auth_device_id, + }; + let result = upload_one_time_keys_helper(auth_info, upload_request).await; + handle_void_result_as_callback(result, promise_id); + }); + } +} + +// helper structs + +struct GetOutboundKeysRequestInfo { + user_id: String, +} + +struct GetInboundKeysRequestInfo { + user_id: String, +} + +// This struct should not be altered without also updating +// OutboundKeyInfoResponse in lib/types/identity-service-types.js +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct OutboundKeyInfoResponse { + pub payload: String, + pub payload_signature: String, + pub social_proof: Option, + pub content_prekey: String, + pub content_prekey_signature: String, + pub notif_prekey: String, + pub notif_prekey_signature: String, + pub one_time_content_prekey: Option, + pub one_time_notif_prekey: Option, +} + +// This struct should not be altered without also updating +// InboundKeyInfoResponse in lib/types/identity-service-types.js +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct InboundKeyInfoResponse { + pub payload: String, + pub payload_signature: String, + pub social_proof: Option, + pub content_prekey: String, + pub content_prekey_signature: String, + pub notif_prekey: String, + pub notif_prekey_signature: String, +} + +impl TryFrom for OutboundKeyInfoResponse { + type Error = Error; + + fn try_from(key_info: OutboundKeyInfo) -> Result { + let identity_info = + key_info.identity_info.ok_or(Error::MissingResponseData)?; + + let IdentityKeyInfo { + payload, + payload_signature, + social_proof, + } = identity_info; + + let content_prekey = + key_info.content_prekey.ok_or(Error::MissingResponseData)?; + + let Prekey { + prekey: content_prekey_value, + prekey_signature: content_prekey_signature, + } = content_prekey; + + let notif_prekey = + key_info.notif_prekey.ok_or(Error::MissingResponseData)?; + + let Prekey { + prekey: notif_prekey_value, + prekey_signature: notif_prekey_signature, + } = notif_prekey; + + let one_time_content_prekey = key_info.one_time_content_prekey; + let one_time_notif_prekey = key_info.one_time_notif_prekey; + + Ok(Self { + payload, + payload_signature, + social_proof, + content_prekey: content_prekey_value, + content_prekey_signature, + notif_prekey: notif_prekey_value, + notif_prekey_signature, + one_time_content_prekey, + one_time_notif_prekey, + }) + } +} + +impl TryFrom for OutboundKeyInfoResponse { + type Error = Error; + + fn try_from(response: KeyserverKeysResponse) -> Result { + let key_info = response.keyserver_info.ok_or(Error::MissingResponseData)?; + Self::try_from(key_info) + } +} + +impl TryFrom for InboundKeyInfoResponse { + type Error = Error; + + fn try_from(key_info: InboundKeyInfo) -> Result { + let identity_info = + key_info.identity_info.ok_or(Error::MissingResponseData)?; + + let IdentityKeyInfo { + payload, + payload_signature, + social_proof, + } = identity_info; + + let content_prekey = + key_info.content_prekey.ok_or(Error::MissingResponseData)?; + + let Prekey { + prekey: content_prekey_value, + prekey_signature: content_prekey_signature, + } = content_prekey; + + let notif_prekey = + key_info.notif_prekey.ok_or(Error::MissingResponseData)?; + + let Prekey { + prekey: notif_prekey_value, + prekey_signature: notif_prekey_signature, + } = notif_prekey; + + Ok(Self { + payload, + payload_signature, + social_proof, + content_prekey: content_prekey_value, + content_prekey_signature, + notif_prekey: notif_prekey_value, + notif_prekey_signature, + }) + } +} + +async fn get_outbound_keys_for_user_helper( + get_outbound_keys_request_info: GetOutboundKeysRequestInfo, + auth_info: AuthInfo, +) -> Result { + let mut identity_client = get_auth_client( + IDENTITY_SOCKET_ADDR, + auth_info.user_id, + auth_info.device_id, + auth_info.access_token, + CODE_VERSION, + DEVICE_TYPE.as_str_name().to_lowercase(), + ) + .await?; + let response = identity_client + .get_outbound_keys_for_user(OutboundKeysForUserRequest { + user_id: get_outbound_keys_request_info.user_id, + }) + .await? + .into_inner(); + + let outbound_key_info: Vec = response + .devices + .into_values() + .map(OutboundKeyInfoResponse::try_from) + .collect::, _>>()?; + + Ok(serde_json::to_string(&outbound_key_info)?) +} + +async fn get_inbound_keys_for_user_helper( + get_inbound_keys_request_info: GetInboundKeysRequestInfo, + auth_info: AuthInfo, +) -> Result { + let mut identity_client = get_auth_client( + IDENTITY_SOCKET_ADDR, + auth_info.user_id, + auth_info.device_id, + auth_info.access_token, + CODE_VERSION, + DEVICE_TYPE.as_str_name().to_lowercase(), + ) + .await?; + let response = identity_client + .get_inbound_keys_for_user(InboundKeysForUserRequest { + user_id: get_inbound_keys_request_info.user_id, + }) + .await? + .into_inner(); + + let inbound_key_info: Vec = response + .devices + .into_values() + .map(InboundKeyInfoResponse::try_from) + .collect::, _>>()?; + + Ok(serde_json::to_string(&inbound_key_info)?) +} + +async fn get_keyserver_keys_helper( + get_keyserver_keys_request: OutboundKeysForUserRequest, + auth_info: AuthInfo, +) -> Result { + let mut identity_client = get_auth_client( + IDENTITY_SOCKET_ADDR, + auth_info.user_id, + auth_info.device_id, + auth_info.access_token, + CODE_VERSION, + DEVICE_TYPE.as_str_name().to_lowercase(), + ) + .await?; + let response = identity_client + .get_keyserver_keys(get_keyserver_keys_request) + .await? + .into_inner(); + + let keyserver_keys = OutboundKeyInfoResponse::try_from(response)?; + + Ok(serde_json::to_string(&keyserver_keys)?) +} + +async fn refresh_user_prekeys_helper( + refresh_request: RefreshUserPrekeysRequest, + auth_info: AuthInfo, +) -> Result<(), Error> { + get_auth_client( + IDENTITY_SOCKET_ADDR, + auth_info.user_id, + auth_info.device_id, + auth_info.access_token, + CODE_VERSION, + DEVICE_TYPE.as_str_name().to_lowercase(), + ) + .await? + .refresh_user_prekeys(refresh_request) + .await?; + + Ok(()) +} + +async fn upload_one_time_keys_helper( + auth_info: AuthInfo, + upload_request: UploadOneTimeKeysRequest, +) -> Result<(), Error> { + let mut identity_client = get_auth_client( + IDENTITY_SOCKET_ADDR, + auth_info.user_id, + auth_info.device_id, + auth_info.access_token, + CODE_VERSION, + DEVICE_TYPE.as_str_name().to_lowercase(), + ) + .await?; + + identity_client.upload_one_time_keys(upload_request).await?; + + Ok(()) +} diff --git a/native/native_rust_library/src/lib.rs b/native/native_rust_library/src/lib.rs index 6762fc162..b03fad540 100644 --- a/native/native_rust_library/src/lib.rs +++ b/native/native_rust_library/src/lib.rs @@ -1,1174 +1,791 @@ use comm_opaque2::client::Registration; use comm_opaque2::grpc::opaque_error_to_grpc_status as handle_error; use grpc_clients::identity::protos::auth::{ GetDeviceListRequest, UpdateDeviceListRequest, }; use grpc_clients::identity::protos::authenticated::{ - InboundKeyInfo, InboundKeysForUserRequest, KeyserverKeysResponse, - OutboundKeyInfo, OutboundKeysForUserRequest, RefreshUserPrekeysRequest, UpdateUserPasswordFinishRequest, UpdateUserPasswordStartRequest, - UploadOneTimeKeysRequest, }; use grpc_clients::identity::protos::unauth::{ DeviceKeyUpload, DeviceType, Empty, ExistingDeviceLoginRequest, IdentityKeyInfo, Prekey, SecondaryDeviceKeysUploadRequest, }; use grpc_clients::identity::{get_auth_client, get_unauthenticated_client}; use lazy_static::lazy_static; -use serde::Serialize; use std::sync::Arc; use tokio::runtime::{Builder, Runtime}; use tonic::Status; -use tracing::instrument; mod argon2_tools; mod backup; mod constants; mod identity; mod utils; use crate::argon2_tools::compute_backup_key_str; use crate::identity::{AuthInfo, UserIDAndDeviceAccessToken}; use crate::utils::jsi_callbacks::{ handle_string_result_as_callback, handle_void_result_as_callback, }; mod generated { // We get the CODE_VERSION from this generated file include!(concat!(env!("OUT_DIR"), "/version.rs")); // We get the IDENTITY_SOCKET_ADDR from this generated file include!(concat!(env!("OUT_DIR"), "/socket_config.rs")); } pub use generated::CODE_VERSION; pub use generated::{BACKUP_SOCKET_ADDR, IDENTITY_SOCKET_ADDR}; #[cfg(not(target_os = "android"))] pub const DEVICE_TYPE: DeviceType = DeviceType::Ios; #[cfg(target_os = "android")] pub const DEVICE_TYPE: DeviceType = DeviceType::Android; lazy_static! { static ref RUNTIME: Arc = Arc::new(Builder::new_multi_thread().enable_all().build().unwrap()); } // ffi uses use backup::ffi::*; use identity::ffi::*; use utils::future_manager::ffi::*; #[cxx::bridge] mod ffi { extern "Rust" { #[cxx_name = "identityRegisterPasswordUser"] fn register_password_user( username: String, password: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, farcaster_id: String, promise_id: u32, ); #[cxx_name = "identityLogInPasswordUser"] fn log_in_password_user( username: String, password: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, promise_id: u32, ); #[cxx_name = "identityRegisterWalletUser"] fn register_wallet_user( siwe_message: String, siwe_signature: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, farcaster_id: String, promise_id: u32, ); #[cxx_name = "identityLogInWalletUser"] fn log_in_wallet_user( siwe_message: String, siwe_signature: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, promise_id: u32, ); #[cxx_name = "identityUpdateUserPassword"] fn update_user_password( user_id: String, device_id: String, access_token: String, password: String, promise_id: u32, ); #[cxx_name = "identityDeleteUser"] fn delete_user( user_id: String, device_id: String, access_token: String, promise_id: u32, ); #[cxx_name = "identityLogOut"] fn log_out( user_id: String, device_id: String, access_token: String, promise_id: u32, ); #[cxx_name = "identityGetOutboundKeysForUser"] fn get_outbound_keys_for_user( auth_user_id: String, auth_device_id: String, auth_access_token: String, user_id: String, promise_id: u32, ); #[cxx_name = "identityGetInboundKeysForUser"] fn get_inbound_keys_for_user( auth_user_id: String, auth_device_id: String, auth_access_token: String, user_id: String, promise_id: u32, ); #[cxx_name = "identityRefreshUserPrekeys"] fn refresh_user_prekeys( auth_user_id: String, auth_device_id: String, auth_access_token: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, promise_id: u32, ); #[cxx_name = "identityGenerateNonce"] fn generate_nonce(promise_id: u32); #[cxx_name = "identityVersionSupported"] fn version_supported(promise_id: u32); #[cxx_name = "identityUploadOneTimeKeys"] fn upload_one_time_keys( auth_user_id: String, auth_device_id: String, auth_access_token: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, promise_id: u32, ); #[cxx_name = "identityGetKeyserverKeys"] fn get_keyserver_keys( user_id: String, device_id: String, access_token: String, keyserver_id: String, promise_id: u32, ); #[cxx_name = "identityGetDeviceListForUser"] fn get_device_list_for_user( auth_user_id: String, auth_device_id: String, auth_access_token: String, user_id: String, since_timestamp: i64, promise_id: u32, ); #[cxx_name = "identityUpdateDeviceList"] fn update_device_list( auth_user_id: String, auth_device_id: String, auth_access_token: String, update_payload: String, promise_id: u32, ); #[cxx_name = "identityUploadSecondaryDeviceKeysAndLogIn"] fn upload_secondary_device_keys_and_log_in( user_id: String, challenge_response: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, promise_id: u32, ); #[cxx_name = "identityLogInExistingDevice"] fn log_in_existing_device( user_id: String, device_id: String, challenge_response: String, promise_id: u32, ); #[cxx_name = "identityFindUserIDForWalletAddress"] fn find_user_id_for_wallet_address(wallet_address: String, promise_id: u32); #[cxx_name = "identityFindUserIDForUsername"] fn find_user_id_for_username(username: String, promise_id: u32); // Farcaster #[cxx_name = "identityGetFarcasterUsers"] fn get_farcaster_users(farcaster_ids: Vec, promise_id: u32); #[cxx_name = "identityLinkFarcasterAccount"] fn link_farcaster_account( user_id: String, device_id: String, access_token: String, farcaster_id: String, promise_id: u32, ); #[cxx_name = "identityUnlinkFarcasterAccount"] fn unlink_farcaster_account( user_id: String, device_id: String, access_token: String, promise_id: u32, ); // Argon2 #[cxx_name = "compute_backup_key"] fn compute_backup_key_str( password: &str, backup_id: &str, ) -> Result<[u8; 32]>; } unsafe extern "C++" { include!("RustCallback.h"); #[namespace = "comm"] #[cxx_name = "stringCallback"] fn string_callback(error: String, promise_id: u32, ret: String); #[namespace = "comm"] #[cxx_name = "voidCallback"] fn void_callback(error: String, promise_id: u32); #[namespace = "comm"] #[cxx_name = "boolCallback"] fn bool_callback(error: String, promise_id: u32, ret: bool); } // AES cryptography #[namespace = "comm"] unsafe extern "C++" { include!("RustAESCrypto.h"); #[allow(unused)] #[cxx_name = "aesGenerateKey"] fn generate_key(buffer: &mut [u8]) -> Result<()>; /// The first two argument aren't mutated but creation of Java ByteBuffer /// requires the underlying bytes to be mutable. #[allow(unused)] #[cxx_name = "aesEncrypt"] fn encrypt( key: &mut [u8], plaintext: &mut [u8], sealed_data: &mut [u8], ) -> Result<()>; /// The first two argument aren't mutated but creation of Java ByteBuffer /// requires the underlying bytes to be mutable. #[allow(unused)] #[cxx_name = "aesDecrypt"] fn decrypt( key: &mut [u8], sealed_data: &mut [u8], plaintext: &mut [u8], ) -> Result<()>; } // Comm Services Auth Metadata Emission #[namespace = "comm"] unsafe extern "C++" { include!("RustCSAMetadataEmitter.h"); #[allow(unused)] #[cxx_name = "sendAuthMetadataToJS"] fn send_auth_metadata_to_js( access_token: String, user_id: String, ) -> Result<()>; } // Backup extern "Rust" { #[cxx_name = "startBackupHandler"] fn start_backup_handler() -> Result<()>; #[cxx_name = "stopBackupHandler"] fn stop_backup_handler() -> Result<()>; #[cxx_name = "triggerBackupFileUpload"] fn trigger_backup_file_upload(); #[cxx_name = "createBackup"] fn create_backup( backup_id: String, backup_secret: String, pickle_key: String, pickled_account: String, promise_id: u32, ); #[cxx_name = "restoreBackup"] fn restore_backup(backup_secret: String, promise_id: u32); #[cxx_name = "restoreBackupData"] fn restore_backup_data( backup_id: String, backup_data_key: String, backup_log_data_key: String, promise_id: u32, ); #[cxx_name = "retrieveBackupKeys"] fn retrieve_backup_keys(backup_secret: String, promise_id: u32); } // Secure store #[namespace = "comm"] unsafe extern "C++" { include!("RustSecureStore.h"); #[allow(unused)] #[cxx_name = "secureStoreSet"] fn secure_store_set(key: &str, value: String) -> Result<()>; #[cxx_name = "secureStoreGet"] fn secure_store_get(key: &str) -> Result; } // C++ Backup creation #[namespace = "comm"] unsafe extern "C++" { include!("RustBackupExecutor.h"); #[cxx_name = "getBackupDirectoryPath"] fn get_backup_directory_path() -> Result; #[cxx_name = "getBackupFilePath"] fn get_backup_file_path( backup_id: &str, is_attachments: bool, ) -> Result; #[cxx_name = "getBackupLogFilePath"] fn get_backup_log_file_path( backup_id: &str, log_id: &str, is_attachments: bool, ) -> Result; #[cxx_name = "getBackupUserKeysFilePath"] fn get_backup_user_keys_file_path(backup_id: &str) -> Result; #[cxx_name = "createMainCompaction"] fn create_main_compaction(backup_id: &str, future_id: usize); #[cxx_name = "restoreFromMainCompaction"] fn restore_from_main_compaction( main_compaction_path: &str, main_compaction_encryption_key: &str, future_id: usize, ); #[cxx_name = "restoreFromBackupLog"] fn restore_from_backup_log(backup_log: Vec, future_id: usize); } // Future handling from C++ extern "Rust" { #[cxx_name = "resolveUnitFuture"] fn resolve_unit_future(future_id: usize); #[cxx_name = "rejectFuture"] fn reject_future(future_id: usize, error: String); } } -fn get_keyserver_keys( - user_id: String, - device_id: String, - access_token: String, - keyserver_id: String, - promise_id: u32, -) { - RUNTIME.spawn(async move { - let get_keyserver_keys_request = OutboundKeysForUserRequest { - user_id: keyserver_id, - }; - let auth_info = AuthInfo { - access_token, - user_id, - device_id, - }; - let result = - get_keyserver_keys_helper(get_keyserver_keys_request, auth_info).await; - handle_string_result_as_callback(result, promise_id); - }); -} - -async fn get_keyserver_keys_helper( - get_keyserver_keys_request: OutboundKeysForUserRequest, - auth_info: AuthInfo, -) -> Result { - let mut identity_client = get_auth_client( - IDENTITY_SOCKET_ADDR, - auth_info.user_id, - auth_info.device_id, - auth_info.access_token, - CODE_VERSION, - DEVICE_TYPE.as_str_name().to_lowercase(), - ) - .await?; - let response = identity_client - .get_keyserver_keys(get_keyserver_keys_request) - .await? - .into_inner(); - - let keyserver_keys = OutboundKeyInfoResponse::try_from(response)?; - - Ok(serde_json::to_string(&keyserver_keys)?) -} - struct UpdatePasswordInfo { user_id: String, device_id: String, access_token: String, password: String, } fn update_user_password( user_id: String, device_id: String, access_token: String, password: String, promise_id: u32, ) { RUNTIME.spawn(async move { let update_password_info = UpdatePasswordInfo { access_token, user_id, device_id, password, }; let result = update_user_password_helper(update_password_info).await; handle_void_result_as_callback(result, promise_id); }); } async fn update_user_password_helper( update_password_info: UpdatePasswordInfo, ) -> Result<(), Error> { let mut client_registration = Registration::new(); let opaque_registration_request = client_registration .start(&update_password_info.password) .map_err(handle_error)?; let update_password_start_request = UpdateUserPasswordStartRequest { opaque_registration_request, }; let mut identity_client = get_auth_client( IDENTITY_SOCKET_ADDR, update_password_info.user_id, update_password_info.device_id, update_password_info.access_token, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let response = identity_client .update_user_password_start(update_password_start_request) .await?; let update_password_start_response = response.into_inner(); let opaque_registration_upload = client_registration .finish( &update_password_info.password, &update_password_start_response.opaque_registration_response, ) .map_err(handle_error)?; let update_password_finish_request = UpdateUserPasswordFinishRequest { session_id: update_password_start_response.session_id, opaque_registration_upload, }; identity_client .update_user_password_finish(update_password_finish_request) .await?; Ok(()) } fn delete_user( user_id: String, device_id: String, access_token: String, promise_id: u32, ) { RUNTIME.spawn(async move { let auth_info = AuthInfo { access_token, user_id, device_id, }; let result = delete_user_helper(auth_info).await; handle_void_result_as_callback(result, promise_id); }); } async fn delete_user_helper(auth_info: AuthInfo) -> Result<(), Error> { let mut identity_client = get_auth_client( IDENTITY_SOCKET_ADDR, auth_info.user_id, auth_info.device_id, auth_info.access_token, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; identity_client.delete_user(Empty {}).await?; Ok(()) } fn log_out( user_id: String, device_id: String, access_token: String, promise_id: u32, ) { RUNTIME.spawn(async move { let auth_info = AuthInfo { access_token, user_id, device_id, }; let result = log_out_helper(auth_info).await; handle_void_result_as_callback(result, promise_id); }); } async fn log_out_helper(auth_info: AuthInfo) -> Result<(), Error> { let mut identity_client = get_auth_client( IDENTITY_SOCKET_ADDR, auth_info.user_id, auth_info.device_id, auth_info.access_token, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; identity_client.log_out_user(Empty {}).await?; Ok(()) } - -struct GetOutboundKeysRequestInfo { - user_id: String, -} - -struct GetInboundKeysRequestInfo { - user_id: String, -} - -// This struct should not be altered without also updating -// OutboundKeyInfoResponse in lib/types/identity-service-types.js -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -struct OutboundKeyInfoResponse { - pub payload: String, - pub payload_signature: String, - pub social_proof: Option, - pub content_prekey: String, - pub content_prekey_signature: String, - pub notif_prekey: String, - pub notif_prekey_signature: String, - pub one_time_content_prekey: Option, - pub one_time_notif_prekey: Option, -} - -// This struct should not be altered without also updating -// InboundKeyInfoResponse in lib/types/identity-service-types.js -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -struct InboundKeyInfoResponse { - pub payload: String, - pub payload_signature: String, - pub social_proof: Option, - pub content_prekey: String, - pub content_prekey_signature: String, - pub notif_prekey: String, - pub notif_prekey_signature: String, -} - -impl TryFrom for OutboundKeyInfoResponse { - type Error = Error; - - fn try_from(key_info: OutboundKeyInfo) -> Result { - let identity_info = - key_info.identity_info.ok_or(Error::MissingResponseData)?; - - let IdentityKeyInfo { - payload, - payload_signature, - social_proof, - } = identity_info; - - let content_prekey = - key_info.content_prekey.ok_or(Error::MissingResponseData)?; - - let Prekey { - prekey: content_prekey_value, - prekey_signature: content_prekey_signature, - } = content_prekey; - - let notif_prekey = - key_info.notif_prekey.ok_or(Error::MissingResponseData)?; - - let Prekey { - prekey: notif_prekey_value, - prekey_signature: notif_prekey_signature, - } = notif_prekey; - - let one_time_content_prekey = key_info.one_time_content_prekey; - let one_time_notif_prekey = key_info.one_time_notif_prekey; - - Ok(Self { - payload, - payload_signature, - social_proof, - content_prekey: content_prekey_value, - content_prekey_signature, - notif_prekey: notif_prekey_value, - notif_prekey_signature, - one_time_content_prekey, - one_time_notif_prekey, - }) - } -} - -impl TryFrom for OutboundKeyInfoResponse { - type Error = Error; - - fn try_from(response: KeyserverKeysResponse) -> Result { - let key_info = response.keyserver_info.ok_or(Error::MissingResponseData)?; - Self::try_from(key_info) - } -} - -fn get_outbound_keys_for_user( - auth_user_id: String, - auth_device_id: String, - auth_access_token: String, - user_id: String, - promise_id: u32, -) { - RUNTIME.spawn(async move { - let get_outbound_keys_request_info = GetOutboundKeysRequestInfo { user_id }; - let auth_info = AuthInfo { - access_token: auth_access_token, - user_id: auth_user_id, - device_id: auth_device_id, - }; - let result = get_outbound_keys_for_user_helper( - get_outbound_keys_request_info, - auth_info, - ) - .await; - handle_string_result_as_callback(result, promise_id); - }); -} - -async fn get_outbound_keys_for_user_helper( - get_outbound_keys_request_info: GetOutboundKeysRequestInfo, - auth_info: AuthInfo, -) -> Result { - let mut identity_client = get_auth_client( - IDENTITY_SOCKET_ADDR, - auth_info.user_id, - auth_info.device_id, - auth_info.access_token, - CODE_VERSION, - DEVICE_TYPE.as_str_name().to_lowercase(), - ) - .await?; - let response = identity_client - .get_outbound_keys_for_user(OutboundKeysForUserRequest { - user_id: get_outbound_keys_request_info.user_id, - }) - .await? - .into_inner(); - - let outbound_key_info: Vec = response - .devices - .into_values() - .map(OutboundKeyInfoResponse::try_from) - .collect::, _>>()?; - - Ok(serde_json::to_string(&outbound_key_info)?) -} - -impl TryFrom for InboundKeyInfoResponse { - type Error = Error; - - fn try_from(key_info: InboundKeyInfo) -> Result { - let identity_info = - key_info.identity_info.ok_or(Error::MissingResponseData)?; - - let IdentityKeyInfo { - payload, - payload_signature, - social_proof, - } = identity_info; - - let content_prekey = - key_info.content_prekey.ok_or(Error::MissingResponseData)?; - - let Prekey { - prekey: content_prekey_value, - prekey_signature: content_prekey_signature, - } = content_prekey; - - let notif_prekey = - key_info.notif_prekey.ok_or(Error::MissingResponseData)?; - - let Prekey { - prekey: notif_prekey_value, - prekey_signature: notif_prekey_signature, - } = notif_prekey; - - Ok(Self { - payload, - payload_signature, - social_proof, - content_prekey: content_prekey_value, - content_prekey_signature, - notif_prekey: notif_prekey_value, - notif_prekey_signature, - }) - } -} - -fn get_inbound_keys_for_user( - auth_user_id: String, - auth_device_id: String, - auth_access_token: String, - user_id: String, - promise_id: u32, -) { - RUNTIME.spawn(async move { - let get_inbound_keys_request_info = GetInboundKeysRequestInfo { user_id }; - let auth_info = AuthInfo { - access_token: auth_access_token, - user_id: auth_user_id, - device_id: auth_device_id, - }; - let result = get_inbound_keys_for_user_helper( - get_inbound_keys_request_info, - auth_info, - ) - .await; - handle_string_result_as_callback(result, promise_id); - }); -} - -async fn get_inbound_keys_for_user_helper( - get_inbound_keys_request_info: GetInboundKeysRequestInfo, - auth_info: AuthInfo, -) -> Result { - let mut identity_client = get_auth_client( - IDENTITY_SOCKET_ADDR, - auth_info.user_id, - auth_info.device_id, - auth_info.access_token, - CODE_VERSION, - DEVICE_TYPE.as_str_name().to_lowercase(), - ) - .await?; - let response = identity_client - .get_inbound_keys_for_user(InboundKeysForUserRequest { - user_id: get_inbound_keys_request_info.user_id, - }) - .await? - .into_inner(); - - let inbound_key_info: Vec = response - .devices - .into_values() - .map(InboundKeyInfoResponse::try_from) - .collect::, _>>()?; - - Ok(serde_json::to_string(&inbound_key_info)?) -} - -fn refresh_user_prekeys( - auth_user_id: String, - auth_device_id: String, - auth_access_token: String, - content_prekey: String, - content_prekey_signature: String, - notif_prekey: String, - notif_prekey_signature: String, - promise_id: u32, -) { - RUNTIME.spawn(async move { - let refresh_request = RefreshUserPrekeysRequest { - new_content_prekeys: Some(Prekey { - prekey: content_prekey, - prekey_signature: content_prekey_signature, - }), - new_notif_prekeys: Some(Prekey { - prekey: notif_prekey, - prekey_signature: notif_prekey_signature, - }), - }; - - let auth_info = AuthInfo { - access_token: auth_access_token, - user_id: auth_user_id, - device_id: auth_device_id, - }; - let result = refresh_user_prekeys_helper(refresh_request, auth_info).await; - handle_void_result_as_callback(result, promise_id); - }); -} - -async fn refresh_user_prekeys_helper( - refresh_request: RefreshUserPrekeysRequest, - auth_info: AuthInfo, -) -> Result<(), Error> { - get_auth_client( - IDENTITY_SOCKET_ADDR, - auth_info.user_id, - auth_info.device_id, - auth_info.access_token, - CODE_VERSION, - DEVICE_TYPE.as_str_name().to_lowercase(), - ) - .await? - .refresh_user_prekeys(refresh_request) - .await?; - - Ok(()) -} - -#[instrument] -fn upload_one_time_keys( - auth_user_id: String, - auth_device_id: String, - auth_access_token: String, - content_one_time_keys: Vec, - notif_one_time_keys: Vec, - promise_id: u32, -) { - RUNTIME.spawn(async move { - let upload_request = UploadOneTimeKeysRequest { - content_one_time_prekeys: content_one_time_keys, - notif_one_time_prekeys: notif_one_time_keys, - }; - let auth_info = AuthInfo { - access_token: auth_access_token, - user_id: auth_user_id, - device_id: auth_device_id, - }; - let result = upload_one_time_keys_helper(auth_info, upload_request).await; - handle_void_result_as_callback(result, promise_id); - }); -} - -async fn upload_one_time_keys_helper( - auth_info: AuthInfo, - upload_request: UploadOneTimeKeysRequest, -) -> Result<(), Error> { - let mut identity_client = get_auth_client( - IDENTITY_SOCKET_ADDR, - auth_info.user_id, - auth_info.device_id, - auth_info.access_token, - CODE_VERSION, - DEVICE_TYPE.as_str_name().to_lowercase(), - ) - .await?; - - identity_client.upload_one_time_keys(upload_request).await?; - - Ok(()) -} - fn get_device_list_for_user( auth_user_id: String, auth_device_id: String, auth_access_token: String, user_id: String, since_timestamp: i64, promise_id: u32, ) { RUNTIME.spawn(async move { let auth_info = AuthInfo { access_token: auth_access_token, user_id: auth_user_id, device_id: auth_device_id, }; let since_timestamp = Option::from(since_timestamp).filter(|&t| t > 0); let result = get_device_list_for_user_helper(auth_info, user_id, since_timestamp) .await; handle_string_result_as_callback(result, promise_id); }); } async fn get_device_list_for_user_helper( auth_info: AuthInfo, user_id: String, since_timestamp: Option, ) -> Result { let mut identity_client = get_auth_client( IDENTITY_SOCKET_ADDR, auth_info.user_id, auth_info.device_id, auth_info.access_token, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let response = identity_client .get_device_list_for_user(GetDeviceListRequest { user_id, since_timestamp, }) .await? .into_inner(); let payload = serde_json::to_string(&response.device_list_updates)?; Ok(payload) } fn update_device_list( auth_user_id: String, auth_device_id: String, auth_access_token: String, update_payload: String, promise_id: u32, ) { RUNTIME.spawn(async move { let auth_info = AuthInfo { access_token: auth_access_token, user_id: auth_user_id, device_id: auth_device_id, }; let result = update_device_list_helper(auth_info, update_payload).await; handle_void_result_as_callback(result, promise_id); }); } async fn update_device_list_helper( auth_info: AuthInfo, update_payload: String, ) -> Result<(), Error> { let mut identity_client = get_auth_client( IDENTITY_SOCKET_ADDR, auth_info.user_id, auth_info.device_id, auth_info.access_token, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let update_request = UpdateDeviceListRequest { new_device_list: update_payload, }; identity_client.update_device_list(update_request).await?; Ok(()) } fn upload_secondary_device_keys_and_log_in( user_id: String, challenge_response: String, key_payload: String, key_payload_signature: String, content_prekey: String, content_prekey_signature: String, notif_prekey: String, notif_prekey_signature: String, content_one_time_keys: Vec, notif_one_time_keys: Vec, promise_id: u32, ) { RUNTIME.spawn(async move { let device_key_upload = DeviceKeyUpload { device_key_info: Some(IdentityKeyInfo { payload: key_payload, payload_signature: key_payload_signature, social_proof: None, }), content_upload: Some(Prekey { prekey: content_prekey, prekey_signature: content_prekey_signature, }), notif_upload: Some(Prekey { prekey: notif_prekey, prekey_signature: notif_prekey_signature, }), one_time_content_prekeys: content_one_time_keys, one_time_notif_prekeys: notif_one_time_keys, device_type: DEVICE_TYPE.into(), }; let result = upload_secondary_device_keys_and_log_in_helper( user_id, challenge_response, device_key_upload, ) .await; handle_string_result_as_callback(result, promise_id); }); } async fn upload_secondary_device_keys_and_log_in_helper( user_id: String, challenge_response: String, device_key_upload: DeviceKeyUpload, ) -> Result { let mut identity_client = get_unauthenticated_client( IDENTITY_SOCKET_ADDR, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let request = SecondaryDeviceKeysUploadRequest { user_id, challenge_response, device_key_upload: Some(device_key_upload), }; let response = identity_client .upload_keys_for_registered_device_and_log_in(request) .await? .into_inner(); let user_id_and_access_token = UserIDAndDeviceAccessToken::from(response); Ok(serde_json::to_string(&user_id_and_access_token)?) } fn log_in_existing_device( user_id: String, device_id: String, challenge_response: String, promise_id: u32, ) { RUNTIME.spawn(async move { let result = log_in_existing_device_helper(user_id, device_id, challenge_response) .await; handle_string_result_as_callback(result, promise_id); }); } async fn log_in_existing_device_helper( user_id: String, device_id: String, challenge_response: String, ) -> Result { let mut identity_client = get_unauthenticated_client( IDENTITY_SOCKET_ADDR, CODE_VERSION, DEVICE_TYPE.as_str_name().to_lowercase(), ) .await?; let request = ExistingDeviceLoginRequest { user_id, device_id, challenge_response, }; let response = identity_client .log_in_existing_device(request) .await? .into_inner(); let user_id_and_access_token = UserIDAndDeviceAccessToken::from(response); Ok(serde_json::to_string(&user_id_and_access_token)?) } #[derive( Debug, derive_more::Display, derive_more::From, derive_more::Error, )] pub enum Error { #[display(fmt = "{}", "_0.message()")] TonicGRPC(Status), #[display(fmt = "{}", "_0")] SerdeJson(serde_json::Error), #[display(fmt = "Missing response data")] MissingResponseData, #[display(fmt = "{}", "_0")] GRPClient(grpc_clients::error::Error), } #[cfg(test)] mod tests { use super::{BACKUP_SOCKET_ADDR, CODE_VERSION, IDENTITY_SOCKET_ADDR}; #[test] fn test_code_version_exists() { assert!(CODE_VERSION > 0); } #[test] fn test_identity_socket_addr_exists() { assert!(IDENTITY_SOCKET_ADDR.len() > 0); assert!(BACKUP_SOCKET_ADDR.len() > 0); } }