diff --git a/keyserver/addons/rust-node-addon/src/identity_client/mod.rs b/keyserver/addons/rust-node-addon/src/identity_client/mod.rs index 593b6ea31..47752388f 100644 --- a/keyserver/addons/rust-node-addon/src/identity_client/mod.rs +++ b/keyserver/addons/rust-node-addon/src/identity_client/mod.rs @@ -1,219 +1,218 @@ pub mod add_reserved_usernames; pub mod config; pub mod get_farcaster_users; pub mod get_inbound_keys_for_user; pub mod login; pub mod nonce; pub mod prekey; pub mod register_user; pub mod remove_reserved_usernames; pub mod upload_one_time_keys; use client_proto::identity_client_service_client::IdentityClientServiceClient; use client_proto::{ AddReservedUsernamesRequest, DeviceKeyUpload, DeviceType, IdentityKeyInfo, Prekey, RegistrationFinishRequest, RegistrationStartRequest, RemoveReservedUsernameRequest, }; use config::get_identity_service_config; use generated::CODE_VERSION; use grpc_clients::identity::authenticated::ChainedInterceptedAuthClient; use grpc_clients::identity::protos::authenticated::{ InboundKeyInfo, UploadOneTimeKeysRequest, }; use grpc_clients::identity::protos::unauthenticated as client_proto; use grpc_clients::identity::shared::CodeVersionLayer; +use grpc_clients::identity::PlatformMetadata; use lazy_static::lazy_static; use napi::bindgen_prelude::*; use serde::{Deserialize, Serialize}; use std::env::var; use tonic::codegen::InterceptedService; use tonic::transport::Channel; use tracing::{self, info, instrument, warn, Level}; use tracing_subscriber::EnvFilter; mod generated { // We get the CODE_VERSION from this generated file include!(concat!(env!("OUT_DIR"), "/version.rs")); } const DEVICE_TYPE: &str = "keyserver"; const ENV_NODE_ENV: &str = "NODE_ENV"; const ENV_DEVELOPMENT: &str = "development"; lazy_static! { static ref IDENTITY_SERVICE_CONFIG: IdentityServiceConfig = { let filter = EnvFilter::builder() .with_default_directive(Level::INFO.into()) .with_env_var(EnvFilter::DEFAULT_ENV) .from_env_lossy(); let subscriber = tracing_subscriber::fmt().with_env_filter(filter).finish(); tracing::subscriber::set_global_default(subscriber) .expect("Unable to configure tracing"); get_identity_service_config() .unwrap_or_else(|_| IdentityServiceConfig::default()) }; } #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct IdentityServiceConfig { identity_socket_addr: String, } impl Default for IdentityServiceConfig { fn default() -> Self { info!("Using default identity configuration based on NODE_ENV"); const DEV_SOCKET_ADDR: &str = "https://identity.staging.commtechnologies.org:50054"; const PROD_SOCKET_ADDR: &str = "https://identity.commtechnologies.org:50054"; let default_socket_addr = match var(ENV_NODE_ENV) { Ok(val) if val == ENV_DEVELOPMENT => DEV_SOCKET_ADDR, _ => PROD_SOCKET_ADDR, } .to_string(); Self { identity_socket_addr: default_socket_addr, } } } async fn get_identity_client() -> Result< IdentityClientServiceClient>, > { info!("Connecting to identity service"); grpc_clients::identity::get_unauthenticated_client( &IDENTITY_SERVICE_CONFIG.identity_socket_addr, - CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(CODE_VERSION, DEVICE_TYPE), ) .await .map_err(|e| { Error::new( Status::GenericFailure, format!("Unable to connect to identity service: {}", e), ) }) } async fn get_authenticated_identity_client( user_id: String, device_id: String, access_token: String, ) -> Result { info!("Connecting to identity service"); grpc_clients::identity::get_auth_client( &IDENTITY_SERVICE_CONFIG.identity_socket_addr, user_id, device_id, access_token, - CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(CODE_VERSION, DEVICE_TYPE), ) .await .map_err(|e| { Error::new( Status::GenericFailure, format!("Unable to connect to identity service: {}", e), ) }) } #[napi(object)] pub struct SignedIdentityKeysBlob { pub payload: String, pub signature: String, } #[napi(object)] pub struct UserLoginInfo { pub user_id: String, pub access_token: String, } pub struct DeviceInboundKeyInfo { pub payload: String, pub payload_signature: String, pub content_prekey: String, pub content_prekey_signature: String, pub notif_prekey: String, pub notif_prekey_signature: String, } impl TryFrom for DeviceInboundKeyInfo { type Error = Error; fn try_from(key_info: InboundKeyInfo) -> Result { let identity_info = key_info .identity_info .ok_or(Error::from_status(Status::GenericFailure))?; let IdentityKeyInfo { payload, payload_signature, } = identity_info; let content_prekey = key_info .content_prekey .ok_or(Error::from_status(Status::GenericFailure))?; let Prekey { prekey: content_prekey_value, prekey_signature: content_prekey_signature, } = content_prekey; let notif_prekey = key_info .notif_prekey .ok_or(Error::from_status(Status::GenericFailure))?; let Prekey { prekey: notif_prekey_value, prekey_signature: notif_prekey_signature, } = notif_prekey; Ok(Self { payload, payload_signature, content_prekey: content_prekey_value, content_prekey_signature, notif_prekey: notif_prekey_value, notif_prekey_signature, }) } } #[napi(object)] pub struct InboundKeyInfoResponse { pub payload: String, pub payload_signature: String, pub content_prekey: String, pub content_prekey_signature: String, pub notif_prekey: String, pub notif_prekey_signature: String, pub username: Option, pub wallet_address: Option, } pub fn handle_grpc_error(error: tonic::Status) -> napi::Error { warn!("Received error: {}", error.message()); Error::new(Status::GenericFailure, error.message()) } #[cfg(test)] mod tests { use super::CODE_VERSION; #[test] #[allow(clippy::assertions_on_constants)] fn test_code_version_exists() { assert!(CODE_VERSION > 0); } } diff --git a/native/native_rust_library/src/identity.rs b/native/native_rust_library/src/identity.rs index 3e53d4604..00d4a376b 100644 --- a/native/native_rust_library/src/identity.rs +++ b/native/native_rust_library/src/identity.rs @@ -1,207 +1,212 @@ -use grpc_clients::identity::get_unauthenticated_client; use grpc_clients::identity::protos::unauth::{ AuthResponse, DeviceKeyUpload, Empty, IdentityKeyInfo, Prekey, }; +use grpc_clients::identity::{get_unauthenticated_client, PlatformMetadata}; +use lazy_static::lazy_static; use serde::Serialize; +use crate::generated::STATE_VERSION; 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 account_actions; pub mod device_list; pub mod exact_user_search; pub mod farcaster; pub mod find_user_identities; pub mod login; pub mod registration; pub mod x3dh; +lazy_static! { + pub static ref PLATFORM_METADATA: PlatformMetadata = PlatformMetadata { + device_type: DEVICE_TYPE.as_str_name().to_lowercase(), + code_version: CODE_VERSION, + state_version: Some(STATE_VERSION), + major_desktop_version: None, + }; +} + pub mod ffi { use super::*; pub use account_actions::ffi::*; pub use device_list::ffi::*; pub use exact_user_search::ffi::*; pub use farcaster::ffi::*; pub use find_user_identities::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 DeviceKeys { 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, } impl From for DeviceKeyUpload { fn from(value: DeviceKeys) -> Self { let DeviceKeys { key_payload, key_payload_signature, content_prekey, content_prekey_signature, notif_prekey, notif_prekey_signature, content_one_time_keys, notif_one_time_keys, } = value; Self { device_key_info: Some(IdentityKeyInfo { payload: key_payload, payload_signature: key_payload_signature, }), 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(), } } } pub struct LogInPasswordUserInfo { pub username: String, pub password: String, pub device_keys: DeviceKeys, } pub struct RegisterPasswordUserInfo { pub username: String, pub password: String, pub device_keys: DeviceKeys, pub farcaster_id: Option, pub initial_device_list: String, } pub struct RegisterReservedPasswordUserInfo { pub username: String, pub password: String, pub device_keys: DeviceKeys, pub keyserver_message: String, pub keyserver_signature: String, pub initial_device_list: String, } pub struct LogInWalletUserInfo { pub siwe_message: String, pub siwe_signature: String, pub device_keys: DeviceKeys, } pub struct RegisterWalletUserInfo { pub siwe_message: String, pub siwe_signature: String, pub device_keys: DeviceKeys, pub farcaster_id: Option, pub initial_device_list: String, } pub struct RegisterReservedWalletUserInfo { pub siwe_message: String, pub siwe_signature: String, pub device_keys: DeviceKeys, pub keyserver_message: String, pub keyserver_signature: String, pub initial_device_list: String, } /// Counterpart of proto [`AuthResponse`] message /// that implements the `Serialize` trait. #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct IdentityAuthResult { #[serde(rename = "userID")] user_id: String, access_token: String, username: String, } impl From for IdentityAuthResult { fn from(value: AuthResponse) -> Self { let AuthResponse { user_id, access_token, username, } = value; Self { user_id, access_token, username, } } } // 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 mut identity_client = + get_unauthenticated_client(IDENTITY_SOCKET_ADDR, PLATFORM_METADATA.clone()) + .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 mut identity_client = + get_unauthenticated_client(IDENTITY_SOCKET_ADDR, PLATFORM_METADATA.clone()) + .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/account_actions.rs b/native/native_rust_library/src/identity/account_actions.rs index ad6c7bb86..a34e7459b 100644 --- a/native/native_rust_library/src/identity/account_actions.rs +++ b/native/native_rust_library/src/identity/account_actions.rs @@ -1,243 +1,241 @@ use comm_opaque2::client::{Login, Registration}; use grpc_clients::identity::get_auth_client; use grpc_clients::identity::protos::auth::{ DeletePasswordUserFinishRequest, DeletePasswordUserStartRequest, UpdateUserPasswordFinishRequest, UpdateUserPasswordStartRequest, }; use grpc_clients::identity::protos::unauth::Empty; use crate::identity::AuthInfo; use crate::utils::jsi_callbacks::handle_void_result_as_callback; -use crate::{Error, CODE_VERSION, DEVICE_TYPE, IDENTITY_SOCKET_ADDR, RUNTIME}; +use crate::{Error, IDENTITY_SOCKET_ADDR, RUNTIME}; + +use super::PLATFORM_METADATA; pub mod ffi { use super::*; pub 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); }); } pub fn delete_wallet_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_wallet_user_helper(auth_info).await; handle_void_result_as_callback(result, promise_id); }); } pub fn delete_password_user( user_id: String, device_id: String, access_token: String, password: String, promise_id: u32, ) { RUNTIME.spawn(async move { let auth_info = AuthInfo { access_token, user_id, device_id, }; let result = delete_password_user_helper(auth_info, password).await; handle_void_result_as_callback(result, promise_id); }); } pub 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, LogOutType::Legacy).await; handle_void_result_as_callback(result, promise_id); }); } pub fn log_out_secondary_device( 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, LogOutType::SecondaryDevice).await; handle_void_result_as_callback(result, promise_id); }); } } struct UpdatePasswordInfo { user_id: String, device_id: String, access_token: String, password: String, } 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(crate::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(), + PLATFORM_METADATA.clone(), ) .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(crate::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(()) } async fn delete_wallet_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(), + PLATFORM_METADATA.clone(), ) .await?; identity_client.delete_wallet_user(Empty {}).await?; Ok(()) } async fn delete_password_user_helper( auth_info: AuthInfo, password: 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(), + PLATFORM_METADATA.clone(), ) .await?; let mut client_login = Login::new(); let opaque_login_request = client_login.start(&password).map_err(crate::handle_error)?; let delete_start_request = DeletePasswordUserStartRequest { opaque_login_request, }; let response = identity_client .delete_password_user_start(delete_start_request) .await?; let delete_start_response = response.into_inner(); let opaque_login_upload = client_login .finish(&delete_start_response.opaque_login_response) .map_err(crate::handle_error)?; let delete_finish_request = DeletePasswordUserFinishRequest { session_id: delete_start_response.session_id, opaque_login_upload, }; identity_client .delete_password_user_finish(delete_finish_request) .await?; Ok(()) } enum LogOutType { Legacy, SecondaryDevice, } async fn log_out_helper( auth_info: AuthInfo, log_out_type: LogOutType, ) -> 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(), + PLATFORM_METADATA.clone(), ) .await?; match log_out_type { LogOutType::Legacy => identity_client.log_out_user(Empty {}).await?, LogOutType::SecondaryDevice => { identity_client.log_out_secondary_device(Empty {}).await? } }; Ok(()) } diff --git a/native/native_rust_library/src/identity/device_list.rs b/native/native_rust_library/src/identity/device_list.rs index 5d2fd187a..979fd6714 100644 --- a/native/native_rust_library/src/identity/device_list.rs +++ b/native/native_rust_library/src/identity/device_list.rs @@ -1,145 +1,144 @@ use grpc_clients::identity::get_auth_client; use grpc_clients::identity::protos::auth::{ GetDeviceListRequest, PeersDeviceListsRequest, UpdateDeviceListRequest, }; 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}; +use crate::{Error, IDENTITY_SOCKET_ADDR, RUNTIME}; + +use super::PLATFORM_METADATA; pub mod ffi { use super::*; pub 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); }); } pub fn get_device_lists_for_users( auth_user_id: String, auth_device_id: String, auth_access_token: String, user_ids: Vec, 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 = get_device_lists_for_users_helper(auth_info, user_ids).await; handle_string_result_as_callback(result, promise_id); }); } pub 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 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(), + PLATFORM_METADATA.clone(), ) .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) } async fn get_device_lists_for_users_helper( auth_info: AuthInfo, user_ids: Vec, ) -> 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(), + PLATFORM_METADATA.clone(), ) .await?; let response = identity_client .get_device_lists_for_users(PeersDeviceListsRequest { user_ids }) .await? .into_inner(); let payload = serde_json::to_string(&response.users_device_lists)?; Ok(payload) } 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(), + PLATFORM_METADATA.clone(), ) .await?; let update_request = UpdateDeviceListRequest { new_device_list: update_payload, }; identity_client.update_device_list(update_request).await?; Ok(()) } diff --git a/native/native_rust_library/src/identity/exact_user_search.rs b/native/native_rust_library/src/identity/exact_user_search.rs index a74da226a..99a47e7df 100644 --- a/native/native_rust_library/src/identity/exact_user_search.rs +++ b/native/native_rust_library/src/identity/exact_user_search.rs @@ -1,72 +1,70 @@ use crate::{ - handle_string_result_as_callback, Error, CODE_VERSION, DEVICE_TYPE, - IDENTITY_SOCKET_ADDR, RUNTIME, + handle_string_result_as_callback, Error, IDENTITY_SOCKET_ADDR, RUNTIME, }; use find_user_id_request::Identifier as RequestIdentifier; use grpc_clients::identity::{ get_unauthenticated_client, protos::unauth::{find_user_id_request, FindUserIdRequest}, }; use serde::Serialize; use tracing::instrument; +use super::PLATFORM_METADATA; + #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct FindUserIDResponse { #[serde(rename = "userID")] user_id: Option, #[serde(rename = "isReserved")] is_reserved: bool, } pub mod ffi { use super::*; #[instrument] pub fn find_user_id_for_wallet_address( wallet_address: String, promise_id: u32, ) { RUNTIME.spawn(async move { let result = find_user_id_helper(RequestIdentifier::WalletAddress(wallet_address)) .await; handle_string_result_as_callback(result, promise_id); }); } #[instrument] pub fn find_user_id_for_username(username: String, promise_id: u32) { RUNTIME.spawn(async move { let result = find_user_id_helper(RequestIdentifier::Username(username)).await; handle_string_result_as_callback(result, promise_id); }); } } async fn find_user_id_helper( identifier: RequestIdentifier, ) -> Result { let find_user_id_request = FindUserIdRequest { identifier: Some(identifier), }; - let mut identity_client = get_unauthenticated_client( - IDENTITY_SOCKET_ADDR, - CODE_VERSION, - DEVICE_TYPE.as_str_name().to_lowercase(), - ) - .await?; + let mut identity_client = + get_unauthenticated_client(IDENTITY_SOCKET_ADDR, PLATFORM_METADATA.clone()) + .await?; let response = identity_client .find_user_id(find_user_id_request) .await? .into_inner(); let find_user_id_response = FindUserIDResponse { user_id: response.user_id, is_reserved: response.is_reserved, }; Ok(serde_json::to_string(&find_user_id_response)?) } diff --git a/native/native_rust_library/src/identity/farcaster.rs b/native/native_rust_library/src/identity/farcaster.rs index 457cf2376..0353ff260 100644 --- a/native/native_rust_library/src/identity/farcaster.rs +++ b/native/native_rust_library/src/identity/farcaster.rs @@ -1,149 +1,146 @@ use crate::{ utils::jsi_callbacks::{ handle_string_result_as_callback, handle_void_result_as_callback, }, - Error, CODE_VERSION, DEVICE_TYPE, IDENTITY_SOCKET_ADDR, RUNTIME, + Error, IDENTITY_SOCKET_ADDR, RUNTIME, }; use grpc_clients::identity::{ get_auth_client, get_unauthenticated_client, protos::auth::LinkFarcasterAccountRequest, protos::unauth::Empty, protos::unauth::GetFarcasterUsersRequest, }; use serde::Serialize; +use super::PLATFORM_METADATA; + #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct FarcasterUser { #[serde(rename = "userID")] user_id: String, username: String, #[serde(rename = "farcasterID")] farcaster_id: String, } pub mod ffi { use super::*; pub fn get_farcaster_users(farcaster_ids: Vec, promise_id: u32) { RUNTIME.spawn(async move { let result = get_farcaster_users_helper(farcaster_ids).await; handle_string_result_as_callback(result, promise_id); }); } pub fn link_farcaster_account( user_id: String, device_id: String, access_token: String, farcaster_id: String, promise_id: u32, ) { RUNTIME.spawn(async move { let result = link_farcaster_account_helper( user_id, device_id, access_token, farcaster_id, ) .await; handle_void_result_as_callback(result, promise_id); }); } pub fn unlink_farcaster_account( user_id: String, device_id: String, access_token: String, promise_id: u32, ) { RUNTIME.spawn(async move { let result = unlink_farcaster_account_helper(user_id, device_id, access_token).await; handle_void_result_as_callback(result, promise_id); }); } } pub fn farcaster_id_string_to_option(input: &str) -> Option { if input.is_empty() { None } else { Some(input.to_string()) } } async fn get_farcaster_users_helper( farcaster_ids: Vec, ) -> Result { let get_farcaster_users_request = GetFarcasterUsersRequest { farcaster_ids }; - let mut identity_client = get_unauthenticated_client( - IDENTITY_SOCKET_ADDR, - CODE_VERSION, - DEVICE_TYPE.as_str_name().to_lowercase(), - ) - .await?; + let mut identity_client = + get_unauthenticated_client(IDENTITY_SOCKET_ADDR, PLATFORM_METADATA.clone()) + .await?; let response = identity_client .get_farcaster_users(get_farcaster_users_request) .await? .into_inner(); let mapped_response: Vec = response .farcaster_users .into_iter() .map(|farcaster_user| FarcasterUser { user_id: farcaster_user.user_id, username: farcaster_user.username, farcaster_id: farcaster_user.farcaster_id, }) .collect(); Ok(serde_json::to_string(&mapped_response)?) } async fn link_farcaster_account_helper( user_id: String, device_id: String, access_token: String, farcaster_id: String, ) -> Result<(), Error> { let mut identity_client = get_auth_client( IDENTITY_SOCKET_ADDR, user_id, device_id, access_token, - CODE_VERSION, - DEVICE_TYPE.as_str_name().to_lowercase(), + PLATFORM_METADATA.clone(), ) .await?; let link_farcaster_account_request = LinkFarcasterAccountRequest { farcaster_id }; identity_client .link_farcaster_account(link_farcaster_account_request) .await?; Ok(()) } async fn unlink_farcaster_account_helper( user_id: String, device_id: String, access_token: String, ) -> Result<(), Error> { let mut identity_client = get_auth_client( IDENTITY_SOCKET_ADDR, user_id, device_id, access_token, - CODE_VERSION, - DEVICE_TYPE.as_str_name().to_lowercase(), + PLATFORM_METADATA.clone(), ) .await?; identity_client.unlink_farcaster_account(Empty {}).await?; Ok(()) } diff --git a/native/native_rust_library/src/identity/find_user_identities.rs b/native/native_rust_library/src/identity/find_user_identities.rs index cd6675fdb..80855cdb0 100644 --- a/native/native_rust_library/src/identity/find_user_identities.rs +++ b/native/native_rust_library/src/identity/find_user_identities.rs @@ -1,108 +1,109 @@ use grpc_clients::identity::get_auth_client; use grpc_clients::identity::protos::auth::{ UserIdentitiesRequest, UserIdentitiesResponse, }; use serde::Serialize; use std::collections::HashMap; use crate::identity::AuthInfo; use crate::utils::jsi_callbacks::handle_string_result_as_callback; -use crate::{Error, CODE_VERSION, DEVICE_TYPE, IDENTITY_SOCKET_ADDR, RUNTIME}; +use crate::{Error, IDENTITY_SOCKET_ADDR, RUNTIME}; + +use super::PLATFORM_METADATA; pub mod ffi { use super::*; pub fn find_user_identities( auth_user_id: String, auth_device_id: String, auth_access_token: String, user_ids: Vec, 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 = find_user_identities_helper(auth_info, user_ids).await; handle_string_result_as_callback(result, promise_id); }); } } #[derive(Serialize)] pub struct UserIdentities { pub identities: HashMap, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct EthereumIdentity { pub wallet_address: String, pub siwe_message: String, pub siwe_signature: String, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct Identity { pub username: String, pub eth_identity: Option, #[serde(rename = "farcasterID")] pub farcaster_id: Option, } impl TryFrom for UserIdentities { type Error = Error; fn try_from(response: UserIdentitiesResponse) -> Result { let identities: HashMap = response .identities .into_iter() .map(|(user_id, identity)| { let eth_identity = identity.eth_identity.map(|eth_identity| EthereumIdentity { wallet_address: eth_identity.wallet_address, siwe_message: eth_identity.siwe_message, siwe_signature: eth_identity.siwe_signature, }); ( user_id, Identity { username: identity.username, eth_identity, farcaster_id: identity.farcaster_id, }, ) }) .collect(); Ok(UserIdentities { identities }) } } async fn find_user_identities_helper( auth_info: AuthInfo, user_ids: Vec, ) -> 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(), + PLATFORM_METADATA.clone(), ) .await?; let response = identity_client .find_user_identities(UserIdentitiesRequest { user_ids }) .await? .into_inner(); let user_identities = UserIdentities::try_from(response)?; Ok(serde_json::to_string(&user_identities.identities)?) } diff --git a/native/native_rust_library/src/identity/login.rs b/native/native_rust_library/src/identity/login.rs index 9a2c795bd..262d2e9d6 100644 --- a/native/native_rust_library/src/identity/login.rs +++ b/native/native_rust_library/src/identity/login.rs @@ -1,280 +1,271 @@ use comm_opaque2::client::Login; use grpc_clients::identity::{ get_unauthenticated_client, protos::unauth::{ DeviceKeyUpload, ExistingDeviceLoginRequest, IdentityKeyInfo, OpaqueLoginFinishRequest, OpaqueLoginStartRequest, Prekey, SecondaryDeviceKeysUploadRequest, WalletAuthRequest, }, }; use tracing::instrument; -use super::{IdentityAuthResult, LogInPasswordUserInfo, LogInWalletUserInfo}; +use super::{ + IdentityAuthResult, LogInPasswordUserInfo, LogInWalletUserInfo, + PLATFORM_METADATA, +}; use crate::utils::jsi_callbacks::handle_string_result_as_callback; -use crate::{Error, CODE_VERSION, DEVICE_TYPE, IDENTITY_SOCKET_ADDR, RUNTIME}; +use crate::{Error, DEVICE_TYPE, IDENTITY_SOCKET_ADDR, RUNTIME}; #[allow(clippy::too_many_arguments)] pub mod ffi { use crate::identity::{ DeviceKeys, LogInPasswordUserInfo, LogInWalletUserInfo, }; use super::*; #[instrument] pub 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, ) { RUNTIME.spawn(async move { let password_user_info = LogInPasswordUserInfo { username, password, device_keys: DeviceKeys { key_payload, key_payload_signature, content_prekey, content_prekey_signature, notif_prekey, notif_prekey_signature, content_one_time_keys: Vec::new(), notif_one_time_keys: Vec::new(), }, }; let result = log_in_password_user_helper(password_user_info).await; handle_string_result_as_callback(result, promise_id); }); } #[instrument] pub 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, ) { RUNTIME.spawn(async move { let wallet_user_info = LogInWalletUserInfo { siwe_message, siwe_signature, device_keys: DeviceKeys { key_payload, key_payload_signature, content_prekey, content_prekey_signature, notif_prekey, notif_prekey_signature, content_one_time_keys: Vec::new(), notif_one_time_keys: Vec::new(), }, }; let result = log_in_wallet_user_helper(wallet_user_info).await; handle_string_result_as_callback(result, promise_id); }); } // QR code device log in pub fn upload_secondary_device_keys_and_log_in( user_id: String, nonce: String, nonce_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, 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, }), 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, nonce, nonce_signature, device_key_upload, ) .await; handle_string_result_as_callback(result, promise_id); }); } pub fn log_in_existing_device( user_id: String, device_id: String, nonce: String, nonce_signature: String, promise_id: u32, ) { RUNTIME.spawn(async move { let result = log_in_existing_device_helper( user_id, device_id, nonce, nonce_signature, ) .await; handle_string_result_as_callback(result, promise_id); }); } } async fn log_in_password_user_helper( password_user_info: LogInPasswordUserInfo, ) -> Result { let mut client_login = Login::new(); let opaque_login_request = client_login .start(&password_user_info.password) .map_err(crate::handle_error)?; let login_start_request = OpaqueLoginStartRequest { opaque_login_request, username: password_user_info.username, device_key_upload: Some(password_user_info.device_keys.into()), force: None, }; - let mut identity_client = get_unauthenticated_client( - IDENTITY_SOCKET_ADDR, - CODE_VERSION, - DEVICE_TYPE.as_str_name().to_lowercase(), - ) - .await?; + let mut identity_client = + get_unauthenticated_client(IDENTITY_SOCKET_ADDR, PLATFORM_METADATA.clone()) + .await?; let response = identity_client .log_in_password_user_start(login_start_request) .await?; let login_start_response = response.into_inner(); let opaque_login_upload = client_login .finish(&login_start_response.opaque_login_response) .map_err(crate::handle_error)?; let login_finish_request = OpaqueLoginFinishRequest { session_id: login_start_response.session_id, opaque_login_upload, }; let login_finish_response = identity_client .log_in_password_user_finish(login_finish_request) .await? .into_inner(); let auth_result = IdentityAuthResult::from(login_finish_response); Ok(serde_json::to_string(&auth_result)?) } async fn log_in_wallet_user_helper( wallet_user_info: LogInWalletUserInfo, ) -> Result { let login_request = WalletAuthRequest { siwe_message: wallet_user_info.siwe_message, siwe_signature: wallet_user_info.siwe_signature, device_key_upload: Some(wallet_user_info.device_keys.into()), farcaster_id: None, initial_device_list: "".to_string(), }; - let mut identity_client = get_unauthenticated_client( - IDENTITY_SOCKET_ADDR, - CODE_VERSION, - DEVICE_TYPE.as_str_name().to_lowercase(), - ) - .await?; + let mut identity_client = + get_unauthenticated_client(IDENTITY_SOCKET_ADDR, PLATFORM_METADATA.clone()) + .await?; let login_response = identity_client .log_in_wallet_user(login_request) .await? .into_inner(); let auth_result = IdentityAuthResult::from(login_response); Ok(serde_json::to_string(&auth_result)?) } async fn upload_secondary_device_keys_and_log_in_helper( user_id: String, nonce: String, nonce_signature: 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 mut identity_client = + get_unauthenticated_client(IDENTITY_SOCKET_ADDR, PLATFORM_METADATA.clone()) + .await?; let request = SecondaryDeviceKeysUploadRequest { user_id, nonce, nonce_signature, device_key_upload: Some(device_key_upload), }; let response = identity_client .upload_keys_for_registered_device_and_log_in(request) .await? .into_inner(); let auth_result = IdentityAuthResult::from(response); Ok(serde_json::to_string(&auth_result)?) } async fn log_in_existing_device_helper( user_id: String, device_id: String, nonce: String, nonce_signature: String, ) -> Result { - let mut identity_client = get_unauthenticated_client( - IDENTITY_SOCKET_ADDR, - CODE_VERSION, - DEVICE_TYPE.as_str_name().to_lowercase(), - ) - .await?; + let mut identity_client = + get_unauthenticated_client(IDENTITY_SOCKET_ADDR, PLATFORM_METADATA.clone()) + .await?; let request = ExistingDeviceLoginRequest { user_id, device_id, nonce, nonce_signature, }; let response = identity_client .log_in_existing_device(request) .await? .into_inner(); let auth_result = IdentityAuthResult::from(response); Ok(serde_json::to_string(&auth_result)?) } diff --git a/native/native_rust_library/src/identity/registration.rs b/native/native_rust_library/src/identity/registration.rs index a878eeae7..8eea5f668 100644 --- a/native/native_rust_library/src/identity/registration.rs +++ b/native/native_rust_library/src/identity/registration.rs @@ -1,337 +1,325 @@ use crate::{ - utils::jsi_callbacks::handle_string_result_as_callback, Error, CODE_VERSION, - DEVICE_TYPE, IDENTITY_SOCKET_ADDR, RUNTIME, + utils::jsi_callbacks::handle_string_result_as_callback, Error, + IDENTITY_SOCKET_ADDR, RUNTIME, }; use comm_opaque2::client::Registration; use grpc_clients::identity::{ get_unauthenticated_client, protos::unauth::{ RegistrationFinishRequest, RegistrationStartRequest, ReservedRegistrationStartRequest, ReservedWalletRegistrationRequest, WalletAuthRequest, }, }; use tracing::instrument; use super::{ farcaster::farcaster_id_string_to_option, IdentityAuthResult, RegisterPasswordUserInfo, RegisterReservedPasswordUserInfo, - RegisterReservedWalletUserInfo, RegisterWalletUserInfo, + RegisterReservedWalletUserInfo, RegisterWalletUserInfo, PLATFORM_METADATA, }; #[allow(clippy::too_many_arguments)] pub mod ffi { use crate::identity::{ DeviceKeys, RegisterPasswordUserInfo, RegisterReservedPasswordUserInfo, RegisterWalletUserInfo, }; use super::*; #[instrument] pub 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, initial_device_list: String, promise_id: u32, ) { RUNTIME.spawn(async move { let password_user_info = RegisterPasswordUserInfo { username, password, device_keys: DeviceKeys { key_payload, key_payload_signature, content_prekey, content_prekey_signature, notif_prekey, notif_prekey_signature, content_one_time_keys, notif_one_time_keys, }, farcaster_id: farcaster_id_string_to_option(&farcaster_id), initial_device_list, }; let result = register_password_user_helper(password_user_info).await; handle_string_result_as_callback(result, promise_id); }); } #[instrument] pub fn register_reserved_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, keyserver_message: String, keyserver_signature: String, initial_device_list: String, promise_id: u32, ) { RUNTIME.spawn(async move { let password_user_info = RegisterReservedPasswordUserInfo { username, password, device_keys: DeviceKeys { key_payload, key_payload_signature, content_prekey, content_prekey_signature, notif_prekey, notif_prekey_signature, content_one_time_keys, notif_one_time_keys, }, keyserver_message, keyserver_signature, initial_device_list, }; let result = register_reserved_password_user_helper(password_user_info).await; handle_string_result_as_callback(result, promise_id); }); } #[instrument] pub 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, initial_device_list: String, promise_id: u32, ) { RUNTIME.spawn(async move { let wallet_user_info = RegisterWalletUserInfo { siwe_message, siwe_signature, device_keys: DeviceKeys { key_payload, key_payload_signature, content_prekey, content_prekey_signature, notif_prekey, notif_prekey_signature, content_one_time_keys, notif_one_time_keys, }, farcaster_id: farcaster_id_string_to_option(&farcaster_id), initial_device_list, }; let result = register_wallet_user_helper(wallet_user_info).await; handle_string_result_as_callback(result, promise_id); }); } #[instrument] pub fn register_reserved_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, keyserver_message: String, keyserver_signature: String, initial_device_list: String, promise_id: u32, ) { RUNTIME.spawn(async move { let wallet_user_info = RegisterReservedWalletUserInfo { siwe_message, siwe_signature, device_keys: DeviceKeys { key_payload, key_payload_signature, content_prekey, content_prekey_signature, notif_prekey, notif_prekey_signature, content_one_time_keys, notif_one_time_keys, }, keyserver_message, keyserver_signature, initial_device_list, }; let result = register_reserved_wallet_user_helper(wallet_user_info).await; handle_string_result_as_callback(result, promise_id); }); } } async fn register_password_user_helper( password_user_info: RegisterPasswordUserInfo, ) -> Result { let mut client_registration = Registration::new(); let opaque_registration_request = client_registration .start(&password_user_info.password) .map_err(crate::handle_error)?; let registration_start_request = RegistrationStartRequest { opaque_registration_request, username: password_user_info.username, device_key_upload: Some(password_user_info.device_keys.into()), farcaster_id: password_user_info.farcaster_id, initial_device_list: password_user_info.initial_device_list, }; - let mut identity_client = get_unauthenticated_client( - IDENTITY_SOCKET_ADDR, - CODE_VERSION, - DEVICE_TYPE.as_str_name().to_lowercase(), - ) - .await?; + let mut identity_client = + get_unauthenticated_client(IDENTITY_SOCKET_ADDR, PLATFORM_METADATA.clone()) + .await?; let response = identity_client .register_password_user_start(registration_start_request) .await?; let registration_start_response = response.into_inner(); let opaque_registration_upload = client_registration .finish( &password_user_info.password, ®istration_start_response.opaque_registration_response, ) .map_err(crate::handle_error)?; let registration_finish_request = RegistrationFinishRequest { session_id: registration_start_response.session_id, opaque_registration_upload, }; let registration_finish_response = identity_client .register_password_user_finish(registration_finish_request) .await? .into_inner(); let auth_result = IdentityAuthResult::from(registration_finish_response); Ok(serde_json::to_string(&auth_result)?) } async fn register_reserved_password_user_helper( password_user_info: RegisterReservedPasswordUserInfo, ) -> Result { let mut client_registration = Registration::new(); let opaque_registration_request = client_registration .start(&password_user_info.password) .map_err(crate::handle_error)?; let registration_start_request = ReservedRegistrationStartRequest { opaque_registration_request, username: password_user_info.username, device_key_upload: Some(password_user_info.device_keys.into()), keyserver_message: password_user_info.keyserver_message, keyserver_signature: password_user_info.keyserver_signature, initial_device_list: password_user_info.initial_device_list, }; - let mut identity_client = get_unauthenticated_client( - IDENTITY_SOCKET_ADDR, - CODE_VERSION, - DEVICE_TYPE.as_str_name().to_lowercase(), - ) - .await?; + let mut identity_client = + get_unauthenticated_client(IDENTITY_SOCKET_ADDR, PLATFORM_METADATA.clone()) + .await?; let response = identity_client .register_reserved_password_user_start(registration_start_request) .await?; let registration_start_response = response.into_inner(); let opaque_registration_upload = client_registration .finish( &password_user_info.password, ®istration_start_response.opaque_registration_response, ) .map_err(crate::handle_error)?; let registration_finish_request = RegistrationFinishRequest { session_id: registration_start_response.session_id, opaque_registration_upload, }; let registration_finish_response = identity_client .register_password_user_finish(registration_finish_request) .await? .into_inner(); let auth_result = IdentityAuthResult::from(registration_finish_response); Ok(serde_json::to_string(&auth_result)?) } async fn register_wallet_user_helper( wallet_user_info: RegisterWalletUserInfo, ) -> Result { let registration_request = WalletAuthRequest { siwe_message: wallet_user_info.siwe_message, siwe_signature: wallet_user_info.siwe_signature, device_key_upload: Some(wallet_user_info.device_keys.into()), farcaster_id: wallet_user_info.farcaster_id, initial_device_list: wallet_user_info.initial_device_list, }; - let mut identity_client = get_unauthenticated_client( - IDENTITY_SOCKET_ADDR, - CODE_VERSION, - DEVICE_TYPE.as_str_name().to_lowercase(), - ) - .await?; + let mut identity_client = + get_unauthenticated_client(IDENTITY_SOCKET_ADDR, PLATFORM_METADATA.clone()) + .await?; let registration_response = identity_client .register_wallet_user(registration_request) .await? .into_inner(); let auth_result = IdentityAuthResult::from(registration_response); Ok(serde_json::to_string(&auth_result)?) } async fn register_reserved_wallet_user_helper( wallet_user_info: RegisterReservedWalletUserInfo, ) -> Result { let registration_request = ReservedWalletRegistrationRequest { siwe_message: wallet_user_info.siwe_message, siwe_signature: wallet_user_info.siwe_signature, device_key_upload: Some(wallet_user_info.device_keys.into()), keyserver_message: wallet_user_info.keyserver_message, keyserver_signature: wallet_user_info.keyserver_signature, initial_device_list: wallet_user_info.initial_device_list, }; - let mut identity_client = get_unauthenticated_client( - IDENTITY_SOCKET_ADDR, - CODE_VERSION, - DEVICE_TYPE.as_str_name().to_lowercase(), - ) - .await?; + let mut identity_client = + get_unauthenticated_client(IDENTITY_SOCKET_ADDR, PLATFORM_METADATA.clone()) + .await?; let registration_response = identity_client .register_reserved_wallet_user(registration_request) .await? .into_inner(); let auth_result = IdentityAuthResult::from(registration_response); Ok(serde_json::to_string(&auth_result)?) } diff --git a/native/native_rust_library/src/identity/x3dh.rs b/native/native_rust_library/src/identity/x3dh.rs index 52c3e850e..3976f1bdf 100644 --- a/native/native_rust_library/src/identity/x3dh.rs +++ b/native/native_rust_library/src/identity/x3dh.rs @@ -1,395 +1,392 @@ 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}; +use crate::{Error, IDENTITY_SOCKET_ADDR, RUNTIME}; + +use super::PLATFORM_METADATA; 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); }); } #[allow(clippy::too_many_arguments)] 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 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 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, } = 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, 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, } = 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, 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(), + PLATFORM_METADATA.clone(), ) .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(), + PLATFORM_METADATA.clone(), ) .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(), + PLATFORM_METADATA.clone(), ) .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(), + PLATFORM_METADATA.clone(), ) .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(), + PLATFORM_METADATA.clone(), ) .await?; identity_client.upload_one_time_keys(upload_request).await?; Ok(()) } diff --git a/services/commtest/src/identity/device.rs b/services/commtest/src/identity/device.rs index 653569e28..1b4d923bf 100644 --- a/services/commtest/src/identity/device.rs +++ b/services/commtest/src/identity/device.rs @@ -1,239 +1,238 @@ use comm_opaque2::client::{Login, Registration}; -use grpc_clients::identity::{get_auth_client, get_unauthenticated_client}; +use grpc_clients::identity::{ + get_auth_client, get_unauthenticated_client, PlatformMetadata, +}; use rand::{distributions::Alphanumeric, Rng}; use crate::identity::olm_account_infos::generate_random_olm_key; use crate::identity::olm_account_infos::{ ClientPublicKeys, DEFAULT_CLIENT_KEYS, }; use crate::service_addr; use grpc_clients::identity::protos::unauth::{ DeviceKeyUpload, DeviceType, Empty, IdentityKeyInfo, OpaqueLoginFinishRequest, OpaqueLoginStartRequest, Prekey, RegistrationFinishRequest, RegistrationStartRequest, VerifyUserAccessTokenRequest, }; pub const PLACEHOLDER_CODE_VERSION: u64 = 0; pub const DEVICE_TYPE: &str = "service"; const PASSWORD: &str = "pass"; pub struct DeviceInfo { pub username: String, pub user_id: String, pub device_id: String, pub access_token: String, } impl From<&DeviceInfo> for VerifyUserAccessTokenRequest { fn from(value: &DeviceInfo) -> Self { Self { user_id: value.user_id.to_string(), device_id: value.device_id.to_string(), access_token: value.access_token.to_string(), } } } /// Register a new user with a device. /// - Gives random username (returned by function). /// - Device type defaults to keyserver. /// - Device ID taken from `keys` (ed25519), see [`DEFAULT_CLIENT_KEYS`] pub async fn register_user_device( keys: Option<&ClientPublicKeys>, device_type: Option, ) -> DeviceInfo { register_user_device_with_device_list(keys, device_type, None).await } /// Same as [`register_user_device`] but with third param being a /// stringified signed device list JSON pub async fn register_user_device_with_device_list( keys: Option<&ClientPublicKeys>, device_type: Option, initial_device_list: Option, ) -> DeviceInfo { let username: String = rand::thread_rng() .sample_iter(&Alphanumeric) .take(7) .map(char::from) .collect(); // TODO: Generate dynamic valid olm account info let keys = keys.unwrap_or_else(|| &DEFAULT_CLIENT_KEYS); let example_payload = serde_json::to_string(&keys).expect("Failed to serialize example payload"); // The ed25519 value from the olm payload let device_id = &keys.primary_identity_public_keys.ed25519; let device_type = device_type.unwrap_or(DeviceType::Keyserver); let mut client_registration = Registration::new(); let opaque_registration_request = client_registration.start(PASSWORD).unwrap(); let registration_start_request = RegistrationStartRequest { opaque_registration_request, username: username.to_string(), device_key_upload: Some(DeviceKeyUpload { device_key_info: Some(IdentityKeyInfo { payload: example_payload.to_string(), payload_signature: "foo".to_string(), }), content_upload: Some(Prekey { prekey: generate_random_olm_key(), prekey_signature: "content_prekey_sig".to_string(), }), notif_upload: Some(Prekey { prekey: generate_random_olm_key(), prekey_signature: "notif_prekey_sig".to_string(), }), one_time_content_prekeys: Vec::new(), one_time_notif_prekeys: Vec::new(), device_type: device_type.into(), }), farcaster_id: None, initial_device_list: initial_device_list.unwrap_or_default(), }; let mut identity_client = get_unauthenticated_client( &service_addr::IDENTITY_GRPC.to_string(), - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let registration_start_response = identity_client .register_password_user_start(registration_start_request) .await .unwrap() .into_inner(); let opaque_registration_upload = client_registration .finish( PASSWORD, ®istration_start_response.opaque_registration_response, ) .unwrap(); let registration_finish_request = RegistrationFinishRequest { session_id: registration_start_response.session_id, opaque_registration_upload, }; let registration_finish_response = identity_client .register_password_user_finish(registration_finish_request) .await .unwrap() .into_inner(); DeviceInfo { username: username.to_string(), device_id: device_id.to_string(), user_id: registration_finish_response.user_id, access_token: registration_finish_response.access_token, } } /// Log in existing user with a device. /// - Tries to log in with given username (it has to be already registered) /// - Device type defaults to keyserver. /// - Device ID taken from `keys` (ed25519), see [`DEFAULT_CLIENT_KEYS`] pub async fn login_user_device( username: &str, keys: Option<&ClientPublicKeys>, device_type: Option, force: bool, ) -> DeviceInfo { // TODO: Generate dynamic valid olm account info let keys = keys.unwrap_or_else(|| &DEFAULT_CLIENT_KEYS); let example_payload = serde_json::to_string(&keys).expect("Failed to serialize example payload"); // The ed25519 value from the olm payload let device_id = &keys.primary_identity_public_keys.ed25519; let device_type = device_type.unwrap_or(DeviceType::Keyserver); let mut client_login = Login::new(); let opaque_login_request = client_login.start(PASSWORD).unwrap(); let login_start_request = OpaqueLoginStartRequest { opaque_login_request, username: username.to_string(), device_key_upload: Some(DeviceKeyUpload { device_key_info: Some(IdentityKeyInfo { payload: example_payload.to_string(), payload_signature: "foo".to_string(), }), content_upload: Some(Prekey { prekey: generate_random_olm_key(), prekey_signature: "content_prekey_sig".to_string(), }), notif_upload: Some(Prekey { prekey: generate_random_olm_key(), prekey_signature: "notif_prekey_sig".to_string(), }), one_time_content_prekeys: Vec::new(), one_time_notif_prekeys: Vec::new(), device_type: device_type.into(), }), force: Some(force), }; let mut identity_client = get_unauthenticated_client( &service_addr::IDENTITY_GRPC.to_string(), - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let login_start_response = identity_client .log_in_password_user_start(login_start_request) .await .unwrap() .into_inner(); let opaque_login_upload = client_login .finish(&login_start_response.opaque_login_response) .unwrap(); let login_finish_request = OpaqueLoginFinishRequest { session_id: login_start_response.session_id, opaque_login_upload, }; let login_finish_response = identity_client .log_in_password_user_finish(login_finish_request) .await .unwrap() .into_inner(); DeviceInfo { username: username.to_string(), device_id: device_id.to_string(), user_id: login_finish_response.user_id, access_token: login_finish_response.access_token, } } pub async fn logout_user_device(device_info: DeviceInfo) { let DeviceInfo { user_id, device_id, access_token, .. } = device_info; let mut client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), user_id, device_id, access_token, - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldnt connect to auth identity service"); client .log_out_user(Empty {}) .await .expect("Failed to logout user"); } diff --git a/services/commtest/tests/identity_access_tokens_tests.rs b/services/commtest/tests/identity_access_tokens_tests.rs index dd186924b..3f9e8fe3f 100644 --- a/services/commtest/tests/identity_access_tokens_tests.rs +++ b/services/commtest/tests/identity_access_tokens_tests.rs @@ -1,89 +1,88 @@ use commtest::identity::device::{ register_user_device, DEVICE_TYPE, PLACEHOLDER_CODE_VERSION, }; use commtest::identity::SigningCapableAccount; use commtest::service_addr; use grpc_clients::identity::protos::unauth::{ Empty, ExistingDeviceLoginRequest, }; +use grpc_clients::identity::PlatformMetadata; use grpc_clients::identity::{ get_unauthenticated_client, protos::unauth::VerifyUserAccessTokenRequest, }; #[tokio::test] async fn verify_access_token() { let identity_grpc_endpoint = service_addr::IDENTITY_GRPC.to_string(); let device_info = register_user_device(None, None).await; let mut identity_client = get_unauthenticated_client( &identity_grpc_endpoint, - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let verify_request = VerifyUserAccessTokenRequest::from(&device_info); let response = identity_client .verify_user_access_token(verify_request) .await .unwrap(); assert!(response.into_inner().token_valid); } #[tokio::test] async fn refresh_token_test() { let identity_grpc_endpoint = service_addr::IDENTITY_GRPC.to_string(); let mut client = get_unauthenticated_client( &identity_grpc_endpoint, - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let account = SigningCapableAccount::new(); let client_keys = account.public_keys(); let user = register_user_device(Some(&client_keys), None).await; // refresh session let nonce = client .generate_nonce(Empty {}) .await .expect("failed to generate nonce") .into_inner() .nonce; let nonce_signature = account.sign_message(&nonce); let new_credentials = client .log_in_existing_device(ExistingDeviceLoginRequest { user_id: user.user_id.clone(), device_id: user.device_id.clone(), nonce, nonce_signature, }) .await .expect("LogInExistingDevice call failed") .into_inner(); // old token should now be invalid let old_token_result = client .verify_user_access_token(VerifyUserAccessTokenRequest::from(&user)) .await .expect("failed to verify token") .into_inner(); assert!(!old_token_result.token_valid); // new token should be valid let new_token_result = client .verify_user_access_token(VerifyUserAccessTokenRequest { user_id: new_credentials.user_id, access_token: new_credentials.access_token, device_id: user.device_id, }) .await .expect("failed to verify token") .into_inner(); assert!(new_token_result.token_valid); } diff --git a/services/commtest/tests/identity_device_list_tests.rs b/services/commtest/tests/identity_device_list_tests.rs index af6f6c608..2d54aca19 100644 --- a/services/commtest/tests/identity_device_list_tests.rs +++ b/services/commtest/tests/identity_device_list_tests.rs @@ -1,571 +1,565 @@ use std::collections::{HashMap, HashSet}; use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; use commtest::identity::device::{ login_user_device, logout_user_device, register_user_device, register_user_device_with_device_list, DEVICE_TYPE, PLACEHOLDER_CODE_VERSION, }; use commtest::identity::SigningCapableAccount; use commtest::service_addr; use grpc_clients::identity::authenticated::ChainedInterceptedAuthClient; -use grpc_clients::identity::get_auth_client; use grpc_clients::identity::protos::auth::{ PeersDeviceListsRequest, UpdateDeviceListRequest, }; use grpc_clients::identity::protos::authenticated::GetDeviceListRequest; use grpc_clients::identity::DeviceType; +use grpc_clients::identity::{get_auth_client, PlatformMetadata}; use serde::{Deserialize, Serialize}; // 1. register user with android device // 2. register a web device // 3. remove android device // 4. register ios device // 5. get device list - should have 4 updates: // - [android] // - [android, web] // - [web] // - [ios, web] - mobile should be first #[tokio::test] async fn test_device_list_rotation() { use commtest::identity::olm_account_infos::{ DEFAULT_CLIENT_KEYS as DEVICE_KEYS_ANDROID, MOCK_CLIENT_KEYS_1 as DEVICE_KEYS_WEB, MOCK_CLIENT_KEYS_2 as DEVICE_KEYS_IOS, }; // Create viewer (user that doesn't change devices) let viewer = register_user_device(None, None).await; let mut auth_client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), viewer.user_id.clone(), viewer.device_id, viewer.access_token, - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let android_device_id = &DEVICE_KEYS_ANDROID.primary_identity_public_keys.ed25519; let web_device_id = &DEVICE_KEYS_WEB.primary_identity_public_keys.ed25519; let ios_device_id = &DEVICE_KEYS_IOS.primary_identity_public_keys.ed25519; // 1. Register user with primary Android device let android = register_user_device(Some(&DEVICE_KEYS_ANDROID), Some(DeviceType::Android)) .await; let user_id = android.user_id.clone(); let username = android.username.clone(); // 2. Log in a web device let _web = login_user_device( &username, Some(&DEVICE_KEYS_WEB), Some(DeviceType::Web), false, ) .await; // 3. Remove android device logout_user_device(android).await; // 4. Log in an iOS device let _ios = login_user_device( &username, Some(&DEVICE_KEYS_IOS), Some(DeviceType::Ios), false, ) .await; // Get device list updates for the user let device_lists_response: Vec> = get_raw_device_list_history(&mut auth_client, &user_id) .await .into_iter() .map(|device_list| device_list.devices) .collect(); let expected_device_list: Vec> = vec![ vec![android_device_id.into()], vec![android_device_id.into(), web_device_id.into()], vec![web_device_id.into()], vec![ios_device_id.into(), web_device_id.into()], ]; assert_eq!(device_lists_response, expected_device_list); } #[tokio::test] async fn test_update_device_list_rpc() { // Register user with primary device let primary_device = register_user_device(None, None).await; let mut auth_client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), primary_device.user_id.clone(), primary_device.device_id, primary_device.access_token, - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); // Initial device list check let initial_device_list = get_raw_device_list_history(&mut auth_client, &primary_device.user_id) .await .into_iter() .map(|device_list| device_list.devices) .next() .expect("Expected to get single device list update"); assert!(initial_device_list.len() == 1, "Expected single device"); let primary_device_id = initial_device_list[0].clone(); // perform update by adding a new device let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); let devices_payload = vec![primary_device_id, "device2".to_string()]; let update_payload = SignedDeviceList::from_raw_unsigned(&RawDeviceList { devices: devices_payload.clone(), timestamp: now.as_millis() as i64, }); let update_request = UpdateDeviceListRequest::from(&update_payload); auth_client .update_device_list(update_request) .await .expect("Update device list RPC failed"); // get device list again let last_device_list = get_raw_device_list_history(&mut auth_client, &primary_device.user_id) .await; let last_device_list = last_device_list .last() .expect("Failed to get last device list update"); // check that the device list is properly updated assert_eq!(last_device_list.devices, devices_payload); assert_eq!(last_device_list.timestamp, now.as_millis() as i64); } #[tokio::test] async fn test_device_list_signatures() { // device list history as list of tuples: (signature, devices) type DeviceListHistoryItem = (Option, Vec); // Register user with primary device let mut primary_account = SigningCapableAccount::new(); let primary_device_keys = primary_account.public_keys(); let primary_device_id = primary_device_keys.device_id(); let user = register_user_device(Some(&primary_device_keys), Some(DeviceType::Ios)) .await; let mut auth_client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), user.user_id.clone(), user.device_id, user.access_token, - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); // Perform unsigned update (add a new device) let first_update: DeviceListHistoryItem = { let update_payload = SignedDeviceList::from_raw_unsigned(&RawDeviceList::new(vec![ primary_device_id.clone(), "device2".to_string(), ])); let update_request = UpdateDeviceListRequest::from(&update_payload); auth_client .update_device_list(update_request) .await .expect("Unsigned device list update failed"); ( update_payload.cur_primary_signature.clone(), update_payload.into_raw().devices, ) }; // now perform a update (remove a device), but sign the device list let second_update: DeviceListHistoryItem = { let update_payload = SignedDeviceList::create_signed( &RawDeviceList::new(vec![primary_device_id.clone()]), &mut primary_account, None, ); let update_request = UpdateDeviceListRequest::from(&update_payload); auth_client .update_device_list(update_request) .await .expect("Signed device list update failed"); ( update_payload.cur_primary_signature.clone(), update_payload.into_raw().devices, ) }; // now perform a signed update (add a device), but with invalid signature { let mut update_payload = SignedDeviceList::create_signed( &RawDeviceList::new(vec![ primary_device_id.clone(), "device3".to_string(), ]), &mut primary_account, None, ); // malfolm signature by replacing first characters update_payload .cur_primary_signature .as_mut() .expect("signature should be present") .replace_range(0..3, "foo"); let update_request = UpdateDeviceListRequest::from(&update_payload); auth_client .update_device_list(update_request) .await .expect_err("RPC should fail for invalid signature"); } // check the history to make sure our updates are correct let device_list_history = get_device_list_history(&mut auth_client, &user.user_id).await; let expected_devices_lists: Vec = vec![ (None, vec![primary_device_id.clone()]), // auto-generated during registration first_update, second_update, ]; let actual_device_lists: Vec = device_list_history .into_iter() .map(|list| (list.cur_primary_signature.clone(), list.into_raw().devices)) .collect(); assert_eq!(actual_device_lists, expected_devices_lists); } #[tokio::test] async fn test_keyserver_force_login() { use commtest::identity::olm_account_infos::{ DEFAULT_CLIENT_KEYS as DEVICE_KEYS_ANDROID, MOCK_CLIENT_KEYS_1 as DEVICE_KEYS_KEYSERVER_1, MOCK_CLIENT_KEYS_2 as DEVICE_KEYS_KEYSERVER_2, }; // Create viewer (user that doesn't change devices) let viewer = register_user_device(None, None).await; let mut auth_client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), viewer.user_id.clone(), viewer.device_id, viewer.access_token, - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let android_device_id = &DEVICE_KEYS_ANDROID.primary_identity_public_keys.ed25519; let keyserver_1_device_id = &DEVICE_KEYS_KEYSERVER_1.primary_identity_public_keys.ed25519; let keyserver_2_device_id = &DEVICE_KEYS_KEYSERVER_2.primary_identity_public_keys.ed25519; // 1. Register user with primary Android device let android = register_user_device(Some(&DEVICE_KEYS_ANDROID), Some(DeviceType::Android)) .await; let user_id = android.user_id.clone(); let username = android.username.clone(); // 2. Log in on keyserver 1 let _keyserver_1 = login_user_device( &username, Some(&DEVICE_KEYS_KEYSERVER_1), Some(DeviceType::Keyserver), false, ) .await; // 3. Log in on keyserver 2 with force = true let _keyserver_2 = login_user_device( &username, Some(&DEVICE_KEYS_KEYSERVER_2), Some(DeviceType::Keyserver), true, ) .await; // Get device list updates for the user let device_lists_response: Vec> = get_raw_device_list_history(&mut auth_client, &user_id) .await .into_iter() .map(|device_list| device_list.devices) .collect(); let expected_device_list: Vec> = vec![ vec![android_device_id.into()], vec![android_device_id.into(), keyserver_1_device_id.into()], vec![android_device_id.into()], vec![android_device_id.into(), keyserver_2_device_id.into()], ]; assert_eq!(device_lists_response, expected_device_list); } #[tokio::test] async fn test_device_list_multifetch() { // Create viewer (user that only auths request) let viewer = register_user_device(None, None).await; let mut auth_client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), viewer.user_id.clone(), viewer.device_id, viewer.access_token, - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); // Register users and prepare expected device lists let mut expected_device_lists = HashMap::new(); for _ in 0..5 { let user = register_user_device(None, None).await; expected_device_lists.insert(user.user_id, vec![user.device_id]); } // Fetch device lists from server let user_ids: Vec<_> = expected_device_lists.keys().cloned().collect(); let request = PeersDeviceListsRequest { user_ids }; let response_device_lists = auth_client .get_device_lists_for_users(request) .await .expect("GetDeviceListsForUser RPC failed") .into_inner() .users_device_lists; // verify if response has the same user IDs as request let expected_user_ids: HashSet = expected_device_lists.keys().cloned().collect(); let response_user_ids: HashSet = response_device_lists.keys().cloned().collect(); let difference: HashSet<_> = expected_user_ids .symmetric_difference(&response_user_ids) .collect(); assert!(difference.is_empty(), "User IDs differ: {:?}", difference); // verify device list for each user for (user_id, expected_devices) in expected_device_lists { let response_payload = response_device_lists.get(&user_id).unwrap(); let returned_devices = SignedDeviceList::from_str(response_payload) .expect("failed to deserialize signed device list") .into_raw() .devices; assert_eq!( returned_devices, expected_devices, "Device list differs for user: {}, Expected {:?}, but got {:?}", user_id, expected_devices, returned_devices ); } } #[tokio::test] async fn test_initial_device_list() { // create signing account let mut primary_account = SigningCapableAccount::new(); let primary_device_keys = primary_account.public_keys(); let primary_device_id = primary_device_keys.device_id(); // create initial device list let raw_device_list = RawDeviceList::new(vec![primary_device_id]); let signed_list = SignedDeviceList::create_signed( &raw_device_list, &mut primary_account, None, ); // register user with initial list let user = register_user_device_with_device_list( Some(&primary_device_keys), Some(DeviceType::Ios), Some(signed_list.as_json_string()), ) .await; let mut auth_client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), user.user_id.clone(), user.device_id, user.access_token, - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let mut history = get_device_list_history(&mut auth_client, &user.user_id).await; let received_list = history.pop().expect("Received empty device list history"); assert!( history.is_empty(), "Device list history should have no more updates" ); assert_eq!( received_list.cur_primary_signature, signed_list.cur_primary_signature, "Signature mismatch" ); assert!(received_list.last_primary_signature.is_none()); assert_eq!(received_list.into_raw(), raw_device_list); } // See GetDeviceListResponse in identity_authenticated.proto // for details on the response format. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] #[allow(unused)] struct RawDeviceList { devices: Vec, timestamp: i64, } #[derive(Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] struct SignedDeviceList { raw_device_list: String, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] cur_primary_signature: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] last_primary_signature: Option, } impl RawDeviceList { fn new(devices: Vec) -> Self { let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); RawDeviceList { devices, timestamp: now.as_millis() as i64, } } fn as_json_string(&self) -> String { serde_json::to_string(self).expect("Failed to serialize RawDeviceList") } } impl SignedDeviceList { fn from_raw_unsigned(raw: &RawDeviceList) -> Self { Self { raw_device_list: raw.as_json_string(), cur_primary_signature: None, last_primary_signature: None, } } fn create_signed( raw: &RawDeviceList, cur_primary_account: &mut SigningCapableAccount, last_primary_account: Option<&mut SigningCapableAccount>, ) -> Self { let raw_device_list = raw.as_json_string(); let cur_primary_signature = cur_primary_account.sign_message(&raw_device_list); let last_primary_signature = last_primary_account .map(|account| account.sign_message(&raw_device_list)); Self { raw_device_list, cur_primary_signature: Some(cur_primary_signature), last_primary_signature, } } fn into_raw(self) -> RawDeviceList { self .raw_device_list .parse() .expect("Failed to parse raw device list") } fn as_json_string(&self) -> String { serde_json::to_string(self).expect("Failed to serialize SignedDeviceList") } } impl FromStr for SignedDeviceList { type Err = serde_json::Error; fn from_str(s: &str) -> Result { serde_json::from_str(s) } } impl FromStr for RawDeviceList { type Err = serde_json::Error; fn from_str(s: &str) -> Result { // The device list payload is sent as an escaped JSON payload. // Escaped double quotes need to be trimmed before attempting to deserialize serde_json::from_str(&s.replace(r#"\""#, r#"""#)) } } impl From<&SignedDeviceList> for UpdateDeviceListRequest { fn from(value: &SignedDeviceList) -> Self { Self { new_device_list: value.as_json_string(), } } } async fn get_device_list_history( client: &mut ChainedInterceptedAuthClient, user_id: &str, ) -> Vec { let request = GetDeviceListRequest { user_id: user_id.to_string(), since_timestamp: None, }; let response = client .get_device_list_for_user(request) .await .expect("Get device list request failed") .into_inner(); response .device_list_updates .into_iter() .map(|update| { SignedDeviceList::from_str(&update) .expect("Failed to parse device list update") }) .collect() } async fn get_raw_device_list_history( client: &mut ChainedInterceptedAuthClient, user_id: &str, ) -> Vec { get_device_list_history(client, user_id) .await .into_iter() .map(|signed| signed.into_raw()) .collect() } diff --git a/services/commtest/tests/identity_integration_tests.rs b/services/commtest/tests/identity_integration_tests.rs index f195c644f..0ef8185b1 100644 --- a/services/commtest/tests/identity_integration_tests.rs +++ b/services/commtest/tests/identity_integration_tests.rs @@ -1,76 +1,75 @@ use commtest::identity::device::{ register_user_device, DEVICE_TYPE, PLACEHOLDER_CODE_VERSION, }; use commtest::service_addr; +use grpc_clients::identity::PlatformMetadata; use grpc_clients::identity::{ get_auth_client, get_unauthenticated_client, protos::auth::{Identity, UserIdentitiesRequest}, protos::unauthenticated::{ find_user_id_request::Identifier, FindUserIdRequest, }, }; #[tokio::test] async fn find_user_id_by_username() { let device_info = register_user_device(None, None).await; let mut client = get_unauthenticated_client( &service_addr::IDENTITY_GRPC.to_string(), - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let request = FindUserIdRequest { identifier: Some(Identifier::Username(device_info.username)), }; let response = client .find_user_id(request) .await .expect("Request failed") .into_inner(); assert_eq!( response.user_id, Some(device_info.user_id), "User ID should match" ); } #[tokio::test] async fn find_username_for_user() { let device_info = register_user_device(None, None).await; let mut client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), device_info.user_id.clone(), device_info.device_id, device_info.access_token, - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let request = UserIdentitiesRequest { user_ids: vec![device_info.user_id.clone()], }; let response = client .find_user_identities(request) .await .expect("request failed") .into_inner(); let expected_username = device_info.username; assert!( matches!( response.identities.get(&device_info.user_id), Some(Identity { username, .. }) if *username == expected_username ), "username doesn't match" ); } diff --git a/services/commtest/tests/identity_keyserver_tests.rs b/services/commtest/tests/identity_keyserver_tests.rs index ee515e36f..3d21404a6 100644 --- a/services/commtest/tests/identity_keyserver_tests.rs +++ b/services/commtest/tests/identity_keyserver_tests.rs @@ -1,76 +1,76 @@ use commtest::identity::device::{ register_user_device, DEVICE_TYPE, PLACEHOLDER_CODE_VERSION, }; use commtest::identity::olm_account_infos::generate_random_olm_key; use commtest::service_addr; +use grpc_clients::identity::PlatformMetadata; use grpc_clients::identity::{ get_auth_client, protos::authenticated::{ OutboundKeysForUserRequest, UploadOneTimeKeysRequest, }, }; #[tokio::test] async fn set_prekey() { let identity_grpc_endpoint = service_addr::IDENTITY_GRPC.to_string(); let device_info = register_user_device(None, None).await; let mut client = get_auth_client( &identity_grpc_endpoint, device_info.user_id.clone(), device_info.device_id, device_info.access_token, - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let content_one_time_prekey = generate_random_olm_key(); let notif_one_time_prekey = generate_random_olm_key(); let upload_request = UploadOneTimeKeysRequest { content_one_time_prekeys: vec![content_one_time_prekey.clone()], notif_one_time_prekeys: vec![notif_one_time_prekey.clone()], }; client .upload_one_time_keys(upload_request) .await .expect("Failed to upload keys"); // Currently allowed to request your own outbound keys let keyserver_request = OutboundKeysForUserRequest { user_id: device_info.user_id.clone(), }; println!("Getting keyserver info for user, {}", device_info.user_id); let first_reponse = client .get_keyserver_keys(keyserver_request.clone()) .await .expect("First keyserver keys request failed") .into_inner() .keyserver_info .unwrap(); assert_eq!( first_reponse.one_time_content_prekey, Some(content_one_time_prekey) ); assert_eq!( first_reponse.one_time_notif_prekey, Some(notif_one_time_prekey) ); let second_reponse = client .get_keyserver_keys(keyserver_request) .await .expect("Second keyserver keys request failed") .into_inner() .keyserver_info .unwrap(); // The one time keys should be exhausted assert_eq!(second_reponse.one_time_content_prekey, None); assert_eq!(second_reponse.one_time_notif_prekey, None); } diff --git a/services/commtest/tests/identity_one_time_key_tests.rs b/services/commtest/tests/identity_one_time_key_tests.rs index 9857f82b4..1ccd580af 100644 --- a/services/commtest/tests/identity_one_time_key_tests.rs +++ b/services/commtest/tests/identity_one_time_key_tests.rs @@ -1,189 +1,187 @@ use commtest::identity::device::{ register_user_device, DEVICE_TYPE, PLACEHOLDER_CODE_VERSION, }; use commtest::identity::olm_account_infos::generate_random_olm_key; use commtest::service_addr; +use grpc_clients::identity::PlatformMetadata; use grpc_clients::identity::{ get_auth_client, protos::authenticated::OutboundKeysForUserRequest, protos::authenticated::UploadOneTimeKeysRequest, }; #[tokio::test] async fn upload_one_time_keys() { let device_info = register_user_device(None, None).await; let mut identity_client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), device_info.user_id, device_info.device_id, device_info.access_token, - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let upload_request = UploadOneTimeKeysRequest { content_one_time_prekeys: vec![ generate_random_olm_key(), generate_random_olm_key(), ], notif_one_time_prekeys: vec![ generate_random_olm_key(), generate_random_olm_key(), ], }; identity_client .upload_one_time_keys(upload_request) .await .unwrap(); } #[tokio::test] async fn max_hundred_keys_in_ddb() { let device_info = register_user_device(None, None).await; let mut identity_client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), device_info.user_id.clone(), device_info.device_id, device_info.access_token, - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); // We expect these keys to be removed by the identity service before we // retrieve any OTKs let first_upload_request = UploadOneTimeKeysRequest { content_one_time_prekeys: vec![generate_random_olm_key()], notif_one_time_prekeys: vec![generate_random_olm_key()], }; identity_client .upload_one_time_keys(first_upload_request) .await .unwrap(); let mut expected_first_retrieved_content_key = None; let mut expected_first_retrieved_notif_key = None; let mut expected_second_retrieved_content_key = None; let mut expected_second_retrieved_notif_key = None; // Upload 100 content and notif one-time keys in batches of 20 keys for request_num in 0..5 { let content_keys: Vec<_> = (0..20).map(|_| generate_random_olm_key()).collect(); let notif_keys: Vec<_> = (0..20).map(|_| generate_random_olm_key()).collect(); if request_num == 0 { expected_first_retrieved_content_key = content_keys.first().cloned(); expected_first_retrieved_notif_key = notif_keys.first().cloned(); expected_second_retrieved_content_key = content_keys.get(5).cloned(); expected_second_retrieved_notif_key = notif_keys.get(5).cloned(); } let upload_request = UploadOneTimeKeysRequest { content_one_time_prekeys: content_keys, notif_one_time_prekeys: notif_keys, }; identity_client .upload_one_time_keys(upload_request) .await .unwrap(); } let keyserver_request = OutboundKeysForUserRequest { user_id: device_info.user_id, }; let first_keyserver_response = identity_client .get_keyserver_keys(keyserver_request.clone()) .await .unwrap() .into_inner() .keyserver_info .unwrap(); assert!(first_keyserver_response.one_time_content_prekey.is_some()); assert!(first_keyserver_response.one_time_notif_prekey.is_some()); assert_eq!( expected_first_retrieved_content_key, first_keyserver_response.one_time_content_prekey ); assert_eq!( expected_first_retrieved_notif_key, first_keyserver_response.one_time_notif_prekey ); // Upload 5 more keys for each account let content_keys: Vec<_> = (0..5).map(|_| generate_random_olm_key()).collect(); let notif_keys: Vec<_> = (0..5).map(|_| generate_random_olm_key()).collect(); let final_upload_request = UploadOneTimeKeysRequest { content_one_time_prekeys: content_keys, notif_one_time_prekeys: notif_keys, }; identity_client .upload_one_time_keys(final_upload_request) .await .unwrap(); let second_keyserver_response = identity_client .get_keyserver_keys(keyserver_request) .await .unwrap() .into_inner() .keyserver_info .unwrap(); assert!(second_keyserver_response.one_time_content_prekey.is_some()); assert!(second_keyserver_response.one_time_notif_prekey.is_some()); assert_eq!( expected_second_retrieved_content_key, second_keyserver_response.one_time_content_prekey ); assert_eq!( expected_second_retrieved_notif_key, second_keyserver_response.one_time_notif_prekey ); } #[tokio::test] async fn max_24_keys_per_account_per_upload() { let device_info = register_user_device(None, None).await; let mut identity_client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), device_info.user_id, device_info.device_id, device_info.access_token, - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); // The limit is 24 keys per account per upload, so this should fail let content_keys = (0..26).map(|_| generate_random_olm_key()).collect(); let notif_keys = (0..20).map(|_| generate_random_olm_key()).collect(); let upload_request = UploadOneTimeKeysRequest { content_one_time_prekeys: content_keys, notif_one_time_prekeys: notif_keys, }; assert!(identity_client .upload_one_time_keys(upload_request) .await .is_err()); } diff --git a/services/commtest/tests/identity_prekey_tests.rs b/services/commtest/tests/identity_prekey_tests.rs index 5cc2dda7a..7eef450a3 100644 --- a/services/commtest/tests/identity_prekey_tests.rs +++ b/services/commtest/tests/identity_prekey_tests.rs @@ -1,41 +1,41 @@ use commtest::identity::device::{ register_user_device, DEVICE_TYPE, PLACEHOLDER_CODE_VERSION, }; use commtest::service_addr; +use grpc_clients::identity::PlatformMetadata; use grpc_clients::identity::{ get_auth_client, protos::{authenticated::RefreshUserPrekeysRequest, unauth::Prekey}, }; #[tokio::test] async fn set_prekey() { let device_info = register_user_device(None, None).await; let mut client = get_auth_client( &service_addr::IDENTITY_GRPC.to_string(), device_info.user_id, device_info.device_id, device_info.access_token, - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let upload_request = RefreshUserPrekeysRequest { new_content_prekeys: Some(Prekey { prekey: "content_prekey".to_string(), prekey_signature: "content_prekey_signature".to_string(), }), new_notif_prekeys: Some(Prekey { prekey: "content_prekey".to_string(), prekey_signature: "content_prekey_signature".to_string(), }), }; // This send will fail if the one-time keys weren't successfully added println!( "Error: {:?}", client.refresh_user_prekeys(upload_request).await ); } diff --git a/services/commtest/tests/identity_tunnelbroker_tests.rs b/services/commtest/tests/identity_tunnelbroker_tests.rs index be304a33b..2e0f6e5bb 100644 --- a/services/commtest/tests/identity_tunnelbroker_tests.rs +++ b/services/commtest/tests/identity_tunnelbroker_tests.rs @@ -1,91 +1,90 @@ use commtest::identity::device::{ register_user_device, DEVICE_TYPE, PLACEHOLDER_CODE_VERSION, }; use commtest::identity::olm_account_infos::generate_random_olm_key; use commtest::service_addr; use commtest::tunnelbroker::socket::{create_socket, receive_message}; use futures_util::StreamExt; -use grpc_clients::identity::get_auth_client; use grpc_clients::identity::protos::authenticated::{ OutboundKeysForUserRequest, UploadOneTimeKeysRequest, }; +use grpc_clients::identity::{get_auth_client, PlatformMetadata}; use tunnelbroker_messages::RefreshKeyRequest; #[tokio::test] async fn test_tunnelbroker_invalid_auth() { let mut device_info = register_user_device(None, None).await; device_info.access_token = "".to_string(); let socket = create_socket(&device_info).await; assert!(matches!(socket, Result::Err(_))) } #[tokio::test] async fn test_tunnelbroker_valid_auth() { let device_info = register_user_device(None, None).await; let mut socket = create_socket(&device_info).await.unwrap(); socket .next() .await .expect("Failed to receive response") .expect("Failed to read the response"); } #[tokio::test] async fn test_refresh_keys_request_upon_depletion() { let identity_grpc_endpoint = service_addr::IDENTITY_GRPC.to_string(); let device_info = register_user_device(None, None).await; // Request outbound keys, which should trigger identity service to ask for more keys let mut client = get_auth_client( &identity_grpc_endpoint, device_info.user_id.clone(), device_info.device_id, device_info.access_token, - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await .expect("Couldn't connect to identity service"); let content_one_time_prekeys = vec![generate_random_olm_key()]; let notif_one_time_prekeys = vec![generate_random_olm_key()]; let upload_request = UploadOneTimeKeysRequest { content_one_time_prekeys, notif_one_time_prekeys, }; client.upload_one_time_keys(upload_request).await.unwrap(); let keyserver_request = OutboundKeysForUserRequest { user_id: device_info.user_id.clone(), }; println!("Getting keyserver info for user, {}", device_info.user_id); let _first_reponse = client .get_keyserver_keys(keyserver_request.clone()) .await .expect("keyserver keys request failed") .into_inner() .keyserver_info .unwrap(); // The current threshold is 5, but we only upload two. Should receive request // from Tunnelbroker to refresh keys // Create session as a keyserver let device_info = register_user_device(None, None).await; let mut socket = create_socket(&device_info).await.unwrap(); for _ in 0..2 { let response = receive_message(&mut socket).await.unwrap(); let serialized_response: RefreshKeyRequest = serde_json::from_str(&response).unwrap(); let expected_response = RefreshKeyRequest { device_id: device_info.device_id.to_string(), number_of_keys: 5, }; assert_eq!(serialized_response, expected_response); } } diff --git a/services/identity/src/websockets/auth.rs b/services/identity/src/websockets/auth.rs index 6f820210f..3fb6c3ea8 100644 --- a/services/identity/src/websockets/auth.rs +++ b/services/identity/src/websockets/auth.rs @@ -1,90 +1,89 @@ use client_proto::VerifyUserAccessTokenRequest; -use grpc_clients::identity; +use grpc_clients::identity::{self, PlatformMetadata}; use grpc_clients::tonic::Request; use identity::get_unauthenticated_client; use identity::protos::unauthenticated as client_proto; use identity_search_messages::IdentitySearchAuthMessage; use tracing::{debug, error}; use crate::constants::{error_types, DEFAULT_IDENTITY_ENDPOINT}; use crate::websockets::errors::WebsocketError; const PLACEHOLDER_CODE_VERSION: u64 = 0; const DEVICE_TYPE: &str = "service"; #[tracing::instrument(skip_all)] async fn verify_user_access_token( user_id: &str, device_id: &str, access_token: &str, ) -> Result { let grpc_client = get_unauthenticated_client( DEFAULT_IDENTITY_ENDPOINT, - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await; let mut grpc_client = match grpc_client { Ok(grpc_client) => grpc_client, Err(e) => { error!( errorType = error_types::SEARCH_LOG, "Failed to get unauthenticated client: {}", e ); return Err(WebsocketError::AuthError); } }; let message = VerifyUserAccessTokenRequest { user_id: user_id.to_string(), device_id: device_id.to_string(), access_token: access_token.to_string(), }; let request = Request::new(message); let response = match grpc_client.verify_user_access_token(request).await { Ok(response) => response, Err(_) => { error!( errorType = error_types::SEARCH_LOG, "Failed to verify user access token" ); return Err(WebsocketError::AuthError); } }; Ok(response.into_inner().token_valid) } #[tracing::instrument(skip_all)] pub async fn handle_auth_message(message: &str) -> Result<(), WebsocketError> { let auth_message = serde_json::from_str(message.trim()); let auth_message: IdentitySearchAuthMessage = match auth_message { Ok(auth_message) => auth_message, Err(_) => { error!( errorType = error_types::SEARCH_LOG, "Failed to parse auth message" ); return Err(WebsocketError::InvalidMessage); } }; let user_id = auth_message.user_id; let device_id = auth_message.device_id; let access_token = auth_message.access_token; let is_valid_token = verify_user_access_token(&user_id, &device_id, &access_token).await?; if is_valid_token { debug!("User {} authenticated", user_id); } else { debug!("User {} not authenticated", user_id); return Err(WebsocketError::UnauthorizedDevice); } Ok(()) } diff --git a/services/tunnelbroker/src/identity/mod.rs b/services/tunnelbroker/src/identity/mod.rs index cde5d0adc..d60736307 100644 --- a/services/tunnelbroker/src/identity/mod.rs +++ b/services/tunnelbroker/src/identity/mod.rs @@ -1,37 +1,36 @@ use client_proto::VerifyUserAccessTokenRequest; -use grpc_clients::identity; +use grpc_clients::identity::{self, PlatformMetadata}; use grpc_clients::tonic::Request; use identity::get_unauthenticated_client; use identity::protos::unauthenticated as client_proto; use crate::config::CONFIG; use crate::error::Error; // Identity service gRPC clients require a code version and device type. // We can supply some placeholder values for services for the time being, since // this metadata is only relevant for devices. const PLACEHOLDER_CODE_VERSION: u64 = 0; const DEVICE_TYPE: &str = "service"; /// Returns true if access token is valid pub async fn verify_user_access_token( user_id: &str, device_id: &str, access_token: &str, ) -> Result { let mut grpc_client = get_unauthenticated_client( &CONFIG.identity_endpoint, - PLACEHOLDER_CODE_VERSION, - DEVICE_TYPE.to_string(), + PlatformMetadata::new(PLACEHOLDER_CODE_VERSION, DEVICE_TYPE), ) .await?; let message = VerifyUserAccessTokenRequest { user_id: user_id.to_string(), device_id: device_id.to_string(), access_token: access_token.to_string(), }; let request = Request::new(message); let response = grpc_client.verify_user_access_token(request).await?; Ok(response.into_inner().token_valid) } diff --git a/shared/grpc_clients/src/identity/authenticated.rs b/shared/grpc_clients/src/identity/authenticated.rs index d81a7610c..d907fad5c 100644 --- a/shared/grpc_clients/src/identity/authenticated.rs +++ b/shared/grpc_clients/src/identity/authenticated.rs @@ -1,76 +1,73 @@ -use super::protos::authenticated::identity_client_service_client::IdentityClientServiceClient as AuthClient; +use super::{ + protos::authenticated::identity_client_service_client::IdentityClientServiceClient as AuthClient, + PlatformMetadata, +}; use tonic::{ codegen::InterceptedService, metadata::{errors::InvalidMetadataValue, Ascii, MetadataValue}, service::Interceptor, transport::Channel, Request, Status, }; use crate::identity::shared::{ChainedInterceptor, ToMetadataValueAscii}; use crate::{error::Error, identity::shared::CodeVersionLayer}; pub struct AuthLayer { user_id: String, device_id: String, access_token: String, } impl ToMetadataValueAscii for str { fn parse_to_ascii(&self) -> Result, Status> { self.parse().map_err(|e: InvalidMetadataValue| { Status::invalid_argument(format!( "Non-Ascii character present in metadata value: {}", e )) }) } } impl Interceptor for AuthLayer { fn call(&mut self, mut request: Request<()>) -> Result, Status> { let metadata = request.metadata_mut(); metadata.insert("user_id", self.user_id.parse_to_ascii()?); metadata.insert("device_id", self.device_id.parse_to_ascii()?); metadata.insert("access_token", self.access_token.parse_to_ascii()?); Ok(request) } } pub type ChainedInterceptedAuthClient = AuthClient< InterceptedService>, >; pub async fn get_auth_client( url: &str, user_id: String, device_id: String, access_token: String, - code_version: u64, - device_type: String, + platform_metadata: PlatformMetadata, ) -> Result { use crate::get_grpc_service_channel; let channel = get_grpc_service_channel(url).await?; let auth_interceptor = AuthLayer { user_id, device_id, access_token, }; - let version_interceptor = CodeVersionLayer { - device_type, - code_version, - state_version: None, - major_desktop_version: None, - }; + let version_interceptor = CodeVersionLayer::from(platform_metadata); let chained = ChainedInterceptor { first: auth_interceptor, second: version_interceptor, }; Ok(AuthClient::with_interceptor(channel, chained)) } diff --git a/shared/grpc_clients/src/identity/mod.rs b/shared/grpc_clients/src/identity/mod.rs index a7eea3568..e78ed07f7 100644 --- a/shared/grpc_clients/src/identity/mod.rs +++ b/shared/grpc_clients/src/identity/mod.rs @@ -1,20 +1,21 @@ pub mod authenticated; pub mod device; pub mod shared; pub mod unauthenticated; pub mod protos { // This must be named unauth for authenticated generated code pub mod unauth { tonic::include_proto!("identity.unauth"); } pub mod auth { tonic::include_proto!("identity.auth"); } pub use auth as authenticated; pub use unauth as unauthenticated; } pub use authenticated::get_auth_client; pub use device::DeviceType; +pub use shared::PlatformMetadata; pub use unauthenticated::get_unauthenticated_client; diff --git a/shared/grpc_clients/src/identity/shared.rs b/shared/grpc_clients/src/identity/shared.rs index a86a45cd2..84d8649b0 100644 --- a/shared/grpc_clients/src/identity/shared.rs +++ b/shared/grpc_clients/src/identity/shared.rs @@ -1,98 +1,98 @@ use tonic::{ metadata::{errors::InvalidMetadataValue, Ascii, MetadataValue}, service::Interceptor, Request, Status, }; #[derive(Clone, Debug)] pub struct PlatformMetadata { pub device_type: String, pub code_version: u64, pub state_version: Option, pub major_desktop_version: Option, } pub struct CodeVersionLayer { pub(crate) code_version: u64, pub(crate) device_type: String, pub(crate) state_version: Option, pub(crate) major_desktop_version: Option, } impl PlatformMetadata { /// Simplified constructor for basic params only - fn new(code_version: u64, device_type: impl Into) -> Self { + pub fn new(code_version: u64, device_type: impl Into) -> Self { Self { code_version, device_type: device_type.into(), state_version: None, major_desktop_version: None, } } } impl From for CodeVersionLayer { fn from(value: PlatformMetadata) -> Self { Self { code_version: value.code_version, device_type: value.device_type, state_version: value.state_version, major_desktop_version: value.major_desktop_version, } } } impl Interceptor for CodeVersionLayer { fn call(&mut self, mut request: Request<()>) -> Result, Status> { let metadata = request.metadata_mut(); metadata.insert("code_version", self.code_version.parse_to_ascii()?); metadata.insert("device_type", self.device_type.parse_to_ascii()?); if let Some(state_version) = self.state_version { metadata.insert("state_version", state_version.parse_to_ascii()?); } if let Some(desktop_version) = self.major_desktop_version { metadata .insert("major_desktop_version", desktop_version.parse_to_ascii()?); } Ok(request) } } pub trait ToMetadataValueAscii { fn parse_to_ascii(&self) -> Result, Status>; } impl ToMetadataValueAscii for u64 { fn parse_to_ascii(&self) -> Result, Status> { let ascii_string = self.to_string(); ascii_string.parse().map_err(|e: InvalidMetadataValue| { Status::invalid_argument(format!( "Non-Ascii character present in metadata value: {}", e )) }) } } pub struct ChainedInterceptor where A: Interceptor + Send + Sync + 'static, B: Interceptor + Send + Sync + 'static, { pub(crate) first: A, pub(crate) second: B, } impl Interceptor for ChainedInterceptor where A: Interceptor + Send + Sync + 'static, B: Interceptor + Send + Sync + 'static, { fn call(&mut self, request: Request<()>) -> Result, Status> { let request = self.first.call(request)?; self.second.call(request) } } diff --git a/shared/grpc_clients/src/identity/unauthenticated/client.rs b/shared/grpc_clients/src/identity/unauthenticated/client.rs index 437cce030..c3c8a913a 100644 --- a/shared/grpc_clients/src/identity/unauthenticated/client.rs +++ b/shared/grpc_clients/src/identity/unauthenticated/client.rs @@ -1,44 +1,50 @@ /// This file is meant to contain commonly used RPCs -use crate::error::Error; +use crate::{error::Error, identity::PlatformMetadata}; use super::get_unauthenticated_client; use crate::identity::protos::unauthenticated::{ Empty, VerifyUserAccessTokenRequest, }; use tonic::Request; /// Returns true if access token is valid pub async fn verify_user_access_token( identity_url: &str, user_id: &str, device_id: &str, access_token: &str, code_version: u64, device_type: String, ) -> Result { - let mut grpc_client = - get_unauthenticated_client(identity_url, code_version, device_type).await?; + let mut grpc_client = get_unauthenticated_client( + identity_url, + PlatformMetadata::new(code_version, device_type), + ) + .await?; let message = VerifyUserAccessTokenRequest { user_id: user_id.to_string(), device_id: device_id.to_string(), access_token: access_token.to_string(), }; let request = Request::new(message); let response = grpc_client.verify_user_access_token(request).await?; Ok(response.into_inner().token_valid) } pub async fn ping( identity_url: &str, code_version: u64, device_type: String, ) -> Result<(), Error> { - let mut grpc_client = - get_unauthenticated_client(identity_url, code_version, device_type).await?; + let mut grpc_client = get_unauthenticated_client( + identity_url, + PlatformMetadata::new(code_version, device_type), + ) + .await?; let request = Request::new(Empty {}); grpc_client.ping(request).await?; Ok(()) } diff --git a/shared/grpc_clients/src/identity/unauthenticated/mod.rs b/shared/grpc_clients/src/identity/unauthenticated/mod.rs index ee5f181b6..87f0f583b 100644 --- a/shared/grpc_clients/src/identity/unauthenticated/mod.rs +++ b/shared/grpc_clients/src/identity/unauthenticated/mod.rs @@ -1,31 +1,25 @@ pub mod client; use tonic::codegen::InterceptedService; use tonic::transport::Channel; use super::{ protos::unauth::identity_client_service_client::IdentityClientServiceClient, - shared::CodeVersionLayer, + shared::CodeVersionLayer, PlatformMetadata, }; use crate::error::Error; pub async fn get_unauthenticated_client( url: &str, - code_version: u64, - device_type: String, + platform_metadata: PlatformMetadata, ) -> Result< IdentityClientServiceClient>, Error, > { let channel = crate::get_grpc_service_channel(url).await?; - let version_interceptor = CodeVersionLayer { - device_type, - code_version, - state_version: None, - major_desktop_version: None, - }; + let version_interceptor = CodeVersionLayer::from(platform_metadata); Ok(IdentityClientServiceClient::with_interceptor( channel, version_interceptor, )) }