diff options
| author | Ellie Huxtable <e@elm.sh> | 2021-04-20 17:07:11 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-04-20 16:07:11 +0000 |
| commit | 34888827f8a06de835cbe5833a06914f28cce514 (patch) | |
| tree | 8b56f20e50065cd2c222d5e8e067ec55cf1947a1 /src/server/handlers | |
| parent | Optimise docker (#34) (diff) | |
| download | atuin-34888827f8a06de835cbe5833a06914f28cce514.zip | |
Switch to Warp + SQLx, use async, switch to Rust stable (#36)
* Switch to warp + sql, use async and stable rust
* Update CI to use stable
Diffstat (limited to 'src/server/handlers')
| -rw-r--r-- | src/server/handlers/history.rs | 89 | ||||
| -rw-r--r-- | src/server/handlers/mod.rs | 6 | ||||
| -rw-r--r-- | src/server/handlers/user.rs | 140 |
3 files changed, 235 insertions, 0 deletions
diff --git a/src/server/handlers/history.rs b/src/server/handlers/history.rs new file mode 100644 index 00000000..4fd6f03f --- /dev/null +++ b/src/server/handlers/history.rs @@ -0,0 +1,89 @@ +use std::convert::Infallible; + +use warp::{http::StatusCode, reply::json}; + +use crate::api::{ + AddHistoryRequest, CountResponse, ErrorResponse, SyncHistoryRequest, SyncHistoryResponse, +}; +use crate::server::database::Database; +use crate::server::models::{NewHistory, User}; + +pub async fn count( + user: User, + db: impl Database + Clone + Send + Sync, +) -> Result<Box<dyn warp::Reply>, Infallible> { + db.count_history(&user).await.map_or( + Ok(Box::new(ErrorResponse::reply( + "failed to query history count", + StatusCode::INTERNAL_SERVER_ERROR, + ))), + |count| Ok(Box::new(json(&CountResponse { count }))), + ) +} + +pub async fn list( + req: SyncHistoryRequest, + user: User, + db: impl Database + Clone + Send + Sync, +) -> Result<Box<dyn warp::Reply>, Infallible> { + let history = db + .list_history( + &user, + req.sync_ts.naive_utc(), + req.history_ts.naive_utc(), + req.host, + ) + .await; + + if let Err(e) = history { + error!("failed to load history: {}", e); + let resp = + ErrorResponse::reply("failed to load history", StatusCode::INTERNAL_SERVER_ERROR); + let resp = Box::new(resp); + return Ok(resp); + } + + let history: Vec<String> = history + .unwrap() + .iter() + .map(|i| i.data.to_string()) + .collect(); + + debug!( + "loaded {} items of history for user {}", + history.len(), + user.id + ); + + Ok(Box::new(json(&SyncHistoryResponse { history }))) +} + +pub async fn add( + req: Vec<AddHistoryRequest>, + user: User, + db: impl Database + Clone + Send + Sync, +) -> Result<Box<dyn warp::Reply>, Infallible> { + debug!("request to add {} history items", req.len()); + + let history: Vec<NewHistory> = req + .iter() + .map(|h| NewHistory { + client_id: h.id.as_str(), + user_id: user.id, + hostname: h.hostname.as_str(), + timestamp: h.timestamp.naive_utc(), + data: h.data.as_str(), + }) + .collect(); + + if let Err(e) = db.add_history(&history).await { + error!("failed to add history: {}", e); + + return Ok(Box::new(ErrorResponse::reply( + "failed to add history", + StatusCode::INTERNAL_SERVER_ERROR, + ))); + }; + + Ok(Box::new(warp::reply())) +} diff --git a/src/server/handlers/mod.rs b/src/server/handlers/mod.rs new file mode 100644 index 00000000..3c20538c --- /dev/null +++ b/src/server/handlers/mod.rs @@ -0,0 +1,6 @@ +pub mod history; +pub mod user; + +pub const fn index() -> &'static str { + "\"Through the fathomless deeps of space swims the star turtle Great A\u{2019}Tuin, bearing on its back the four giant elephants who carry on their shoulders the mass of the Discworld.\"\n\t-- Sir Terry Pratchett" +} diff --git a/src/server/handlers/user.rs b/src/server/handlers/user.rs new file mode 100644 index 00000000..782d7dbd --- /dev/null +++ b/src/server/handlers/user.rs @@ -0,0 +1,140 @@ +use std::convert::Infallible; + +use sodiumoxide::crypto::pwhash::argon2id13; +use uuid::Uuid; +use warp::http::StatusCode; +use warp::reply::json; + +use crate::api::{ + ErrorResponse, LoginRequest, LoginResponse, RegisterRequest, RegisterResponse, UserResponse, +}; +use crate::server::database::Database; +use crate::server::models::{NewSession, NewUser}; +use crate::settings::Settings; +use crate::utils::hash_secret; + +pub fn verify_str(secret: &str, verify: &str) -> bool { + sodiumoxide::init().unwrap(); + + let mut padded = [0_u8; 128]; + secret.as_bytes().iter().enumerate().for_each(|(i, val)| { + padded[i] = *val; + }); + + match argon2id13::HashedPassword::from_slice(&padded) { + Some(hp) => argon2id13::pwhash_verify(&hp, verify.as_bytes()), + None => false, + } +} + +pub async fn get( + username: String, + db: impl Database + Clone + Send + Sync, +) -> Result<Box<dyn warp::Reply>, Infallible> { + let user = match db.get_user(username).await { + Ok(user) => user, + Err(e) => { + debug!("user not found: {}", e); + return Ok(Box::new(ErrorResponse::reply( + "user not found", + StatusCode::NOT_FOUND, + ))); + } + }; + + Ok(Box::new(warp::reply::json(&UserResponse { + username: user.username, + }))) +} + +pub async fn register( + register: RegisterRequest, + settings: Settings, + db: impl Database + Clone + Send + Sync, +) -> Result<Box<dyn warp::Reply>, Infallible> { + if !settings.server.open_registration { + return Ok(Box::new(ErrorResponse::reply( + "this server is not open for registrations", + StatusCode::BAD_REQUEST, + ))); + } + + let hashed = hash_secret(register.password.as_str()); + + let new_user = NewUser { + email: register.email, + username: register.username, + password: hashed, + }; + + let user_id = match db.add_user(new_user).await { + Ok(id) => id, + Err(e) => { + error!("failed to add user: {}", e); + return Ok(Box::new(ErrorResponse::reply( + "failed to add user", + StatusCode::BAD_REQUEST, + ))); + } + }; + + let token = Uuid::new_v4().to_simple().to_string(); + + let new_session = NewSession { + user_id, + token: token.as_str(), + }; + + match db.add_session(&new_session).await { + Ok(_) => Ok(Box::new(json(&RegisterResponse { session: token }))), + Err(e) => { + error!("failed to add session: {}", e); + Ok(Box::new(ErrorResponse::reply( + "failed to register user", + StatusCode::BAD_REQUEST, + ))) + } + } +} + +pub async fn login( + login: LoginRequest, + db: impl Database + Clone + Send + Sync, +) -> Result<Box<dyn warp::Reply>, Infallible> { + let user = match db.get_user(login.username.clone()).await { + Ok(u) => u, + Err(e) => { + error!("failed to get user {}: {}", login.username.clone(), e); + + return Ok(Box::new(ErrorResponse::reply( + "user not found", + StatusCode::NOT_FOUND, + ))); + } + }; + + let session = match db.get_user_session(&user).await { + Ok(u) => u, + Err(e) => { + error!("failed to get session for {}: {}", login.username, e); + + return Ok(Box::new(ErrorResponse::reply( + "user not found", + StatusCode::NOT_FOUND, + ))); + } + }; + + let verified = verify_str(user.password.as_str(), login.password.as_str()); + + if !verified { + return Ok(Box::new(ErrorResponse::reply( + "user not found", + StatusCode::NOT_FOUND, + ))); + } + + Ok(Box::new(warp::reply::json(&LoginResponse { + session: session.token, + }))) +} |
