Skip to content

Commit

Permalink
restrict unique emails in user registration, add integration test for db
Browse files Browse the repository at this point in the history
  • Loading branch information
Kirill-K-1 committed Feb 8, 2024
1 parent 912a428 commit 74dc4bb
Show file tree
Hide file tree
Showing 13 changed files with 985 additions and 912 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ futures-util = "0"
ethabi = "18.0.0"
ethereum-types = "0.14.1"
jsonrpc-core = "18.0.0"
web3 = { version = "0.19" }

# Crypto
k256 = { version = "0.13" }
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ Supported logging levels: `info`, `debug`, `trace`. Defaults to `info` log level

Integration tests will require running local hardhat node with deployed contracts.

To run integration tests use `cargo test --features enable-integration-tests`
To run integration tests use `cargo test --features enable-integration-tests -- --test-threads=1`

## Run

Expand Down
7 changes: 7 additions & 0 deletions gov-portal-db/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ futures-util = { workspace = true }
# Ethereum
ethabi = { workspace = true }
ethereum-types = { workspace = true }
web3 = { workspace = true, optional = true }

# Crypto
k256 = { workspace = true }
Expand Down Expand Up @@ -53,3 +54,9 @@ hex = { workspace = true }
base64 = { workspace = true }
anyhow = { workspace = true }
dotenv = { workspace = true }

[dev-dependencies]
assert_matches = { workspace = true }

[features]
enable-integration-tests = ["web3"]
4 changes: 4 additions & 0 deletions gov-portal-db/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod config;
pub mod error;
pub mod session_token;
pub mod users_manager;
3 changes: 2 additions & 1 deletion gov-portal-db/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

let config = utils::load_config::<config::AppConfig>("./gov-portal-db").await?;

let users_manager = Arc::new(UsersManager::new(&config).await?);
let users_manager =
Arc::new(UsersManager::new(&config.mongo, config.registration.clone()).await?);

server::start(config, users_manager.clone()).await?;

Expand Down
6 changes: 3 additions & 3 deletions gov-portal-db/src/session_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ use tokio::time::Duration;

#[derive(Clone, Debug)]
pub struct SessionManager {
config: SessionConfig,
pub config: SessionConfig,
}

#[derive(Clone, Debug, Deserialize)]
pub struct SessionConfig {
/// Session lifetime in seconds
#[serde(deserialize_with = "shared::utils::de_secs_duration")]
lifetime: Duration,
pub lifetime: Duration,
/// Secret phrase used to generate and verify session JWT tokens
secret: String,
pub secret: String,
}

impl SessionManager {
Expand Down
32 changes: 17 additions & 15 deletions gov-portal-db/src/users_manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ use tokio::time::Duration;

use shared::common::{RawUserRegistrationToken, User, UserRegistrationToken};

use crate::config::AppConfig;
use mongo_client::MongoClient;
use mongo_client::{MongoClient, MongoConfig};

const MONGO_DUPLICATION_ERROR: i32 = 11000;

Expand All @@ -26,24 +25,24 @@ const MONGO_DUPLICATION_ERROR: i32 = 11000;
pub struct UserRegistrationConfig {
/// Lifetime for which JWT registration token will be valid to register new user profile
#[serde(deserialize_with = "shared::utils::de_secs_duration")]
lifetime: Duration,
pub lifetime: Duration,
/// Secret being used to sign JWT registration token
secret: String,
pub secret: String,
/// User profile attributes verification settings
user_profile_attributes: UserProfileAttributes,
pub user_profile_attributes: UserProfileAttributes,
}

/// Contains settings to verify user profile [`User`] attributes
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct UserProfileAttributes {
name_max_length: usize,
role_max_length: usize,
email_max_length: usize,
telegram_max_length: usize,
twitter_max_length: usize,
bio_max_length: usize,
avatar_url_max_length: usize,
pub name_max_length: usize,
pub role_max_length: usize,
pub email_max_length: usize,
pub telegram_max_length: usize,
pub twitter_max_length: usize,
pub bio_max_length: usize,
pub avatar_url_max_length: usize,
}

/// User profiles manager which provides read/write access to user profile data stored MongoDB
Expand All @@ -54,12 +53,15 @@ pub struct UsersManager {

impl UsersManager {
/// Constructs [`UsersManager`] with provided confuguration
pub async fn new(config: &AppConfig) -> anyhow::Result<Self> {
let mongo_client = MongoClient::new(&config.mongo).await?;
pub async fn new(
mongo_config: &MongoConfig,
registration_config: UserRegistrationConfig,
) -> anyhow::Result<Self> {
let mongo_client = MongoClient::new(mongo_config).await?;

Ok(Self {
mongo_client,
registration_config: config.registration.clone(),
registration_config,
})
}

Expand Down
20 changes: 19 additions & 1 deletion gov-portal-db/src/users_manager/mongo_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use serde::Deserialize;
#[derive(Clone)]
pub struct MongoClient {
/// Collection being accessed by insert/update/query requests
collection: Collection<Document>,
pub collection: Collection<Document>,
/// MongoDB query maximum timeout
pub req_timeout: std::time::Duration,
}
Expand Down Expand Up @@ -116,7 +116,11 @@ async fn initialize_collection(
.any(|it| it.as_str() == collection_name)
{
db.create_collection(collection_name, None).await?;
}

let indexes = collection.list_index_names().await?;

if !indexes.iter().any(|index| index == "wallet_1") {
let _ = collection
.create_index(
IndexModel::builder()
Expand All @@ -130,5 +134,19 @@ async fn initialize_collection(
.await?;
}

if !indexes.iter().any(|index| index == "email_1") {
let _ = collection
.create_index(
IndexModel::builder()
.keys(bson::doc! {
"email": 1,
})
.options(Some(IndexOptions::builder().unique(true).build()))
.build(),
None,
)
.await?;
}

Ok(collection)
}
70 changes: 70 additions & 0 deletions gov-portal-db/tests/test_db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#![cfg(feature = "enable-integration-tests")]
use airdao_gov_portal_db::users_manager::*;
use assert_matches::assert_matches;
use shared::common::User;
use web3::types::Address;

#[tokio::test]
async fn test_register_user() -> Result<(), anyhow::Error> {
let mongo_config = mongo_client::MongoConfig {
url: Some("mongodb://localhost:27017".to_owned()),
db: "AirDAOGovPortal_IntegrationTest".to_owned(),
collection: "Users".to_owned(),
request_timeout: 10_000,
};

let registration_config = UserRegistrationConfig {
secret: "IntegrationTestRegistrationSecretForJWT".to_owned(),
lifetime: std::time::Duration::from_secs(600),
user_profile_attributes: UserProfileAttributes {
name_max_length: 64,
role_max_length: 50,
email_max_length: 64,
telegram_max_length: 32,
twitter_max_length: 32,
bio_max_length: 250,
avatar_url_max_length: 250,
},
};

let users_manager = UsersManager::new(&mongo_config, registration_config).await?;

let addr_1 = Address::from_low_u64_le(0);
let addr_2 = Address::from_low_u64_le(1);

users_manager
.register_user(&User {
wallet: addr_1,
email: Some("test@test.com".try_into()?),
..Default::default()
})
.await?;

// Verify that same wallet can't be registered twice
assert_matches!(
users_manager
.register_user(&User {
wallet: addr_1,
email: Some("test1@test.com".try_into()?),
..Default::default()
})
.await,
Err(error::Error::UserAlreadyExist)
);

// Verify that same email can't be registered twice
assert_matches!(
users_manager
.register_user(&User {
wallet: addr_2,
email: Some("test@test.com".try_into()?),
..Default::default()
})
.await,
Err(error::Error::UserAlreadyExist)
);

users_manager.mongo_client.collection.drop(None).await?;

Ok(())
}
2 changes: 1 addition & 1 deletion user-verifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ tokio = { workspace = true }
# Ethereum
ethabi = { workspace = true }
ethereum-types = { workspace = true }
web3 = { version = "0.19", optional = true }
web3 = { workspace = true, optional = true }
jsonrpc-core = { workspace = true, optional = true }

# Crypto
Expand Down
Loading

0 comments on commit 74dc4bb

Please sign in to comment.