diff --git a/server/migrations/002_create_users.sql b/server/migrations/002_create_users.sql index d9c888c..faffd57 100644 --- a/server/migrations/002_create_users.sql +++ b/server/migrations/002_create_users.sql @@ -1,6 +1,6 @@ CREATE TABLE "users" ( user_id uuid NOT NULL PRIMARY KEY, - email text UNIQUE, + email text NOT NULL UNIQUE, password text NOT NULL, admin boolean NOT NULL ); diff --git a/server/src/auth.rs b/server/src/auth.rs index 93dd59b..fc9f16b 100644 --- a/server/src/auth.rs +++ b/server/src/auth.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use argon2::{ password_hash::{rand_core::OsRng, PasswordHasher, SaltString}, - Argon2, + Argon2, PasswordHash, PasswordVerifier, }; use axum::{extract::FromRequestParts, http::request::Parts, RequestPartsExt}; use axum_extra::{ @@ -76,6 +76,16 @@ pub async fn generate_password_hash( Ok(password_hash?) } +pub async fn verify_password_hash( + password: &str, + hash: &str, +) -> Result<(), argon2::password_hash::Error> { + let parsed_hash = PasswordHash::new(hash)?; + Argon2::default().verify_password(password.as_bytes(), &parsed_hash)?; + + Ok(()) +} + pub fn generate_session_token() -> String { let session = ChaCha20Rng::from_os_rng() .sample_iter(&Alphanumeric) diff --git a/server/src/auth/error.rs b/server/src/auth/error.rs index 18f597a..4f50157 100644 --- a/server/src/auth/error.rs +++ b/server/src/auth/error.rs @@ -5,6 +5,7 @@ pub enum AuthError { NoPermssions, InvalidToken, Unexpected, + InvalidPassword, } impl Display for AuthError { @@ -13,6 +14,7 @@ impl Display for AuthError { Self::NoPermssions => write!(f, "{}", "No permissions"), Self::InvalidToken => write!(f, "{}", "Invalid token"), Self::Unexpected => write!(f, "{}", "Unexpected error"), + Self::InvalidPassword => write!(f, "{}", "Password is incorrect"), } } } diff --git a/server/src/database/model/user.rs b/server/src/database/model/user.rs index a77cfc5..0a5c880 100644 --- a/server/src/database/model/user.rs +++ b/server/src/database/model/user.rs @@ -1,4 +1,4 @@ -use sqlx::Postgres; +use sqlx::{PgPool, Postgres}; #[derive(validator::Validate)] pub struct User { @@ -36,6 +36,14 @@ impl User { Ok(user_id) } + + pub async fn get_from_email(transaction: &PgPool, email: String) -> Result { + let user = sqlx::query_as!(Self, "SELECT * FROM users WHERE email = $1", email) + .fetch_one(transaction) + .await?; + + Ok(user) + } } #[derive(Debug)] diff --git a/server/src/routes.rs b/server/src/routes.rs index faa1270..9b99c8b 100644 --- a/server/src/routes.rs +++ b/server/src/routes.rs @@ -14,7 +14,7 @@ pub fn routes() -> Router { } async fn root( - State(state): State, + State(_state): State, permissions: Permissions<'_>, ) -> Result { tracing::info!("{:?}", permissions); diff --git a/server/src/routes/auth.rs b/server/src/routes/auth.rs index 252e8b7..816c34b 100644 --- a/server/src/routes/auth.rs +++ b/server/src/routes/auth.rs @@ -1,15 +1,13 @@ use axum::{extract::State, routing::post, Json, Router}; +use crate::auth::verify_password_hash; use crate::database::model::member::SearchMember; use crate::database::model::Member as DbMember; use crate::database::model::Session as DbSession; use crate::database::model::User as DbUser; use crate::database::model::UserMember as DbUserMember; use crate::model::session::Session; -use crate::{ - auth::{generate_password_hash, Permissions}, - AppState, -}; +use crate::{auth::generate_password_hash, AppState}; pub fn routes() -> Router { Router::new() @@ -17,16 +15,36 @@ pub fn routes() -> Router { .route("/auth/register", post(register)) } +#[derive(serde::Deserialize)] +pub struct LoginRequest { + email: String, + password: String, +} + pub async fn login<'a>( State(state): State, - permissions: Permissions<'a>, - body: String, -) -> Result<(), crate::Error> { - Ok(()) + Json(login_request): Json, +) -> Result { + let db_user = DbUser::get_from_email(&state.pool, login_request.email).await?; + + match verify_password_hash(&login_request.password, &db_user.password).await { + Ok(_) => (), + Err(_err) => return Err(crate::Error::Auth(crate::auth::AuthError::InvalidPassword)), + }; + + // Create session + let mut transaction = state.pool.begin().await?; + + let db_session: DbSession = Session::new(db_user.user_id).into(); + db_session.insert(&mut transaction).await?; + + transaction.commit().await?; + + Ok(db_session.token) } #[derive(serde::Deserialize)] -pub struct AuthRequest { +pub struct RegisterRequest { email: String, password: String, registration_tokens: Vec, @@ -34,8 +52,7 @@ pub struct AuthRequest { pub async fn register<'a>( State(state): State, - permissions: Permissions<'a>, - Json(auth_request): Json, + Json(auth_request): Json, ) -> Result { // Get all the members to link with the user let members = DbMember::search( diff --git a/server/src/routes/member.rs b/server/src/routes/member.rs index dcfe087..f45036b 100644 --- a/server/src/routes/member.rs +++ b/server/src/routes/member.rs @@ -6,8 +6,8 @@ pub mod migrate; pub fn routes() -> Router { Router::new() - // .route("/members/migrate_request", post(migrate::migrate_request)) - // .route("/members/migrate_confirm", post(migrate::migrate_confirm)) + .route("/members/migrate_request", post(migrate::migrate_request)) + .route("/members/migrate_confirm", post(migrate::migrate_confirm)) } pub async fn get_members<'a>( diff --git a/server/src/util/error.rs b/server/src/util/error.rs index e4bd320..9293aa9 100644 --- a/server/src/util/error.rs +++ b/server/src/util/error.rs @@ -35,6 +35,10 @@ impl IntoResponse for Error { StatusCode::INTERNAL_SERVER_ERROR, String::from("Unexpected error occured"), ), + Error::Auth(AuthError::InvalidPassword) => ( + StatusCode::INTERNAL_SERVER_ERROR, + String::from("Invalid password"), + ), Error::Csv(err) => (StatusCode::BAD_REQUEST, err.to_string()), Error::NotFound => ( StatusCode::BAD_REQUEST,