diff --git a/Cargo.lock b/Cargo.lock index 0209909..3c90bdf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -381,6 +381,17 @@ dependencies = [ "parking_lot 0.11.2", ] +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.21" @@ -400,6 +411,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ "futures-core", + "futures-macro", "futures-sink", "futures-task", "pin-project-lite", @@ -542,6 +554,7 @@ dependencies = [ name = "homedisk-utils" version = "0.0.0" dependencies = [ + "futures-util", "hex", "log", "serde", @@ -1684,7 +1697,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cfcd319456c4d6ea10087ed423473267e1a071f3bc0aa89f80d60997843c6f0" dependencies = [ - "getrandom", "sha1_smol", ] diff --git a/server/src/auth/login.rs b/server/src/auth/login.rs index 3fab5af..19e4043 100644 --- a/server/src/auth/login.rs +++ b/server/src/auth/login.rs @@ -1,9 +1,40 @@ -use axum::Json; +use axum::{Extension, Json}; use homedisk_types::{ auth::login::{Request, Response}, + config::types::Config, errors::{AuthError, ServerError}, + token::{Claims, Token}, }; +use homedisk_utils::database::{Database, User}; -pub async fn handle(Json(_request): Json) -> Result, ServerError> { - Err(ServerError::AuthError(AuthError::UserAlreadyExists)) +pub async fn handle( + db: Extension, + config: Extension, + Json(request): Json, +) -> Result, ServerError> { + let user = User::new(&request.username, &request.password); + + let response = match db.find_user(&user.username, &user.password).await { + Ok(res) => { + let token = Token::new(config.jwt.secret.as_bytes(), Claims::new(res.id)).unwrap(); + + Response::LoggedIn { + access_token: token.encoded, + } + } + + Err(e) => { + use homedisk_utils::database::Error; + match e { + Error::UserNotFound => return Err(ServerError::AuthError(AuthError::UserNotFound)), + _ => { + return Err(ServerError::AuthError(AuthError::UnknowError( + e.to_string(), + ))) + } + } + } + }; + + Ok(Json(response)) } diff --git a/types/src/errors/auth.rs b/types/src/errors/auth.rs index a16b925..2d0032f 100644 --- a/types/src/errors/auth.rs +++ b/types/src/errors/auth.rs @@ -8,6 +8,9 @@ pub enum Error { #[error("user already exists")] UserAlreadyExists, + #[error("generate jwt token")] + TokenGenerate, + #[error("unknow error")] UnknowError(String), } diff --git a/types/src/errors/mod.rs b/types/src/errors/mod.rs index 66fb239..a8b5a91 100644 --- a/types/src/errors/mod.rs +++ b/types/src/errors/mod.rs @@ -24,6 +24,7 @@ impl axum::response::IntoResponse for ServerError { Self::AuthError(ref err) => match err { AuthError::UserNotFound => StatusCode::BAD_REQUEST, AuthError::UserAlreadyExists => StatusCode::NOT_ACCEPTABLE, + AuthError::TokenGenerate => StatusCode::INTERNAL_SERVER_ERROR, AuthError::UnknowError(_) => StatusCode::INTERNAL_SERVER_ERROR, }, }; diff --git a/utils/Cargo.toml b/utils/Cargo.toml index ddeea03..9e5b67d 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -10,6 +10,7 @@ database = ["sqlx", "uuid"] [dependencies] log = "0.4.16" +futures-util = "0.3.21" [dependencies.serde] version = "1.0.136" @@ -33,7 +34,7 @@ features = ["runtime-tokio-rustls", "sqlite"] optional = true [dependencies.uuid] version = "1.0.0" -features = ["v4", "v5"] +features = ["v5"] optional = true [dev-dependencies.tokio] diff --git a/utils/src/database/error.rs b/utils/src/database/error.rs index e1ca816..572ebca 100644 --- a/utils/src/database/error.rs +++ b/utils/src/database/error.rs @@ -4,8 +4,10 @@ use crate::crypto; #[derive(Debug)] pub enum Error { + UserNotFound, Crypto(crypto::Error), SQLx(sqlx::Error), + Io(std::io::Error), } impl From for Error { @@ -20,11 +22,19 @@ impl From for Error { } } +impl From for Error { + fn from(err: std::io::Error) -> Self { + Error::Io(err) + } +} + impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { + Error::UserNotFound => write!(f, "user not found"), Error::Crypto(err) => write!(f, "crypto error: {}", err), Error::SQLx(err) => write!(f, "sqlx error: {}", err), + Error::Io(err) => write!(f, "error: {}", err), } } } diff --git a/utils/src/database/sqlite.rs b/utils/src/database/sqlite.rs index f79f609..fdb6e9a 100644 --- a/utils/src/database/sqlite.rs +++ b/utils/src/database/sqlite.rs @@ -1,5 +1,7 @@ +use futures_util::TryStreamExt; use log::debug; -use sqlx::{sqlite::SqliteQueryResult, Executor, SqlitePool}; +use sqlx::{sqlite::SqliteQueryResult, Executor, Row, SqlitePool}; +use user::User; use super::{user, Error}; @@ -27,7 +29,7 @@ impl Database { /// ```ignore /// use homedisk_utils::database::{Database, User}; /// - /// let user = User::new("medzik", "SuperSecretPassword123"); + /// let user = User::new("username", "password"); /// db.create_user(&user).await?; /// ``` pub async fn create_user(&self, user: &user::User) -> Result { @@ -40,6 +42,30 @@ impl Database { Ok(self.conn.execute(query).await?) } + + /// Find user + pub async fn find_user(&self, username: &str, password: &str) -> Result { + debug!("search for user - {}", username); + + let query = + sqlx::query_as::<_, User>("SELECT * FROM user WHERE username = ? AND password = ?") + .bind(username) + .bind(password); + + let mut stream = self.conn.fetch(query); + + let row = stream.try_next().await?.ok_or(Error::UserNotFound)?; + + let id = row.try_get("id")?; + let username = row.try_get("username")?; + let password = row.try_get("password")?; + + Ok(User { + id, + username, + password, + }) + } } #[cfg(test)] @@ -54,15 +80,7 @@ mod tests { Database::open("sqlite::memory:").await.expect("open db") } - #[tokio::test] - async fn open_db_in_memory() { - open_db().await; - } - - #[tokio::test] - async fn create_user() { - let db = open_db().await; - + async fn new_user(db: &Database) { // create user table db.conn .execute(sqlx::query( @@ -72,7 +90,67 @@ mod tests { .expect("create tables"); // create new user - let user = User::new("medzik", "SuperSecretPassword123"); + let user = User::new("medzik", "Qwerty1234!"); db.create_user(&user).await.expect("create user"); } + + #[tokio::test] + async fn open_db_in_memory() { + open_db().await; + } + + #[tokio::test] + async fn create_user() { + let db = open_db().await; + + new_user(&db).await; + } + + #[tokio::test] + async fn find_user() { + let db = open_db().await; + + new_user(&db).await; + + let user = User::new("medzik", "Qwerty1234!"); + + let res = db + .find_user(&user.username, &user.password) + .await + .expect("find user"); + + assert_eq!(res.password, user.password) + } + + #[tokio::test] + async fn find_user_wrong_password() { + let db = open_db().await; + + new_user(&db).await; + + let user = User::new("medzik", "wrong password 123!"); + + let err = db + .find_user(&user.username, &user.password) + .await + .unwrap_err(); + + assert_eq!(err.to_string(), "user not found") + } + + #[tokio::test] + async fn find_user_wrong_username() { + let db = open_db().await; + + new_user(&db).await; + + let user = User::new("not_exists_user", "secret password of a not existing user"); + + let err = db + .find_user(&user.username, &user.password) + .await + .unwrap_err(); + + assert_eq!(err.to_string(), "user not found") + } }