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 @@ -23,7 +23,8 @@ database::{DatabaseClient, Error as DBError, KeyPayload}, id::generate_uuid, nonce::generate_nonce_data, - token::AccessTokenData, + siwe::parse_and_verify_siwe_message, + token::{AccessTokenData, AuthType}, }; use aws_sdk_dynamodb::Error as DynamoDBError; pub use client_proto::identity_client_service_server::{ @@ -178,13 +179,13 @@ let device_id = state.flattened_device_key_upload.device_id_key.clone(); let user_id = self .client - .add_user_to_users_table(state, password_file) + .add_password_user_to_users_table(state, password_file) .await .map_err(handle_db_error)?; // Create access token let token = AccessTokenData::new( - message.session_id, + user_id.clone(), device_id, crate::token::AuthType::Password, &mut OsRng, @@ -324,13 +325,16 @@ self .client - .add_device_to_users_table(state.clone()) + .add_password_user_device_to_users_table( + state.user_id.clone(), + state.flattened_device_key_upload.clone(), + ) .await .map_err(handle_db_error)?; // Create access token let token = AccessTokenData::new( - message.session_id, + state.user_id.clone(), state.flattened_device_key_upload.device_id_key, crate::token::AuthType::Password, &mut OsRng, @@ -356,9 +360,116 @@ async fn login_wallet_user( &self, - _request: tonic::Request, + request: tonic::Request, ) -> Result, tonic::Status> { - unimplemented!(); + let message = request.into_inner(); + + let wallet_address = parse_and_verify_siwe_message( + &message.siwe_message, + &message.siwe_signature, + )?; + + let (flattened_device_key_upload, social_proof) = + if let client_proto::WalletLoginRequest { + siwe_message: _, + siwe_signature: _, + device_key_upload: + Some(client_proto::DeviceKeyUpload { + device_key_info: + Some(client_proto::IdentityKeyInfo { + payload, + payload_signature, + social_proof: Some(social_proof), + }), + identity_upload: + Some(client_proto::PreKey { + pre_key: identity_prekey, + pre_key_signature: identity_prekey_signature, + }), + notif_upload: + Some(client_proto::PreKey { + pre_key: notif_prekey, + pre_key_signature: notif_prekey_signature, + }), + onetime_identity_prekeys, + onetime_notif_prekeys, + }), + } = message + { + let key_info = KeyPayload::from_str(&payload) + .map_err(|_| tonic::Status::invalid_argument("malformed payload"))?; + ( + FlattenedDeviceKeyUpload { + device_id_key: key_info.primary_identity_public_keys.curve25519, + key_payload: payload, + key_payload_signature: payload_signature, + identity_prekey, + identity_prekey_signature, + identity_onetime_keys: onetime_identity_prekeys, + notif_prekey, + notif_prekey_signature, + notif_onetime_keys: onetime_notif_prekeys, + }, + social_proof, + ) + } else { + return Err(tonic::Status::invalid_argument("unexpected message data")); + }; + + let user_id = match self + .client + .get_user_id_from_user_info(wallet_address.clone(), AuthType::Wallet) + .await + .map_err(handle_db_error)? + { + Some(id) => { + // User already exists, so we should update the DDB item + self + .client + .add_wallet_user_device_to_users_table( + id.clone(), + flattened_device_key_upload.clone(), + social_proof, + ) + .await + .map_err(handle_db_error)?; + id + } + None => { + // User doesn't exist yet, so we should add a new user in DDB + self + .client + .add_wallet_user_to_users_table( + flattened_device_key_upload.clone(), + wallet_address, + social_proof, + ) + .await + .map_err(handle_db_error)? + } + }; + + // Create access token + let token = AccessTokenData::new( + user_id.clone(), + flattened_device_key_upload.device_id_key, + crate::token::AuthType::Password, + &mut OsRng, + ); + + let access_token = token.access_token.clone(); + + self + .client + .put_access_token_data(token) + .await + .map_err(handle_db_error)?; + + let response = WalletLoginResponse { + user_id: user_id, + access_token, + }; + Ok(Response::new(response)) } async fn delete_user( 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 @@ -34,6 +34,7 @@ // notifPreKey: String, // notifPreKeySignature: String, // notifOneTimeKeys: Vec, +// socialProof: Option // } // } // @@ -65,6 +66,8 @@ pub const USERS_TABLE_DEVICES_MAP_NOTIF_ONETIME_KEYS_ATTRIBUTE_NAME: &str = "notifOneTimeKeys"; pub const USERS_TABLE_WALLET_ADDRESS_ATTRIBUTE: &str = "walletAddress"; +pub const USERS_TABLE_DEVICES_MAP_SOCIAL_PROOF_ATTRIBUTE_NAME: &str = + "socialProof"; pub const USERS_TABLE_USERNAME_INDEX: &str = "username-index"; pub const USERS_TABLE_WALLET_ADDRESS_INDEX: &str = "walletAddress-index"; 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 @@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize}; use tracing::{debug, error, info, warn}; -use crate::client_service::{UserLoginInfo, UserRegistrationInfo}; +use crate::client_service::{FlattenedDeviceKeyUpload, UserRegistrationInfo}; use crate::config::CONFIG; use crate::constants::{ ACCESS_TOKEN_SORT_KEY, ACCESS_TOKEN_TABLE, @@ -33,6 +33,7 @@ USERS_TABLE_DEVICES_MAP_NOTIF_ONETIME_KEYS_ATTRIBUTE_NAME, USERS_TABLE_DEVICES_MAP_NOTIF_PREKEY_ATTRIBUTE_NAME, USERS_TABLE_DEVICES_MAP_NOTIF_PREKEY_SIGNATURE_ATTRIBUTE_NAME, + USERS_TABLE_DEVICES_MAP_SOCIAL_PROOF_ATTRIBUTE_NAME, USERS_TABLE_PARTITION_KEY, USERS_TABLE_REGISTRATION_ATTRIBUTE, USERS_TABLE_USERNAME_ATTRIBUTE, USERS_TABLE_USERNAME_INDEX, USERS_TABLE_WALLET_ADDRESS_ATTRIBUTE, USERS_TABLE_WALLET_ADDRESS_INDEX, @@ -104,55 +105,75 @@ } } - pub async fn add_user_to_users_table( + pub async fn add_password_user_to_users_table( &self, registration_state: UserRegistrationInfo, password_file: Vec, + ) -> Result { + self + .add_user_to_users_table( + registration_state.flattened_device_key_upload, + Some((registration_state.username, Blob::new(password_file))), + None, + None, + ) + .await + } + + pub async fn add_wallet_user_to_users_table( + &self, + flattened_device_key_upload: FlattenedDeviceKeyUpload, + wallet_address: String, + social_proof: String, + ) -> Result { + self + .add_user_to_users_table( + flattened_device_key_upload, + None, + Some(wallet_address), + Some(social_proof), + ) + .await + } + + async fn add_user_to_users_table( + &self, + flattened_device_key_upload: FlattenedDeviceKeyUpload, + username_and_password_file: Option<(String, Blob)>, + wallet_address: Option, + social_proof: Option, ) -> Result { let user_id = generate_uuid(); - let device_info = HashMap::from([ + let mut device_info = HashMap::from([ ( USERS_TABLE_DEVICES_MAP_DEVICE_TYPE_ATTRIBUTE_NAME.to_string(), AttributeValue::S(Device::Client.to_string()), ), ( USERS_TABLE_DEVICES_MAP_KEY_PAYLOAD_ATTRIBUTE_NAME.to_string(), - AttributeValue::S( - registration_state.flattened_device_key_upload.key_payload, - ), + AttributeValue::S(flattened_device_key_upload.key_payload), ), ( USERS_TABLE_DEVICES_MAP_KEY_PAYLOAD_SIGNATURE_ATTRIBUTE_NAME .to_string(), - AttributeValue::S( - registration_state - .flattened_device_key_upload - .key_payload_signature, - ), + AttributeValue::S(flattened_device_key_upload.key_payload_signature), ), ( USERS_TABLE_DEVICES_MAP_IDENTITY_PREKEY_ATTRIBUTE_NAME.to_string(), - AttributeValue::S( - registration_state - .flattened_device_key_upload - .identity_prekey, - ), + AttributeValue::S(flattened_device_key_upload.identity_prekey), ), ( USERS_TABLE_DEVICES_MAP_IDENTITY_PREKEY_SIGNATURE_ATTRIBUTE_NAME .to_string(), AttributeValue::S( - registration_state - .flattened_device_key_upload - .identity_prekey_signature, + flattened_device_key_upload.identity_prekey_signature, ), ), ( USERS_TABLE_DEVICES_MAP_IDENTITY_ONETIME_KEYS_ATTRIBUTE_NAME .to_string(), AttributeValue::L( - registration_state - .flattened_device_key_upload + flattened_device_key_upload .identity_onetime_keys .into_iter() .map(AttributeValue::S) @@ -161,24 +182,17 @@ ), ( USERS_TABLE_DEVICES_MAP_NOTIF_PREKEY_ATTRIBUTE_NAME.to_string(), - AttributeValue::S( - registration_state.flattened_device_key_upload.notif_prekey, - ), + AttributeValue::S(flattened_device_key_upload.notif_prekey), ), ( USERS_TABLE_DEVICES_MAP_NOTIF_PREKEY_SIGNATURE_ATTRIBUTE_NAME .to_string(), - AttributeValue::S( - registration_state - .flattened_device_key_upload - .notif_prekey_signature, - ), + AttributeValue::S(flattened_device_key_upload.notif_prekey_signature), ), ( USERS_TABLE_DEVICES_MAP_NOTIF_ONETIME_KEYS_ATTRIBUTE_NAME.to_string(), AttributeValue::L( - registration_state - .flattened_device_key_upload + flattened_device_key_upload .notif_onetime_keys .into_iter() .map(AttributeValue::S) @@ -186,30 +200,46 @@ ), ), ]); + + if let Some(social_proof) = social_proof { + device_info.insert( + USERS_TABLE_DEVICES_MAP_SOCIAL_PROOF_ATTRIBUTE_NAME.to_string(), + AttributeValue::S(social_proof), + ); + } let devices = HashMap::from([( - registration_state.flattened_device_key_upload.device_id_key, + flattened_device_key_upload.device_id_key, AttributeValue::M(device_info), )]); - - let user = HashMap::from([ + let mut user = HashMap::from([ ( USERS_TABLE_PARTITION_KEY.to_string(), AttributeValue::S(user_id.clone()), ), - ( - USERS_TABLE_USERNAME_ATTRIBUTE.to_string(), - AttributeValue::S(registration_state.username), - ), ( USERS_TABLE_DEVICES_ATTRIBUTE.to_string(), AttributeValue::M(devices), ), - ( - USERS_TABLE_REGISTRATION_ATTRIBUTE.to_string(), - AttributeValue::B(Blob::new(password_file)), - ), ]); + if let Some((username, password_file)) = username_and_password_file { + user.insert( + USERS_TABLE_USERNAME_ATTRIBUTE.to_string(), + AttributeValue::S(username), + ); + user.insert( + USERS_TABLE_REGISTRATION_ATTRIBUTE.to_string(), + AttributeValue::B(password_file), + ); + } + + if let Some(address) = wallet_address { + user.insert( + USERS_TABLE_WALLET_ADDRESS_ATTRIBUTE.to_string(), + AttributeValue::S(address), + ); + } + self .client .put_item() @@ -222,49 +252,67 @@ Ok(user_id) } - pub async fn add_device_to_users_table( + pub async fn add_password_user_device_to_users_table( + &self, + user_id: String, + flattened_device_key_upload: FlattenedDeviceKeyUpload, + ) -> Result<(), Error> { + self + .add_device_to_users_table(user_id, flattened_device_key_upload, None) + .await + } + + pub async fn add_wallet_user_device_to_users_table( + &self, + user_id: String, + flattened_device_key_upload: FlattenedDeviceKeyUpload, + social_proof: String, + ) -> Result<(), Error> { + self + .add_device_to_users_table( + user_id, + flattened_device_key_upload, + Some(social_proof), + ) + .await + } + + async fn add_device_to_users_table( &self, - login_state: UserLoginInfo, + user_id: String, + flattened_device_key_upload: FlattenedDeviceKeyUpload, + social_proof: Option, ) -> Result<(), Error> { - let device_info = HashMap::from([ + let mut device_info = HashMap::from([ ( USERS_TABLE_DEVICES_MAP_DEVICE_TYPE_ATTRIBUTE_NAME.to_string(), AttributeValue::S(Device::Client.to_string()), ), ( USERS_TABLE_DEVICES_MAP_KEY_PAYLOAD_ATTRIBUTE_NAME.to_string(), - AttributeValue::S(login_state.flattened_device_key_upload.key_payload), + AttributeValue::S(flattened_device_key_upload.key_payload), ), ( USERS_TABLE_DEVICES_MAP_KEY_PAYLOAD_SIGNATURE_ATTRIBUTE_NAME .to_string(), - AttributeValue::S( - login_state - .flattened_device_key_upload - .key_payload_signature, - ), + AttributeValue::S(flattened_device_key_upload.key_payload_signature), ), ( USERS_TABLE_DEVICES_MAP_IDENTITY_PREKEY_ATTRIBUTE_NAME.to_string(), - AttributeValue::S( - login_state.flattened_device_key_upload.identity_prekey, - ), + AttributeValue::S(flattened_device_key_upload.identity_prekey), ), ( USERS_TABLE_DEVICES_MAP_IDENTITY_PREKEY_SIGNATURE_ATTRIBUTE_NAME .to_string(), AttributeValue::S( - login_state - .flattened_device_key_upload - .identity_prekey_signature, + flattened_device_key_upload.identity_prekey_signature, ), ), ( USERS_TABLE_DEVICES_MAP_IDENTITY_ONETIME_KEYS_ATTRIBUTE_NAME .to_string(), AttributeValue::L( - login_state - .flattened_device_key_upload + flattened_device_key_upload .identity_onetime_keys .into_iter() .map(AttributeValue::S) @@ -273,22 +321,17 @@ ), ( USERS_TABLE_DEVICES_MAP_NOTIF_PREKEY_ATTRIBUTE_NAME.to_string(), - AttributeValue::S(login_state.flattened_device_key_upload.notif_prekey), + AttributeValue::S(flattened_device_key_upload.notif_prekey), ), ( USERS_TABLE_DEVICES_MAP_NOTIF_PREKEY_SIGNATURE_ATTRIBUTE_NAME .to_string(), - AttributeValue::S( - login_state - .flattened_device_key_upload - .notif_prekey_signature, - ), + AttributeValue::S(flattened_device_key_upload.notif_prekey_signature), ), ( USERS_TABLE_DEVICES_MAP_NOTIF_ONETIME_KEYS_ATTRIBUTE_NAME.to_string(), AttributeValue::L( - login_state - .flattened_device_key_upload + flattened_device_key_upload .notif_onetime_keys .into_iter() .map(AttributeValue::S) @@ -297,11 +340,17 @@ ), ]); + if let Some(social_proof) = social_proof { + device_info.insert( + USERS_TABLE_DEVICES_MAP_SOCIAL_PROOF_ATTRIBUTE_NAME.to_string(), + AttributeValue::S(social_proof), + ); + } let update_expression = format!("SET {}.#{} = :v", USERS_TABLE_DEVICES_ATTRIBUTE, "deviceID",); let expression_attribute_names = HashMap::from([( format!("#{}", "deviceID"), - login_state.flattened_device_key_upload.device_id_key, + flattened_device_key_upload.device_id_key, )]); let expression_attribute_values = HashMap::from([(":v".to_string(), AttributeValue::M(device_info))]); @@ -310,10 +359,7 @@ .client .update_item() .table_name(USERS_TABLE) - .key( - USERS_TABLE_PARTITION_KEY, - AttributeValue::S(login_state.user_id), - ) + .key(USERS_TABLE_PARTITION_KEY, AttributeValue::S(user_id)) .update_expression(update_expression) .set_expression_attribute_names(Some(expression_attribute_names)) .set_expression_attribute_values(Some(expression_attribute_values)) diff --git a/services/identity/src/main.rs b/services/identity/src/main.rs --- a/services/identity/src/main.rs +++ b/services/identity/src/main.rs @@ -14,6 +14,7 @@ mod interceptor; mod keygen; mod nonce; +mod siwe; mod token; use config::load_config; diff --git a/services/identity/src/siwe.rs b/services/identity/src/siwe.rs new file mode 100644 --- /dev/null +++ b/services/identity/src/siwe.rs @@ -0,0 +1,34 @@ +use chrono::Utc; +use siwe::{eip55, Message}; +use tonic::Status; +use tracing::error; + +pub fn parse_and_verify_siwe_message( + siwe_message: &str, + siwe_signature: &str, +) -> Result { + let siwe_message: Message = siwe_message.parse().map_err(|e| { + error!("Failed to parse SIWE message: {}", e); + Status::invalid_argument("invalid message") + })?; + + let decoded_signature = hex::decode(siwe_signature.trim_start_matches("0x")) + .map_err(|e| { + error!("Failed to decode SIWE signature: {}", e); + Status::invalid_argument("invalid signature") + })?; + + let signature = decoded_signature.try_into().map_err(|e| { + error!("Conversion to SIWE signature failed: {:?}", e); + Status::invalid_argument("invalid message") + })?; + + siwe_message + .verify(signature, None, None, Some(&Utc::now())) + .map_err(|e| { + error!("Signature verification failed: {}", e); + Status::unauthenticated("message not authenticated") + })?; + + Ok(eip55(&siwe_message.address)) +} diff --git a/shared/protos/identity_client.proto b/shared/protos/identity_client.proto --- a/shared/protos/identity_client.proto +++ b/shared/protos/identity_client.proto @@ -82,7 +82,7 @@ string payload = 1; // Payload signed with the signing ed25519 key string payloadSignature = 2; - // Signed message used for SIWE (optional) + // Signed message used for SIWE // This correlates a given wallet with the identity of a device optional string socialProof = 3; }