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 @@ -26,6 +26,10 @@ pub const ACCESS_TOKEN_TABLE_VALID_ATTRIBUTE: &str = "valid"; pub const ACCESS_TOKEN_TABLE_TOKEN_ATTRIBUTE: &str = "token"; +pub const NONCE_TABLE: &str = "identity-nonces"; +pub const NONCE_TABLE_PARTITION_KEY: &str = "nonce"; +pub const NONCE_TABLE_CREATED_ATTRIBUTE: &str = "created"; + // Tokio pub const MPSC_CHANNEL_BUFFER_CAPACITY: usize = 1; @@ -38,3 +42,7 @@ // Temporary config pub const AUTH_TOKEN: &str = "COMM_IDENTITY_SERVICE_AUTH_TOKEN"; + +// Nonce + +pub const NONCE_LENGTH: usize = 17; 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,13 +17,15 @@ ACCESS_TOKEN_SORT_KEY, ACCESS_TOKEN_TABLE, ACCESS_TOKEN_TABLE_AUTH_TYPE_ATTRIBUTE, ACCESS_TOKEN_TABLE_CREATED_ATTRIBUTE, ACCESS_TOKEN_TABLE_PARTITION_KEY, ACCESS_TOKEN_TABLE_TOKEN_ATTRIBUTE, - ACCESS_TOKEN_TABLE_VALID_ATTRIBUTE, USERS_TABLE, + ACCESS_TOKEN_TABLE_VALID_ATTRIBUTE, NONCE_TABLE, + NONCE_TABLE_CREATED_ATTRIBUTE, NONCE_TABLE_PARTITION_KEY, USERS_TABLE, USERS_TABLE_DEVICES_ATTRIBUTE, USERS_TABLE_DEVICES_MAP_ATTRIBUTE_NAME, USERS_TABLE_DEVICE_ATTRIBUTE, USERS_TABLE_PARTITION_KEY, USERS_TABLE_REGISTRATION_ATTRIBUTE, USERS_TABLE_USERNAME_ATTRIBUTE, USERS_TABLE_USERNAME_INDEX, USERS_TABLE_USER_PUBLIC_KEY_ATTRIBUTE, USERS_TABLE_WALLET_ADDRESS_ATTRIBUTE, USERS_TABLE_WALLET_ADDRESS_INDEX, }; +use crate::nonce::NonceData; use crate::token::{AccessTokenData, AuthType}; use comm_opaque::Cipher; @@ -465,6 +467,30 @@ } Ok(result) } + + pub async fn add_nonce_to_nonces_table( + &self, + nonce_data: NonceData, + ) -> Result { + let item = HashMap::from([ + ( + NONCE_TABLE_PARTITION_KEY.to_string(), + AttributeValue::S(nonce_data.nonce), + ), + ( + NONCE_TABLE_CREATED_ATTRIBUTE.to_string(), + AttributeValue::S(nonce_data.created.to_rfc3339()), + ), + ]); + self + .client + .put_item() + .table_name(NONCE_TABLE) + .set_item(Some(item)) + .send() + .await + .map_err(|e| Error::AwsSdk(e.into())) + } } #[derive( 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 @@ -9,6 +9,7 @@ mod database; mod interceptor; mod keygen; +mod nonce; mod service; mod token; diff --git a/services/identity/src/nonce.rs b/services/identity/src/nonce.rs new file mode 100644 --- /dev/null +++ b/services/identity/src/nonce.rs @@ -0,0 +1,20 @@ +use chrono::{DateTime, Utc}; +use rand::{ + distributions::{Alphanumeric, DistString}, + CryptoRng, Rng, +}; + +use crate::constants::NONCE_LENGTH; + +pub fn generate_nonce_data(rng: &mut (impl Rng + CryptoRng)) -> NonceData { + NonceData { + nonce: Alphanumeric.sample_string(rng, NONCE_LENGTH), + created: Utc::now(), + } +} + +#[derive(Clone)] +pub struct NonceData { + pub nonce: String, + pub created: DateTime, +} diff --git a/services/identity/src/service.rs b/services/identity/src/service.rs --- a/services/identity/src/service.rs +++ b/services/identity/src/service.rs @@ -23,6 +23,7 @@ use crate::config::CONFIG; use crate::constants::MPSC_CHANNEL_BUFFER_CAPACITY; use crate::database::{DatabaseClient, Error as DBError}; +use crate::nonce::generate_nonce_data; use crate::token::{AccessTokenData, AuthType}; pub use proto::identity_service_server::IdentityServiceServer; @@ -43,9 +44,9 @@ registration_response::Data::PakeLoginResponse as PakeRegistrationLoginResponse, registration_response::Data::PakeRegistrationResponse, CompareUsersRequest, CompareUsersResponse, DeleteUserRequest, DeleteUserResponse, - GetUserIdRequest, GetUserIdResponse, GetUserPublicKeyRequest, - GetUserPublicKeyResponse, LoginRequest, LoginResponse, - PakeLoginRequest as PakeLoginRequestStruct, + GenerateNonceRequest, GenerateNonceResponse, GetUserIdRequest, + GetUserIdResponse, GetUserPublicKeyRequest, GetUserPublicKeyResponse, + LoginRequest, LoginResponse, PakeLoginRequest as PakeLoginRequestStruct, PakeLoginResponse as PakeLoginResponseStruct, RegistrationRequest, RegistrationResponse, VerifyUserTokenRequest, VerifyUserTokenResponse, WalletLoginRequest as WalletLoginRequestStruct, @@ -269,6 +270,24 @@ users_missing_from_identity: mysql_users_vec, })) } + + #[instrument(skip(self))] + async fn generate_nonce( + &self, + _request: Request, + ) -> Result, Status> { + let nonce_data = generate_nonce_data(&mut OsRng); + match self + .client + .add_nonce_to_nonces_table(nonce_data.clone()) + .await + { + Ok(_) => Ok(Response::new(GenerateNonceResponse { + nonce: nonce_data.nonce, + })), + Err(e) => Err(handle_db_error(e)), + } + } } async fn put_token_helper( diff --git a/shared/protos/identity.proto b/shared/protos/identity.proto --- a/shared/protos/identity.proto +++ b/shared/protos/identity.proto @@ -24,6 +24,8 @@ // 1. a list of user IDs that are in DynamoDB but not in the supplied list // 2. a list of user IDs that are in the supplied list but not in DynamoDB rpc CompareUsers(CompareUsersRequest) returns (CompareUsersResponse) {} + // Called by clients to get a nonce for a Sign-In with Ethereum message + rpc GenerateNonce(GenerateNonceRequest) returns (GenerateNonceResponse) {} } // Helper types @@ -180,3 +182,12 @@ repeated string usersMissingFromKeyserver = 1; repeated string usersMissingFromIdentity = 2; } + +// GenerateNonce + +message GenerateNonceRequest { +} + +message GenerateNonceResponse{ + string nonce = 1; +}