diff --git a/keyserver/addons/rust-node-addon/src/identity_client/get_inbound_keys_for_user.rs b/keyserver/addons/rust-node-addon/src/identity_client/get_inbound_keys_for_user.rs --- a/keyserver/addons/rust-node-addon/src/identity_client/get_inbound_keys_for_user.rs +++ b/keyserver/addons/rust-node-addon/src/identity_client/get_inbound_keys_for_user.rs @@ -1,4 +1,6 @@ -use grpc_clients::identity::protos::authenticated::InboundKeysForUserRequest; +use grpc_clients::identity::protos::authenticated::{ + identity::IdentityInfo, EthereumIdentity, Identity, InboundKeysForUserRequest, +}; use super::*; @@ -27,12 +29,38 @@ .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))? .into_inner(); - let inbound_key_info = InboundKeyInfoResponse::try_from( + let device_inbound_key_info = DeviceInboundKeyInfo::try_from( response .devices .remove(&device_id) .ok_or(Error::from_status(Status::GenericFailure))?, )?; - Ok(inbound_key_info) + let (username, wallet_address) = match response.identity { + Some(Identity { + identity_info: Some(IdentityInfo::Username(u)), + }) => (Some(u), None), + Some(Identity { + identity_info: + Some(IdentityInfo::EthIdentity(EthereumIdentity { + wallet_address: w, + .. // We ignore the social proof for now + })), + }) => (None, Some(w)), + _ => (None, None), + }; + + let inbound_key_info_response = InboundKeyInfoResponse { + payload: device_inbound_key_info.payload, + payload_signature: device_inbound_key_info.payload_signature, + social_proof: device_inbound_key_info.social_proof, + content_prekey: device_inbound_key_info.content_prekey, + content_prekey_signature: device_inbound_key_info.content_prekey_signature, + notif_prekey: device_inbound_key_info.notif_prekey, + notif_prekey_signature: device_inbound_key_info.notif_prekey_signature, + username, + wallet_address, + }; + + Ok(inbound_key_info_response) } diff --git a/keyserver/addons/rust-node-addon/src/identity_client/mod.rs b/keyserver/addons/rust-node-addon/src/identity_client/mod.rs --- a/keyserver/addons/rust-node-addon/src/identity_client/mod.rs +++ b/keyserver/addons/rust-node-addon/src/identity_client/mod.rs @@ -140,8 +140,7 @@ pub access_token: String, } -#[napi(object)] -pub struct InboundKeyInfoResponse { +pub struct DeviceInboundKeyInfo { pub payload: String, pub payload_signature: String, pub social_proof: Option, @@ -151,7 +150,7 @@ pub notif_prekey_signature: String, } -impl TryFrom for InboundKeyInfoResponse { +impl TryFrom for DeviceInboundKeyInfo { type Error = Error; fn try_from(key_info: InboundKeyInfo) -> Result { @@ -195,6 +194,19 @@ } } +#[napi(object)] +pub struct InboundKeyInfoResponse { + pub payload: String, + pub payload_signature: String, + pub social_proof: Option, + pub content_prekey: String, + pub content_prekey_signature: String, + pub notif_prekey: String, + pub notif_prekey_signature: String, + 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()) diff --git a/lib/types/identity-service-types.js b/lib/types/identity-service-types.js --- a/lib/types/identity-service-types.js +++ b/lib/types/identity-service-types.js @@ -43,4 +43,6 @@ +contentPrekeySignature: string, +notifPrekey: string, +notifPrekeySignature: string, + +username?: ?string, + +walletAddress?: ?string, }; diff --git a/services/identity/src/database.rs b/services/identity/src/database.rs --- a/services/identity/src/database.rs +++ b/services/identity/src/database.rs @@ -17,7 +17,8 @@ use std::sync::Arc; use crate::ddb_utils::{ - create_one_time_key_partition_key, into_one_time_put_requests, OlmAccountType, + create_one_time_key_partition_key, into_one_time_put_requests, + EthereumIdentity, Identifier, OlmAccountType, }; use crate::error::{consume_error, Error}; use chrono::{DateTime, Utc}; @@ -967,9 +968,7 @@ user_id: &str, get_one_time_keys: bool, ) -> Result, Error> { - let Some(user) = - self.get_item_from_users_table(user_id).await?.item - else { + let Some(user) = self.get_item_from_users_table(user_id).await?.item else { return Ok(None); }; @@ -1098,6 +1097,22 @@ .map_err(|e| Error::AwsSdk(e.into())) } + pub async fn get_user_identifier( + &self, + user_id: &str, + ) -> Result { + let user_info = self + .get_item_from_users_table(user_id) + .await? + .item + .ok_or(Error::MissingItem)?; + + Identifier::try_from(user_info).map_err(|e| { + error!(user_id, "Database item is missing an identifier"); + e + }) + } + async fn get_all_usernames(&self) -> Result, Error> { let scan_output = self .client diff --git a/services/identity/src/ddb_utils.rs b/services/identity/src/ddb_utils.rs --- a/services/identity/src/ddb_utils.rs +++ b/services/identity/src/ddb_utils.rs @@ -1,13 +1,18 @@ use chrono::{DateTime, NaiveDateTime, Utc}; use comm_lib::{ aws::ddb::types::{AttributeValue, PutRequest, WriteRequest}, - database::Value, + database::{AttributeExtractor, AttributeMap, Value}, }; use std::collections::HashMap; use std::iter::IntoIterator; use comm_lib::database::{DBItemAttributeError, DBItemError}; +use crate::constants::{ + USERS_TABLE_DEVICES_MAP_SOCIAL_PROOF_ATTRIBUTE_NAME, + USERS_TABLE_USERNAME_ATTRIBUTE, USERS_TABLE_WALLET_ADDRESS_ATTRIBUTE, +}; + #[derive(Copy, Clone, Debug)] pub enum OlmAccountType { Content, @@ -93,3 +98,41 @@ Some(Self::from_utc(naive, Utc)) } } + +pub enum Identifier { + Username(String), + WalletAddress(EthereumIdentity), +} + +pub struct EthereumIdentity { + pub wallet_address: String, + pub social_proof: String, +} + +impl TryFrom for Identifier { + type Error = crate::error::Error; + + fn try_from(mut value: AttributeMap) -> Result { + let username_result = value.take_attr(USERS_TABLE_USERNAME_ATTRIBUTE); + + if let Ok(username) = username_result { + return Ok(Identifier::Username(username)); + } + + let wallet_address_result = + value.take_attr(USERS_TABLE_WALLET_ADDRESS_ATTRIBUTE); + let social_proof_result = + value.take_attr(USERS_TABLE_DEVICES_MAP_SOCIAL_PROOF_ATTRIBUTE_NAME); + + if let (Ok(wallet_address), Ok(social_proof)) = + (wallet_address_result, social_proof_result) + { + Ok(Identifier::WalletAddress(EthereumIdentity { + wallet_address, + social_proof, + })) + } else { + Err(Self::Error::MalformedItem) + } + } +} diff --git a/services/identity/src/error.rs b/services/identity/src/error.rs --- a/services/identity/src/error.rs +++ b/services/identity/src/error.rs @@ -21,6 +21,8 @@ MissingItem, #[display(...)] DeviceList(DeviceListError), + #[display(...)] + MalformedItem, } #[derive(Debug, derive_more::Display, derive_more::Error)] diff --git a/services/identity/src/grpc_services/authenticated.rs b/services/identity/src/grpc_services/authenticated.rs --- a/services/identity/src/grpc_services/authenticated.rs +++ b/services/identity/src/grpc_services/authenticated.rs @@ -20,9 +20,10 @@ use tracing::{debug, error}; use super::protos::auth::{ - find_user_id_request, identity_client_service_server::IdentityClientService, + find_user_id_request, identity, + identity_client_service_server::IdentityClientService, EthereumIdentity, FindUserIdRequest, FindUserIdResponse, GetDeviceListRequest, - GetDeviceListResponse, InboundKeyInfo, InboundKeysForUserRequest, + GetDeviceListResponse, Identity, InboundKeyInfo, InboundKeysForUserRequest, InboundKeysForUserResponse, KeyserverKeysResponse, OutboundKeyInfo, OutboundKeysForUserRequest, OutboundKeysForUserResponse, RefreshUserPrekeysRequest, UpdateUserPasswordFinishRequest, @@ -163,6 +164,8 @@ &self, request: tonic::Request, ) -> Result, tonic::Status> { + use identity::IdentityInfo; + let message = request.into_inner(); let devices_map = self @@ -189,8 +192,19 @@ }) .collect::>(); + let identifier = self + .db_client + .get_user_identifier(&message.user_id) + .await + .map_err(handle_db_error)?; + + let identity_info = IdentityInfo::try_from(identifier)?; + Ok(tonic::Response::new(InboundKeysForUserResponse { devices: transformed_devices, + identity: Some(Identity { + identity_info: Some(identity_info), + }), })) } diff --git a/services/identity/src/grpc_utils.rs b/services/identity/src/grpc_utils.rs --- a/services/identity/src/grpc_utils.rs +++ b/services/identity/src/grpc_utils.rs @@ -15,8 +15,11 @@ USERS_TABLE_DEVICES_MAP_SOCIAL_PROOF_ATTRIBUTE_NAME, }, database::DeviceKeys, + ddb_utils::Identifier as DBIdentifier, grpc_services::protos::{ - auth::{InboundKeyInfo, OutboundKeyInfo}, + auth::{ + identity::IdentityInfo, EthereumIdentity, InboundKeyInfo, OutboundKeyInfo, + }, unauth::{ DeviceKeyUpload, IdentityKeyInfo, OpaqueLoginStartRequest, Prekey, RegistrationStartRequest, ReservedRegistrationStartRequest, @@ -256,3 +259,19 @@ .ok_or_else(|| Status::invalid_argument("unexpected message data")) } } + +impl TryFrom for IdentityInfo { + type Error = Status; + + fn try_from(value: DBIdentifier) -> Result { + match value { + DBIdentifier::Username(username) => Ok(IdentityInfo::Username(username)), + DBIdentifier::WalletAddress(eth_identity) => { + Ok(IdentityInfo::EthIdentity(EthereumIdentity { + wallet_address: eth_identity.wallet_address, + social_proof: eth_identity.social_proof, + })) + } + } + } +} diff --git a/shared/protos/identity_auth.proto b/shared/protos/identity_auth.proto --- a/shared/protos/identity_auth.proto +++ b/shared/protos/identity_auth.proto @@ -28,9 +28,10 @@ // - One-time Prekey rpc GetOutboundKeysForUser(OutboundKeysForUserRequest) returns (OutboundKeysForUserResponse) {} - // Called by receivers of a communication request. The reponse will only - // return identity keys (both content and notif keys) and related prekeys per - // device, but will not contain one-time keys. + // Called by receivers of a communication request. The reponse will return + // identity keys (both content and notif keys) and related prekeys per device, + // but will not contain one-time keys. Additionally, the response will contain + // the other user's username. rpc GetInboundKeysForUser(InboundKeysForUserRequest) returns (InboundKeysForUserResponse) {} @@ -60,6 +61,18 @@ // Helper types +message EthereumIdentity { + string wallet_address = 1; + string social_proof = 2; +} + +message Identity { + oneof identity_info { + string username = 1; + EthereumIdentity eth_identity = 2; + } +} + // UploadOneTimeKeys // As OPKs get exhausted, they need to be refreshed @@ -114,6 +127,7 @@ message InboundKeysForUserResponse { // Map is keyed on devices' public ed25519 key used for signing map devices = 1; + Identity identity = 2; } message InboundKeysForUserRequest {