diff --git a/keyserver/addons/rust-node-addon/rust-binding-types.js b/keyserver/addons/rust-node-addon/rust-binding-types.js index 14449861a..d089f3f9d 100644 --- a/keyserver/addons/rust-node-addon/rust-binding-types.js +++ b/keyserver/addons/rust-node-addon/rust-binding-types.js @@ -1,79 +1,86 @@ // @flow import type { SignedIdentityKeysBlob } from 'lib/types/crypto-types.js'; import type { InboundKeyInfoResponse, FarcasterUser, + UserIdentitiesResponse, } from 'lib/types/identity-service-types.js'; import type { IdentityInfo } from '../../src/user/identity.js'; type RustNativeBindingAPI = { +loginUser: ( username: string, password: string, signedIdentityKeysBlob: SignedIdentityKeysBlob, contentPrekey: string, contentPrekeySignature: string, notifPrekey: string, notifPrekeySignature: string, force: ?boolean, ) => Promise, +registerUser: ( username: string, password: string, signedIdentityKeysBlob: SignedIdentityKeysBlob, contentPrekey: string, contentPrekeySignature: string, notifPrekey: string, notifPrekeySignature: string, contentOneTimeKeys: $ReadOnlyArray, notifOneTimeKeys: $ReadOnlyArray, ) => Promise, +addReservedUsernames: (message: string, signature: string) => Promise, +removeReservedUsername: ( message: string, signature: string, ) => Promise, +publishPrekeys: ( userId: string, deviceId: string, accessToken: string, contentPrekey: string, contentPrekeySignature: string, notifPrekey: string, notifPrekeySignature: string, ) => Promise, +uploadOneTimeKeys: ( userId: string, deviceId: string, accessToken: string, contentOneTimePrekeys: $ReadOnlyArray, notifOneTimePrekeys: $ReadOnlyArray, ) => Promise, +getInboundKeysForUserDevice: ( authUserId: string, authDeviceId: string, authAccessToken: string, userId: string, deviceId: string, ) => Promise, +getFarcasterUsers: ( farcasterIds: $ReadOnlyArray, ) => Promise<$ReadOnlyArray>, +generateNonce: () => Promise, +uploadSecondaryDeviceKeysAndLogIn: ( userId: string, nonce: string, nonceSignature: string, signedIdentityKeysBlob: SignedIdentityKeysBlob, contentPrekey: string, contentPrekeySignature: string, notifPrekey: string, notifPrekeySignature: string, contentOneTimeKeys: $ReadOnlyArray, notifOneTimeKeys: $ReadOnlyArray, ) => Promise, + +findUserIdentities: ( + authUserId: string, + authDeviceId: string, + authAccessToken: string, + userIds: $ReadOnlyArray, + ) => Promise, }; export type { RustNativeBindingAPI }; diff --git a/keyserver/addons/rust-node-addon/src/identity_client/find_user_identities.rs b/keyserver/addons/rust-node-addon/src/identity_client/find_user_identities.rs new file mode 100644 index 000000000..718b7016b --- /dev/null +++ b/keyserver/addons/rust-node-addon/src/identity_client/find_user_identities.rs @@ -0,0 +1,99 @@ +use std::collections::HashMap; + +use super::*; + +use grpc_clients::identity::protos::auth::{ + EthereumIdentity as ProtoEthereumIdentity, Identity as ProtoIdentity, + UserIdentitiesRequest, UserIdentitiesResponse as ProtoResponse, +}; +use tracing::debug; + +#[napi] +#[instrument(skip_all)] +pub async fn find_user_identities( + auth_user_id: String, + auth_device_id: String, + auth_access_token: String, + user_ids: Vec, +) -> Result { + // Set up the gRPC client that will be used to talk to the Identity service + let mut identity_client = get_authenticated_identity_client( + auth_user_id, + auth_device_id, + auth_access_token, + ) + .await?; + + let user_identities_request = UserIdentitiesRequest { user_ids }; + + debug!("Sending one time keys to Identity service"); + let response = identity_client + .find_user_identities(user_identities_request) + .await + .map_err(handle_grpc_error)?; + + let user_identities_response = + UserIdentitiesResponse::from(response.into_inner()); + + Ok(user_identities_response) +} + +// This struct should not be altered without also updating +// UserIdentitiesResponse in lib/types/identity-service-types.js +#[napi(object)] +pub struct UserIdentitiesResponse { + pub identities: HashMap, + pub reserved_user_identifiers: HashMap, +} + +// This struct should not be altered without also updating Identity in +// lib/types/identity-service-types.js +#[napi(object)] +pub struct Identity { + pub username: String, + pub eth_identity: Option, + #[napi(js_name = "farcasterID")] + pub farcaster_id: Option, +} + +// This struct should not be altered without also updating EthereumIdentity in +// lib/types/identity-service-types.js +#[napi(object)] +pub struct EthereumIdentity { + pub wallet_address: String, + pub siwe_message: String, + pub siwe_signature: String, +} + +impl From for UserIdentitiesResponse { + fn from(response: ProtoResponse) -> UserIdentitiesResponse { + UserIdentitiesResponse { + identities: response + .identities + .into_iter() + .map(|(key, proto_identity)| (key, Identity::from(proto_identity))) + .collect(), + reserved_user_identifiers: response.reserved_user_identifiers, + } + } +} + +impl From for Identity { + fn from(proto_identity: ProtoIdentity) -> Self { + Identity { + username: proto_identity.username, + eth_identity: proto_identity.eth_identity.map(EthereumIdentity::from), + farcaster_id: proto_identity.farcaster_id, + } + } +} + +impl From for EthereumIdentity { + fn from(proto_ethereum_identity: ProtoEthereumIdentity) -> Self { + EthereumIdentity { + wallet_address: proto_ethereum_identity.wallet_address, + siwe_message: proto_ethereum_identity.siwe_message, + siwe_signature: proto_ethereum_identity.siwe_signature, + } + } +} 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 47752388f..19a9b77bc 100644 --- a/keyserver/addons/rust-node-addon/src/identity_client/mod.rs +++ b/keyserver/addons/rust-node-addon/src/identity_client/mod.rs @@ -1,218 +1,219 @@ pub mod add_reserved_usernames; pub mod config; +mod find_user_identities; 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, 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, 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); } }