-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implement session manager within db middleware, update mocker with a …
…new flow with session tokens
- Loading branch information
1 parent
ca86caa
commit 0bc4eb8
Showing
25 changed files
with
1,463 additions
and
247 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
[package] | ||
name = "airdao-gov-portal-db" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
# Local | ||
shared = { path = "../shared/" } | ||
|
||
# Async | ||
tokio = { workspace = true } | ||
|
||
# Ethereum | ||
ethabi = { workspace = true } | ||
ethereum-types = { workspace = true } | ||
|
||
# Crypto | ||
k256 = { workspace = true } | ||
ecdsa = { workspace = true } | ||
sha3 = { workspace = true } | ||
|
||
# Web | ||
axum = { workspace = true } | ||
tower-http = { workspace = true } | ||
reqwest = { workspace = true } | ||
jsonwebtoken = { workspace = true } | ||
|
||
# SerDe | ||
serde = { workspace = true } | ||
serde_json = { workspace = true } | ||
|
||
# Logging | ||
tracing = { workspace = true } | ||
tracing-subscriber = { workspace = true } | ||
log = { workspace = true } | ||
|
||
# Persistence | ||
mongodb = { workspace = true } | ||
|
||
# Misc | ||
backtrace = { workspace = true } | ||
uuid = { workspace = true } | ||
config = { workspace = true } | ||
chrono = { workspace = true } | ||
thiserror = { workspace = true } | ||
hex = { workspace = true } | ||
base64 = { workspace = true } | ||
anyhow = { workspace = true } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"listenAddress": "0.0.0.0:10001", | ||
"session": { | ||
"secret": null, | ||
"duration": 86400 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
use serde::Deserialize; | ||
|
||
use crate::session_token::SessionConfig; | ||
|
||
#[derive(Deserialize, Debug, Clone)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct AppConfig { | ||
pub listen_address: String, | ||
pub session: SessionConfig, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
use axum::{http::StatusCode, response::IntoResponse, Json}; | ||
use serde_json::json; | ||
|
||
#[derive(thiserror::Error, Debug)] | ||
pub enum AppError { | ||
#[error("JSON parse failure: {0}")] | ||
ParseError(#[from] serde_json::Error), | ||
#[error("Generic error: {0}")] | ||
Generic(String), | ||
#[error("Server error: {0}")] | ||
ServerError(#[from] std::io::Error), | ||
} | ||
|
||
impl IntoResponse for AppError { | ||
fn into_response(self) -> axum::response::Response { | ||
let (status, err_msg) = match self { | ||
Self::ParseError(_) | Self::ServerError(_) => ( | ||
StatusCode::INTERNAL_SERVER_ERROR, | ||
"Internal server error".to_owned(), | ||
), | ||
Self::Generic(e) => (StatusCode::UNAUTHORIZED, format!("Request failure: {e}")), | ||
}; | ||
(status, Json(json!({ "error": err_msg }))).into_response() | ||
} | ||
} | ||
|
||
impl From<String> for AppError { | ||
fn from(error_str: String) -> Self { | ||
Self::Generic(error_str) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
mod config; | ||
mod error; | ||
mod mongo_client; | ||
mod server; | ||
mod session_token; | ||
|
||
use shared::{logger, utils}; | ||
|
||
#[tokio::main] | ||
async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
logger::init(); | ||
utils::set_heavy_panic(); | ||
|
||
let config = utils::load_config::<config::AppConfig>("./gov-portal-db").await?; | ||
|
||
server::start(config).await?; | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
use mongodb::{ | ||
bson::Document, | ||
options::{ | ||
ClientOptions, ConnectionString, FindOptions, InsertOneOptions, UpdateModifications, | ||
UpdateOptions, | ||
}, | ||
results::{InsertOneResult, UpdateResult}, | ||
Client, Collection, Cursor, | ||
}; | ||
use serde::Deserialize; | ||
|
||
#[derive(Clone)] | ||
pub struct MongoClient { | ||
collection: Collection<Document>, | ||
pub req_timeout: std::time::Duration, | ||
} | ||
|
||
#[derive(Deserialize, Debug, Default)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct MongoConfig { | ||
/// MongoDB connection url, e.g. mongodb+srv://... | ||
#[serde(default)] | ||
pub url: Option<ConnectionString>, | ||
/// MongoDB database name | ||
pub db_name: String, | ||
/// MongoDB user profiles collection name | ||
pub collection_name: String, | ||
#[serde(default = "default_request_timeout")] | ||
pub request_timeout: u64, | ||
} | ||
|
||
fn default_request_timeout() -> u64 { | ||
10_000 | ||
} | ||
|
||
impl MongoClient { | ||
pub async fn init(mut config: MongoConfig) -> anyhow::Result<Self> { | ||
if let Ok(conn_str) = std::env::var("MONGO_CONN_URL") { | ||
// Parse a connection string into an options struct. | ||
config.url = Some(ConnectionString::parse(conn_str)?); | ||
} | ||
|
||
let client_options = ClientOptions::parse_connection_string( | ||
config | ||
.url | ||
.ok_or_else(|| anyhow::Error::msg("Mongo connection url is missed"))?, | ||
) | ||
.await?; | ||
|
||
// Get a handle to the deployment. | ||
let inner = Client::with_options(client_options)?; | ||
|
||
// Get a handle to a database. | ||
let db = inner.database(&config.db_name); | ||
|
||
// Get a handle to a collection in the database. | ||
let collection = db.collection::<Document>(&config.collection_name); | ||
|
||
Ok(Self { | ||
collection, | ||
req_timeout: std::time::Duration::from_millis(config.request_timeout), | ||
}) | ||
} | ||
|
||
pub async fn find( | ||
&self, | ||
filter: impl Into<Option<Document>>, | ||
find_options: impl Into<Option<FindOptions>>, | ||
) -> Result<Cursor<Document>, mongodb::error::Error> { | ||
self.collection.find(filter, find_options).await | ||
} | ||
|
||
pub async fn insert_one( | ||
&self, | ||
doc: impl std::borrow::Borrow<Document>, | ||
options: impl Into<Option<InsertOneOptions>>, | ||
) -> Result<InsertOneResult, mongodb::error::Error> { | ||
self.collection.insert_one(doc, options).await | ||
} | ||
|
||
pub async fn update_one( | ||
&self, | ||
query: Document, | ||
update: impl Into<UpdateModifications>, | ||
options: impl Into<Option<UpdateOptions>>, | ||
) -> Result<UpdateResult, mongodb::error::Error> { | ||
self.collection.update_one(query, update, options).await | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
use axum::{extract::State, routing::post, Json, Router}; | ||
use serde::Deserialize; | ||
use shared::common::{SessionToken, User}; | ||
use tower_http::cors::CorsLayer; | ||
|
||
use crate::{config::AppConfig, error::AppError, session_token::SessionManager}; | ||
|
||
#[derive(Clone)] | ||
pub struct AppState { | ||
pub config: AppConfig, | ||
pub session_manager: SessionManager, | ||
} | ||
|
||
impl AppState { | ||
pub fn new(config: AppConfig) -> Result<Self, AppError> { | ||
Ok(Self { | ||
session_manager: SessionManager::new(config.session.clone()), | ||
config, | ||
}) | ||
} | ||
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
#[serde(untagged)] | ||
pub enum TokenQuery { | ||
Message { data: String }, | ||
NoMessage {}, | ||
} | ||
|
||
pub async fn start(config: AppConfig) -> Result<(), AppError> { | ||
let addr = config | ||
.listen_address | ||
.parse::<std::net::SocketAddr>() | ||
.expect("Can't parse socket address"); | ||
|
||
let state = AppState::new(config.clone())?; | ||
|
||
let app = Router::new() | ||
.route("/token", post(token_route)) | ||
.route("/user", post(user_route)) | ||
.layer(CorsLayer::permissive()) | ||
.with_state(state); | ||
|
||
tracing::debug!("Server listening on {}", addr); | ||
|
||
let listener = tokio::net::TcpListener::bind(addr).await?; | ||
|
||
axum::serve(listener, app).await.map_err(AppError::from) | ||
} | ||
|
||
async fn token_route( | ||
State(state): State<AppState>, | ||
Json(req): Json<TokenQuery>, | ||
) -> Result<Json<SessionToken>, String> { | ||
tracing::debug!("Request {req:?}"); | ||
|
||
let res = match req { | ||
TokenQuery::Message { data } => state | ||
.session_manager | ||
.acquire_token(&data) | ||
.map_err(|e| e.to_string()), | ||
TokenQuery::NoMessage {} => Err("Resource Not Found".to_owned()), | ||
}; | ||
|
||
tracing::debug!("Response {res:?}"); | ||
|
||
res.map(Json) | ||
} | ||
|
||
async fn user_route( | ||
State(state): State<AppState>, | ||
Json(token): Json<SessionToken>, | ||
) -> Result<Json<User>, String> { | ||
tracing::debug!("Request {token:?}"); | ||
|
||
// TODO: fetch user information from MongoDB | ||
let user = state | ||
.session_manager | ||
.verify_token(&token) | ||
.map(|wallet| User { wallet }) | ||
.map_err(|e| e.to_string()); | ||
|
||
tracing::debug!("Response {user:?}"); | ||
|
||
user.map(Json) | ||
} |
Oops, something went wrong.