Skip to content

Commit

Permalink
implement user registration
Browse files Browse the repository at this point in the history
  • Loading branch information
Kirill-K-1 committed Jan 7, 2024
1 parent 21e6db4 commit a62f04a
Show file tree
Hide file tree
Showing 20 changed files with 546 additions and 47 deletions.
25 changes: 25 additions & 0 deletions Cargo.lock

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

9 changes: 6 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ resolver = "2"
[workspace.dependencies]
# Async
tokio = { version = "1", features = ["rt-multi-thread", "time", "macros"] }
futures-util = "0"

# Ethereum
ethabi = "18.0.0"
Expand All @@ -24,16 +25,18 @@ sha3 = "0"
# Web
axum = "0"
tower-http = { version = "0", features = ["cors"] }
reqwest = { version = "0.11", features = ["rustls-tls", "json", "deflate"] }
reqwest = { version = "0", features = ["rustls-tls", "json", "deflate"] }
jsonwebtoken = "9"

# SerDe
serde = { version = "1.0", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde-email = "3"
bson = { version = "2", features = ["chrono-0_4"] }

# Logging
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = [ "env-filter", "registry" ] }
tracing-subscriber = { version = "0", features = [ "env-filter", "registry" ] }
log = "0"

# Persistence
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,15 @@ Default configuration could be found in `./gov-portal-mocker/config/default.json
- `listenAddress`: host:port to run the gov portal database middleware at. Defaults to `localhost:10001`
- `session`: session manager configuration
- `secret`: secret to generate session tokens. Should be set before app start
- `duration`: lifetime duration in seconds for which the session token will be valid to access database by using middleware. Defaults to 1 day
- `lifetime`: lifetime duration in seconds for which the session token will be valid to access database by using middleware. Defaults to 1 day
- `registration`: registration manager configuration
- `secret`: secret to generate registration tokens to be sent to user specified email for verification. Should be set before app start
- `lifetime`: lifetime duration in seconds for which the registration token will be valid to register user by using middleware. Defaults to 10 min
- `mongo`: MongoDB configuration
- `url`: mongo connection url in format `mongodb://host:port`. Should be set before app start
- `db`: database name with users collection. Defaults to `AirDAOGovPortal`
- `collection`: collection name with user profiles. Defaults to `Users`
- `requestTimeout`: maximum amount of time given to execute MongoDB requests before timeout. Defaults to 10 sec

#### Override configuration

Expand Down
3 changes: 3 additions & 0 deletions gov-portal-db/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ shared = { path = "../shared/" }

# Async
tokio = { workspace = true }
futures-util = { workspace = true }

# Ethereum
ethabi = { workspace = true }
Expand All @@ -30,6 +31,8 @@ jsonwebtoken = { workspace = true }
# SerDe
serde = { workspace = true }
serde_json = { workspace = true }
serde-email = { workspace = true }
bson = { workspace = true }

# Logging
tracing = { workspace = true }
Expand Down
6 changes: 5 additions & 1 deletion gov-portal-db/config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
"listenAddress": "0.0.0.0:10001",
"session": {
"secret": null,
"duration": 86400
"lifetime": 86400
},
"registration": {
"secret": null,
"lifetime": 600
},
"mongo": {
"url": null,
Expand Down
6 changes: 5 additions & 1 deletion gov-portal-db/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use serde::Deserialize;

use crate::{mongo_client::MongoConfig, session_token::SessionConfig};
use crate::{
session_token::SessionConfig,
users_manager::{mongo_client::MongoConfig, UserRegistrationConfig},
};

#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct AppConfig {
pub listen_address: String,
pub session: SessionConfig,
pub registration: UserRegistrationConfig,
pub mongo: MongoConfig,
}
9 changes: 7 additions & 2 deletions gov-portal-db/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
mod config;
mod error;
mod mongo_client;
mod server;
mod session_token;
mod users_manager;

use std::sync::Arc;

use shared::{logger, utils};
use users_manager::UsersManager;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
Expand All @@ -16,7 +19,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

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

server::start(config).await?;
let users_manager = Arc::new(UsersManager::new(&config).await?);

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

Ok(())
}
94 changes: 78 additions & 16 deletions gov-portal-db/src/server.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
use axum::{extract::State, routing::post, Json, Router};
use serde::Deserialize;
use shared::common::{SessionToken, User};
use std::sync::Arc;
use tower_http::cors::CorsLayer;

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

use crate::{
config::AppConfig, error::AppError, mongo_client::MongoClient, session_token::SessionManager,
config::AppConfig, error::AppError, session_token::SessionManager, users_manager::UsersManager,
};

#[derive(Clone)]
pub struct AppState {
pub config: AppConfig,
pub session_manager: SessionManager,
pub mongo_db: MongoClient,
pub users_manager: Arc<UsersManager>,
}

impl AppState {
pub async fn new(config: AppConfig) -> Result<Self, AppError> {
pub async fn new(
config: AppConfig,
users_manager: Arc<UsersManager>,
) -> Result<Self, AppError> {
Ok(Self {
session_manager: SessionManager::new(config.session.clone()),
mongo_db: MongoClient::init(&config.mongo)
.await
.map_err(AppError::generic)?,
users_manager,
config,
})
}
Expand All @@ -33,17 +36,26 @@ pub enum TokenQuery {
NoMessage {},
}

pub async fn start(config: AppConfig) -> Result<(), AppError> {
#[derive(Debug, Deserialize)]
pub struct VerifyEmailReguest {
pub email: serde_email::Email,
#[serde(flatten)]
pub session: SessionToken,
}

pub async fn start(config: AppConfig, users_manager: Arc<UsersManager>) -> Result<(), AppError> {
let addr = config
.listen_address
.parse::<std::net::SocketAddr>()
.expect("Can't parse socket address");

let state = AppState::new(config).await?;
let state = AppState::new(config, users_manager).await?;

let app = Router::new()
.route("/token", post(token_route))
.route("/user", post(user_route))
.route("/verify-email", post(verify_email_route))
.route("/register", post(register_route))
.layer(CorsLayer::permissive())
.with_state(state);

Expand All @@ -58,7 +70,7 @@ async fn token_route(
State(state): State<AppState>,
Json(req): Json<TokenQuery>,
) -> Result<Json<SessionToken>, String> {
tracing::debug!("Request {req:?}");
tracing::debug!("[/token] Request {req:?}");

let res = match req {
TokenQuery::Message { data } => state
Expand All @@ -68,7 +80,7 @@ async fn token_route(
TokenQuery::NoMessage {} => Err("Resource Not Found".to_owned()),
};

tracing::debug!("Response {res:?}");
tracing::debug!("[/token] Response {res:?}");

res.map(Json)
}
Expand All @@ -77,16 +89,66 @@ async fn user_route(
State(state): State<AppState>,
Json(token): Json<SessionToken>,
) -> Result<Json<User>, String> {
tracing::debug!("Request {token:?}");
tracing::debug!("[/user] Request {token:?}");

let res = match state.session_manager.verify_token(&token) {
Ok(wallet) => state
.users_manager
.get_user_by_wallet(wallet)
.await
.map_err(|e| format!("Unable to acquire user information. Error: {e}")),

Err(e) => Err(format!("User request failure. Error: {e}")),
};

tracing::debug!("[/user] Response {res:?}");

res.map(Json)
}

async fn verify_email_route(
State(state): State<AppState>,
Json(req): Json<VerifyEmailReguest>,
) -> Result<Json<UserRegistrationToken>, String> {
tracing::debug!("[/verify-email] Request {req:?}");

// TODO: fetch user information from MongoDB
let user = state
.session_manager
.verify_token(&token)
.map(|wallet| User { wallet })
.map_err(|e| e.to_string());
.verify_token(&req.session)
.and_then(|wallet| {
state
.users_manager
.acquire_registration_token(wallet, req.email)
})
.map_err(|e| format!("Verify email request failure. Error: {e}"));

tracing::debug!("Response {user:?}");
tracing::debug!("[/verify-email] Response {user:?}");

user.map(Json)
}

async fn register_route(
State(state): State<AppState>,
Json(reg_token): Json<UserRegistrationToken>,
) -> Result<Json<User>, String> {
tracing::debug!("[/register] Request {reg_token:?}");

// TODO: fetch user information from MongoDB
let res = match state.users_manager.verify_registration_token(&reg_token) {
Ok(user) => {
state
.users_manager
.register_user(&user)
.await
.map_err(|e| format!("User registration failure. Error: {e}"))?;
Ok(user)
}

Err(e) => Err(format!("Wrong registration request. Error: {e}")),
};

tracing::debug!("[/register] Response {res:?}");

res.map(Json)
}
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 @@ -13,7 +13,7 @@ pub struct SessionManager {
#[derive(Clone, Debug, Deserialize)]
pub struct SessionConfig {
#[serde(deserialize_with = "shared::utils::de_secs_duration")]
duration: Duration,
lifetime: Duration,
secret: String,
}

Expand Down Expand Up @@ -41,7 +41,7 @@ impl SessionManager {
SessionToken::new(
RawSessionToken {
checksum_wallet: shared::utils::get_checksum_address(&wallet),
expires_at: (Utc::now() + self.config.duration).timestamp_millis() as u64,
expires_at: (Utc::now() + self.config.lifetime).timestamp_millis() as u64,
},
self.config.secret.as_bytes(),
)
Expand All @@ -65,7 +65,7 @@ mod tests {
#[test]
fn test_acquire_token() {
let session_manager = SessionManager::new(super::SessionConfig {
duration: tokio::time::Duration::from_secs(180),
lifetime: tokio::time::Duration::from_secs(180),
secret: "TestSecretForJWT".to_owned(),
});

Expand Down
15 changes: 15 additions & 0 deletions gov-portal-db/src/users_manager/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("User is already registered")]
UserAlreadyExist,
#[error("User is not registered")]
UserNotFound,
#[error("DB comminication failure: {0}")]
DbCommunicationFailure(#[from] mongodb::error::Error),
#[error("Serialization error: {0}")]
BsonSerialization(#[from] bson::ser::Error),
#[error("Deserialization error: {0}")]
BsonDeserialization(#[from] bson::de::Error),
#[error("Request timeout")]
Timeout(#[from] tokio::time::error::Elapsed),
}
Loading

0 comments on commit a62f04a

Please sign in to comment.