diff --git a/keyserver/addons/rust-node-addon/rust-binding-types.js b/keyserver/addons/rust-node-addon/rust-binding-types.js --- a/keyserver/addons/rust-node-addon/rust-binding-types.js +++ b/keyserver/addons/rust-node-addon/rust-binding-types.js @@ -21,7 +21,7 @@ password: string, signedIdentityKeysBlob: SignedIdentityKeysBlob, ) => Promise, - +addReservedUsername: (message: string, signature: string) => Promise, + +addReservedUsernames: (message: string, signature: string) => Promise, +removeReservedUsername: ( message: string, signature: string, diff --git a/keyserver/addons/rust-node-addon/src/identity_client/add_reserved_username.rs b/keyserver/addons/rust-node-addon/src/identity_client/add_reserved_usernames.rs rename from keyserver/addons/rust-node-addon/src/identity_client/add_reserved_username.rs rename to keyserver/addons/rust-node-addon/src/identity_client/add_reserved_usernames.rs --- a/keyserver/addons/rust-node-addon/src/identity_client/add_reserved_username.rs +++ b/keyserver/addons/rust-node-addon/src/identity_client/add_reserved_usernames.rs @@ -10,11 +10,11 @@ let channel = get_identity_service_channel().await?; let mut identity_client = IdentityClientServiceClient::new(channel); - let add_reserved_username_request = - AddReservedUsernameRequest { message, signature }; + let add_reserved_usernames_request = + AddReservedUsernamesRequest { message, signature }; identity_client - .add_reserved_username(add_reserved_username_request) + .add_reserved_usernames(add_reserved_usernames_request) .await .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))? .into_inner(); 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 @@ -1,4 +1,4 @@ -pub mod add_reserved_username; +pub mod add_reserved_usernames; pub mod register_user; pub mod remove_reserved_username; pub mod identity_client { @@ -7,7 +7,7 @@ use identity_client::identity_client_service_client::IdentityClientServiceClient; use identity_client::{ - AddReservedUsernameRequest, DeviceKeyUpload, IdentityKeyInfo, + AddReservedUsernamesRequest, DeviceKeyUpload, IdentityKeyInfo, RegistrationFinishRequest, RegistrationStartRequest, RemoveReservedUsernameRequest, }; 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 @@ -6,14 +6,16 @@ use crate::{ client_service::client_proto::{ - DeleteUserRequest, Empty, GenerateNonceResponse, InboundKeysForUserRequest, + AddReservedUsernamesRequest, DeleteUserRequest, Empty, + GenerateNonceResponse, InboundKeysForUserRequest, InboundKeysForUserResponse, OpaqueLoginFinishRequest, OpaqueLoginFinishResponse, OpaqueLoginStartRequest, OpaqueLoginStartResponse, OutboundKeysForUserRequest, OutboundKeysForUserResponse, OutboundKeyserverResponse, RefreshUserPreKeysRequest, RegistrationFinishRequest, RegistrationFinishResponse, RegistrationStartRequest, - RegistrationStartResponse, UpdateUserPasswordFinishRequest, + RegistrationStartResponse, RemoveReservedUsernameRequest, + ReservedRegistrationStartRequest, UpdateUserPasswordFinishRequest, UpdateUserPasswordStartRequest, UpdateUserPasswordStartResponse, UploadOneTimeKeysRequest, VerifyUserAccessTokenRequest, VerifyUserAccessTokenResponse, WalletLoginRequest, WalletLoginResponse, @@ -23,7 +25,7 @@ id::generate_uuid, nonce::generate_nonce_data, reserved_users::{ - validate_add_reserved_username_message, + validate_add_reserved_usernames_message, validate_remove_reserved_username_message, validate_signed_account_ownership_message, }, @@ -41,11 +43,6 @@ use tonic::Response; use tracing::{debug, error}; -use self::client_proto::{ - AddReservedUsernameRequest, RemoveReservedUsernameRequest, - ReservedRegistrationStartRequest, -}; - #[derive(Clone)] pub enum WorkflowInProgress { Registration(UserRegistrationInfo), @@ -819,20 +816,33 @@ Ok(response) } - async fn add_reserved_username( + async fn add_reserved_usernames( &self, - request: tonic::Request, + request: tonic::Request, ) -> Result, tonic::Status> { let message = request.into_inner(); - let username = validate_add_reserved_username_message( + let usernames = validate_add_reserved_usernames_message( &message.message, &message.signature, )?; + let mut filtered_usernames = Vec::new(); + + for username in usernames { + if !self + .client + .username_taken(username.clone()) + .await + .map_err(handle_db_error)? + { + filtered_usernames.push(username); + } + } + self .client - .add_username_to_reserved_usernames_table(username) + .add_usernames_to_reserved_usernames_table(filtered_usernames) .await .map_err(handle_db_error)?; 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 @@ -5,7 +5,7 @@ use std::sync::Arc; use aws_config::SdkConfig; -use aws_sdk_dynamodb::model::AttributeValue; +use aws_sdk_dynamodb::model::{AttributeValue, PutRequest, WriteRequest}; use aws_sdk_dynamodb::output::{ DeleteItemOutput, GetItemOutput, PutItemOutput, QueryOutput, }; @@ -665,22 +665,58 @@ .map_err(|e| Error::AwsSdk(e.into())) } - pub async fn add_username_to_reserved_usernames_table( + pub async fn add_usernames_to_reserved_usernames_table( &self, - username: String, - ) -> Result { - let item = HashMap::from([( - RESERVED_USERNAMES_TABLE_PARTITION_KEY.to_string(), - AttributeValue::S(username), - )]); - self - .client - .put_item() - .table_name(RESERVED_USERNAMES_TABLE) - .set_item(Some(item)) - .send() - .await - .map_err(|e| Error::AwsSdk(e.into())) + usernames: Vec, + ) -> Result<(), Error> { + let mut write_requests = vec![]; + + for username in usernames { + let item: HashMap = vec![( + RESERVED_USERNAMES_TABLE_PARTITION_KEY.to_string(), + AttributeValue::S(username), + )] + .into_iter() + .collect(); + + let write_request = WriteRequest::builder() + .put_request(PutRequest::builder().set_item(Some(item)).build()) + .build(); + + write_requests.push(write_request); + } + + loop { + let output = self + .client + .batch_write_item() + .request_items(RESERVED_USERNAMES_TABLE, write_requests) + .send() + .await + .map_err(|e| Error::AwsSdk(e.into()))?; + + let unprocessed_items_map = match output.unprocessed_items() { + Some(map) => map, + None => break, + }; + + let unprocessed_requests = + match unprocessed_items_map.get(RESERVED_USERNAMES_TABLE) { + Some(requests) => requests, + None => break, + }; + + info!( + "{} unprocessed items, retrying...", + unprocessed_requests.len() + ); + + write_requests = unprocessed_requests.to_vec(); + } + + info!("Batch write item to reserved usernames table succeeded"); + + Ok(()) } pub async fn delete_username_from_reserved_usernames_table( 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 @@ -9,22 +9,25 @@ #[derive(Deserialize)] #[serde(rename_all = "camelCase")] -struct ReservedUsernameMessage { +struct Message { statement: String, - username: String, + payload: T, issued_at: String, } -fn validate_message( +fn validate_and_decode_message( keyserver_message: &str, keyserver_signature: &str, - statement: &[u8], -) -> Result { - let deserialized_message: ReservedUsernameMessage = + expected_statement: &[u8], +) -> Result, Status> { + let deserialized_message: Message = serde_json::from_str(keyserver_message) .map_err(|_| Status::invalid_argument("message format invalid"))?; - if !constant_time_eq(deserialized_message.statement.as_bytes(), statement) { + if !constant_time_eq( + deserialized_message.statement.as_bytes(), + expected_statement, + ) { return Err(Status::invalid_argument("message invalid")); } @@ -69,41 +72,41 @@ keyserver_message: &str, keyserver_signature: &str, ) -> Result<(), Status> { - let deserialized_message = validate_message( + let deserialized_message = validate_and_decode_message::( keyserver_message, keyserver_signature, b"This user is the owner of the following username", )?; - if deserialized_message.username != username { + if deserialized_message.payload != username { return Err(Status::invalid_argument("message invalid")); } Ok(()) } -pub fn validate_add_reserved_username_message( +pub fn validate_add_reserved_usernames_message( keyserver_message: &str, keyserver_signature: &str, -) -> Result { - let deserialized_message = validate_message( +) -> Result, Status> { + let deserialized_message = validate_and_decode_message::>( keyserver_message, keyserver_signature, - b"Add the following username to reserved list", + b"Add the following usernames to reserved list", )?; - Ok(deserialized_message.username) + Ok(deserialized_message.payload) } pub fn validate_remove_reserved_username_message( keyserver_message: &str, keyserver_signature: &str, ) -> Result { - let deserialized_message = validate_message( + let deserialized_message = validate_and_decode_message::( keyserver_message, keyserver_signature, b"Remove the following username from reserved list", )?; - Ok(deserialized_message.username) + Ok(deserialized_message.payload) } 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 @@ -71,7 +71,7 @@ // Called by Ashoat's keyserver to add usernames to the Identity service's // reserved list - rpc AddReservedUsername(AddReservedUsernameRequest) returns (Empty) {} + rpc AddReservedUsernames(AddReservedUsernamesRequest) returns (Empty) {} // Called by Ashoat's keyserver to remove usernames from the Identity // service's reserved list rpc RemoveReservedUsername(RemoveReservedUsernameRequest) returns (Empty) {} @@ -340,9 +340,9 @@ bool tokenValid = 1; } -// AddReservedUsername +// AddReservedUsernames -message AddReservedUsernameRequest { +message AddReservedUsernamesRequest { // Message from Ashoat's keyserver containing the username to be added string message = 1; // Above message signed with Ashoat's keyserver's signing ed25519 key