diff --git a/keyserver/src/responders/user-responders.js b/keyserver/src/responders/user-responders.js --- a/keyserver/src/responders/user-responders.js +++ b/keyserver/src/responders/user-responders.js @@ -740,8 +740,11 @@ const issuedAt = new Date().toISOString(); const reservedUsernameMessage: ReservedUsernameMessage = { - statement: 'This user is the owner of the following username', - payload: username, + statement: 'This user is the owner of the following username and user ID', + payload: { + username, + userID: viewer.userID, + }, issuedAt, }; const message = JSON.stringify(reservedUsernameMessage); diff --git a/lib/types/crypto-types.js b/lib/types/crypto-types.js --- a/lib/types/crypto-types.js +++ b/lib/types/crypto-types.js @@ -61,8 +61,11 @@ +issuedAt: string, } | { - +statement: 'This user is the owner of the following username', - +payload: string, + +statement: 'This user is the owner of the following username and user ID', + +payload: { + +username: string, + +userID: string, + }, +issuedAt: string, }; 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 @@ -36,9 +36,9 @@ use crate::id::generate_uuid; use crate::nonce::generate_nonce_data; use crate::reserved_users::{ + validate_account_ownership_message_and_get_user_id, validate_add_reserved_usernames_message, validate_remove_reserved_username_message, - validate_signed_account_ownership_message, }; use crate::siwe::{is_valid_ethereum_address, parse_and_verify_siwe_message}; use crate::token::{AccessTokenData, AuthType}; @@ -61,6 +61,7 @@ pub struct UserRegistrationInfo { pub username: String, pub flattened_device_key_upload: FlattenedDeviceKeyUpload, + pub user_id: Option, } #[derive(Clone)] @@ -173,6 +174,7 @@ device_type: DeviceType::try_from(DBDeviceTypeInt(device_type)) .map_err(handle_db_error)?, }, + user_id: None, }; let session_id = generate_uuid(); self @@ -217,16 +219,16 @@ .username_in_reserved_usernames_table(&message.username) .await .map_err(handle_db_error)?; - if username_in_reserved_usernames_table { - validate_signed_account_ownership_message( - &message.username, - &message.keyserver_message, - &message.keyserver_signature, - )?; - } else { + if !username_in_reserved_usernames_table { return Err(tonic::Status::permission_denied("username not reserved")); } + let user_id = validate_account_ownership_message_and_get_user_id( + &message.username, + &message.keyserver_message, + &message.keyserver_signature, + )?; + if let client_proto::ReservedRegistrationStartRequest { opaque_registration_request: register_message, username, @@ -276,6 +278,7 @@ device_type: DeviceType::try_from(DBDeviceTypeInt(device_type)) .map_err(handle_db_error)?, }, + user_id: Some(user_id), }; let session_id = generate_uuid(); 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 @@ -144,6 +144,7 @@ Some((registration_state.username, Blob::new(password_file))), None, None, + registration_state.user_id, ) .await } @@ -160,6 +161,7 @@ None, Some(wallet_address), Some(social_proof), + None, ) .await } @@ -170,8 +172,9 @@ username_and_password_file: Option<(String, Blob)>, wallet_address: Option, social_proof: Option, + user_id: Option, ) -> Result { - let user_id = generate_uuid(); + let user_id = user_id.unwrap_or_else(generate_uuid); let device_info = create_device_info(flattened_device_key_upload.clone(), social_proof); let devices = HashMap::from([( @@ -212,6 +215,9 @@ .put_item() .table_name(USERS_TABLE) .set_item(Some(user)) + // make sure we don't accidentaly overwrite existing row + .condition_expression("attribute_not_exists(#pk)") + .expression_attribute_names("#pk", USERS_TABLE_PARTITION_KEY) .send() .await .map_err(|e| Error::AwsSdk(e.into()))?; diff --git a/services/identity/src/reserved_users.rs b/services/identity/src/reserved_users.rs --- a/services/identity/src/reserved_users.rs +++ b/services/identity/src/reserved_users.rs @@ -17,6 +17,16 @@ issued_at: String, } +// This type should not be changed without making equivalent changes to +// `ReservedUsernameMessage` in lib/types/crypto-types.js +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct UsernameAndID { + username: String, + #[serde(rename = "userID")] + user_id: String, +} + fn validate_and_decode_message( keyserver_message: &str, keyserver_signature: &str, @@ -69,22 +79,25 @@ Ok(deserialized_message) } -pub fn validate_signed_account_ownership_message( +pub fn validate_account_ownership_message_and_get_user_id( username: &str, keyserver_message: &str, keyserver_signature: &str, -) -> Result<(), Status> { - let deserialized_message = validate_and_decode_message::( +) -> Result { + const EXPECTED_STATEMENT: &[u8; 60] = + b"This user is the owner of the following username and user ID"; + + let deserialized_message = validate_and_decode_message::( keyserver_message, keyserver_signature, - b"This user is the owner of the following username", + EXPECTED_STATEMENT, )?; - if deserialized_message.payload != username { + if deserialized_message.payload.username != username { return Err(Status::invalid_argument("message invalid")); } - Ok(()) + Ok(deserialized_message.payload.user_id) } pub fn validate_add_reserved_usernames_message(