diff --git a/services/identity/src/client_service.rs b/services/identity/src/client_service.rs --- a/services/identity/src/client_service.rs +++ b/services/identity/src/client_service.rs @@ -554,15 +554,13 @@ let social_proof = SocialProof::new(message.siwe_message, message.siwe_signature); - let serialized_social_proof = serde_json::to_string(&social_proof) - .map_err(|_| tonic::Status::invalid_argument("invalid_social_proof"))?; let user_id = self .client .add_wallet_user_to_users_table( flattened_device_key_upload.clone(), wallet_address, - serialized_social_proof, + social_proof, None, code_version, login_time, @@ -635,8 +633,6 @@ let social_proof = SocialProof::new(message.siwe_message, message.siwe_signature); - let serialized_social_proof = serde_json::to_string(&social_proof) - .map_err(|_| tonic::Status::invalid_argument("invalid_social_proof"))?; let login_time = chrono::Utc::now(); self @@ -644,7 +640,7 @@ .add_wallet_user_to_users_table( flattened_device_key_upload.clone(), wallet_address, - serialized_social_proof, + social_proof, Some(user_id.clone()), code_version, login_time, diff --git a/services/identity/src/constants.rs b/services/identity/src/constants.rs --- a/services/identity/src/constants.rs +++ b/services/identity/src/constants.rs @@ -84,6 +84,10 @@ pub const RESERVED_USERNAMES_TABLE_PARTITION_KEY: &str = "username"; pub const RESERVED_USERNAMES_TABLE_USER_ID_ATTRIBUTE: &str = "userID"; +// Users table social proof attribute +pub const SOCIAL_PROOF_MESSAGE_ATTRIBUTE: &str = "siweMessage"; +pub const SOCIAL_PROOF_SIGNATURE_ATTRIBUTE: &str = "siweSignature"; + pub mod devices_table { /// table name pub const NAME: &str = "identity-devices"; 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 @@ -18,7 +18,7 @@ use crate::{ constants::USERS_TABLE_SOCIAL_PROOF_ATTRIBUTE_NAME, - ddb_utils::EthereumIdentity, reserved_users::UserDetail, + ddb_utils::EthereumIdentity, reserved_users::UserDetail, siwe::SocialProof, }; use crate::{ ddb_utils::{ @@ -193,7 +193,7 @@ &self, flattened_device_key_upload: FlattenedDeviceKeyUpload, wallet_address: String, - social_proof: String, + social_proof: SocialProof, user_id: Option, code_version: u64, access_token_creation_time: DateTime, @@ -257,7 +257,7 @@ ); user.insert( USERS_TABLE_SOCIAL_PROOF_ATTRIBUTE_NAME.to_string(), - AttributeValue::S(eth_identity.social_proof), + eth_identity.social_proof.into(), ); } 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 @@ -6,9 +6,12 @@ use std::collections::HashMap; use std::iter::IntoIterator; -use crate::constants::{ - USERS_TABLE_SOCIAL_PROOF_ATTRIBUTE_NAME, USERS_TABLE_USERNAME_ATTRIBUTE, - USERS_TABLE_WALLET_ADDRESS_ATTRIBUTE, +use crate::{ + constants::{ + USERS_TABLE_SOCIAL_PROOF_ATTRIBUTE_NAME, USERS_TABLE_USERNAME_ATTRIBUTE, + USERS_TABLE_WALLET_ADDRESS_ATTRIBUTE, + }, + siwe::SocialProof, }; #[derive(Copy, Clone, Debug)] @@ -84,7 +87,7 @@ pub struct EthereumIdentity { pub wallet_address: String, - pub social_proof: String, + pub social_proof: SocialProof, } impl TryFrom for Identifier { 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 @@ -169,7 +169,7 @@ Ok(tonic::Response::new(InboundKeysForUserResponse { devices: transformed_devices, - identity: Some(identifier.try_into()?), + identity: Some(identifier.into()), })) } @@ -204,7 +204,7 @@ let response = Response::new(KeyserverKeysResponse { keyserver_info: Some(keyserver_info.into()), - identity: Some(identifier.try_into()?), + identity: Some(identifier.into()), primary_device_identity_info: Some(primary_device_keys.into()), }); @@ -462,7 +462,7 @@ .ok_or_else(|| tonic::Status::not_found("user not found"))?; let response = UserIdentityResponse { - identity: Some(identifier.try_into()?), + identity: Some(identifier.into()), }; return Ok(Response::new(response)); } 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 @@ -242,32 +242,22 @@ } } -impl TryFrom for Identity { - type Error = Status; - - fn try_from(value: DBIdentifier) -> Result { - let identity = match value { +impl From for Identity { + fn from(value: DBIdentifier) -> Self { + match value { DBIdentifier::Username(username) => Identity { username, eth_identity: None, }, - DBIdentifier::WalletAddress(eth_identity) => { - let SocialProof { message, signature } = - eth_identity.social_proof.try_into().map_err(|err| { - error!("Failed to construct wallet identity: {err}"); - Status::internal("unexpected error") - })?; - Identity { - username: eth_identity.wallet_address.clone(), - eth_identity: Some(EthereumIdentity { - wallet_address: eth_identity.wallet_address, - siwe_message: message, - siwe_signature: signature, - }), - } - } - }; - Ok(identity) + DBIdentifier::WalletAddress(eth_identity) => Identity { + username: eth_identity.wallet_address.clone(), + eth_identity: Some(EthereumIdentity { + wallet_address: eth_identity.wallet_address, + siwe_message: eth_identity.social_proof.message, + siwe_signature: eth_identity.social_proof.signature, + }), + }, + } } } diff --git a/services/identity/src/siwe.rs b/services/identity/src/siwe.rs --- a/services/identity/src/siwe.rs +++ b/services/identity/src/siwe.rs @@ -1,10 +1,20 @@ +use std::collections::HashMap; + use chrono::Utc; +use comm_lib::{ + aws::ddb::types::AttributeValue, + database::{AttributeExtractor, AttributeMap, TryFromAttribute}, +}; use regex::Regex; use serde::{Deserialize, Serialize}; use siwe::Message; use tonic::Status; use tracing::error; +use crate::constants::{ + SOCIAL_PROOF_MESSAGE_ATTRIBUTE, SOCIAL_PROOF_SIGNATURE_ATTRIBUTE, +}; + pub fn parse_and_verify_siwe_message( siwe_message: &str, siwe_signature: &str, @@ -40,25 +50,51 @@ ethereum_address_regex.is_match(candidate) } -#[derive(derive_more::Constructor, Serialize, Deserialize)] +#[derive(derive_more::Constructor)] pub struct SocialProof { pub message: String, pub signature: String, } -impl TryFrom for SocialProof { - type Error = crate::error::Error; +impl From for AttributeValue { + fn from(value: SocialProof) -> Self { + AttributeValue::M(HashMap::from([ + ( + SOCIAL_PROOF_MESSAGE_ATTRIBUTE.to_string(), + AttributeValue::S(value.message), + ), + ( + SOCIAL_PROOF_SIGNATURE_ATTRIBUTE.to_string(), + AttributeValue::S(value.signature), + ), + ])) + } +} + +impl TryFrom for SocialProof { + type Error = comm_lib::database::DBItemError; + + fn try_from(mut attrs: AttributeMap) -> Result { + let message = attrs.take_attr(SOCIAL_PROOF_MESSAGE_ATTRIBUTE)?; + let signature = attrs.take_attr(SOCIAL_PROOF_SIGNATURE_ATTRIBUTE)?; + Ok(Self { message, signature }) + } +} - fn try_from(value: String) -> Result { - serde_json::from_str(&value).map_err(|err| { - error!("Failed to deserialize social proof: {err}"); - err.into() - }) +impl TryFromAttribute for SocialProof { + fn try_from_attr( + attribute_name: impl Into, + attribute: Option, + ) -> Result { + AttributeMap::try_from_attr(attribute_name, attribute) + .and_then(SocialProof::try_from) } } #[cfg(test)] mod tests { + use crate::constants::USERS_TABLE_SOCIAL_PROOF_ATTRIBUTE_NAME; + use super::*; #[test] @@ -99,4 +135,24 @@ // Empty string assert_eq!(is_valid_ethereum_address(""), false); } + + #[test] + fn test_social_proof_ddb_format() { + let message = "foo"; + let signature = "bar"; + let social_proof = + SocialProof::new(message.to_string(), signature.to_string()); + + let mut user_item = AttributeMap::from([( + USERS_TABLE_SOCIAL_PROOF_ATTRIBUTE_NAME.to_string(), + social_proof.into(), + )]); + + let social_proof_from_attr: SocialProof = user_item + .take_attr(USERS_TABLE_SOCIAL_PROOF_ATTRIBUTE_NAME) + .expect("social proof fetch failed"); + + assert_eq!(social_proof_from_attr.message, message); + assert_eq!(social_proof_from_attr.signature, signature); + } }