From d5aceb60b4301c4b711dc54f50d022e34e06ecec Mon Sep 17 00:00:00 2001 From: realaravinth Date: Tue, 29 Jun 2021 19:42:07 +0530 Subject: [PATCH] sign in with email --- Cargo.lock | 2 +- Cargo.toml | 1 + src/api/v1/auth.rs | 76 +++++++++++++++++++++-------- src/api/v1/tests/auth.rs | 9 ++-- src/errors.rs | 3 ++ src/tests/mod.rs | 2 +- templates/auth/register/ts/index.ts | 2 +- 7 files changed, 68 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab1e4732..d738de68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -441,7 +441,7 @@ dependencies = [ [[package]] name = "argon2-creds" version = "0.2.1" -source = "git+https://github.com/realaravinth/argon2-creds?branch=master#c899181a1bdb65134cc329f26c5dd3601d89fc45" +source = "git+https://github.com/realaravinth/argon2-creds?branch=master#3330634832150ad2af1c31768ed3b84e69f4c8ad" dependencies = [ "ammonia", "derive_builder 0.10.2", diff --git a/Cargo.toml b/Cargo.toml index 2cce9f3d..21b0899a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ futures = "0.3.14" sqlx = { version = "0.4.0", features = [ "runtime-actix-rustls", "postgres", "time", "offline" ] } argon2-creds = { branch = "master", git = "https://github.com/realaravinth/argon2-creds"} +#argon2-creds = { version="*", path = "../../argon2-creds/" } config = "0.11" validator = { version = "0.13", features = ["derive"]} diff --git a/src/api/v1/auth.rs b/src/api/v1/auth.rs index b5869190..4fcd1ec5 100644 --- a/src/api/v1/auth.rs +++ b/src/api/v1/auth.rs @@ -19,6 +19,7 @@ use actix_identity::Identity; use actix_web::http::header; use actix_web::{web, HttpResponse, Responder}; use serde::{Deserialize, Serialize}; +//use futures::{future::TryFutureExt, join}; use super::mcaptcha::get_random; use crate::errors::*; @@ -60,7 +61,9 @@ pub mod runners { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Login { - pub username: String, + // login accepts both username and email under "username field" + // TODO update all instances where login is used + pub login: String, pub password: String, } @@ -70,28 +73,59 @@ pub mod runners { } /// returns Ok(()) when everything checks out and the user is authenticated. Erros otherwise - pub async fn login_runner(payload: &Login, data: &AppData) -> ServiceResult<()> { + pub async fn login_runner(payload: Login, data: &AppData) -> ServiceResult { use argon2_creds::Config; use sqlx::Error::RowNotFound; - let rec = sqlx::query_as!( - Password, - r#"SELECT password FROM mcaptcha_users WHERE name = ($1)"#, - &payload.username, - ) - .fetch_one(&data.db) - .await; - - match rec { - Ok(s) => { - if Config::verify(&s.password, &payload.password)? { - Ok(()) - } else { - Err(ServiceError::WrongPassword) - } + let verify = |stored: &str, received: &str| { + if Config::verify(&stored, &received)? { + Ok(()) + } else { + Err(ServiceError::WrongPassword) + } + }; + + if payload.login.contains("@") { + #[derive(Clone, Debug)] + struct EmailLogin { + name: String, + password: String, + } + + let email_fut = sqlx::query_as!( + EmailLogin, + r#"SELECT name, password FROM mcaptcha_users WHERE email = ($1)"#, + &payload.login, + ) + .fetch_one(&data.db) + .await; + + match email_fut { + Ok(s) => { + verify(&s.password, &payload.password)?; + Ok(s.name) + } + + Err(RowNotFound) => Err(ServiceError::AccountNotFound), + Err(_) => Err(ServiceError::InternalServerError), + } + } else { + let username_fut = sqlx::query_as!( + Password, + r#"SELECT password FROM mcaptcha_users WHERE name = ($1)"#, + &payload.login, + ) + .fetch_one(&data.db) + .await; + + match username_fut { + Ok(s) => { + verify(&s.password, &payload.password)?; + Ok(payload.login) + } + Err(RowNotFound) => Err(ServiceError::AccountNotFound), + Err(_) => Err(ServiceError::InternalServerError), } - Err(RowNotFound) => Err(ServiceError::UsernameNotFound), - Err(_) => Err(ServiceError::InternalServerError), } } @@ -181,8 +215,8 @@ async fn login( payload: web::Json, data: AppData, ) -> ServiceResult { - runners::login_runner(&payload, &data).await?; - id.remember(payload.into_inner().username); + let username = runners::login_runner(payload.into_inner(), &data).await?; + id.remember(username); Ok(HttpResponse::Ok()) } diff --git a/src/api/v1/tests/auth.rs b/src/api/v1/tests/auth.rs index 5bc1d300..d641113e 100644 --- a/src/api/v1/tests/auth.rs +++ b/src/api/v1/tests/auth.rs @@ -57,6 +57,9 @@ async fn auth_works() { let (_, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await; let cookies = get_cookie!(signin_resp); + // Sign in with email + signin(EMAIL, PASSWORD).await; + // 2. check if duplicate username is allowed let msg = Register { username: NAME.into(), @@ -76,7 +79,7 @@ async fn auth_works() { // 3. sigining in with non-existent user let mut creds = Login { - username: "nonexistantuser".into(), + login: "nonexistantuser".into(), password: msg.password.clone(), }; bad_post_req_test( @@ -84,13 +87,13 @@ async fn auth_works() { PASSWORD, ROUTES.auth.login, &creds, - ServiceError::UsernameNotFound, + ServiceError::AccountNotFound, StatusCode::NOT_FOUND, ) .await; // 4. trying to signin with wrong password - creds.username = NAME.into(); + creds.login = NAME.into(); creds.password = NAME.into(); bad_post_req_test( diff --git a/src/errors.rs b/src/errors.rs index 98a03201..da5fda5d 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -50,6 +50,8 @@ pub enum ServiceError { WrongPassword, #[display(fmt = "Username not found")] UsernameNotFound, + #[display(fmt = "Account not found")] + AccountNotFound, /// when the value passed contains profainity #[display(fmt = "Can't allow profanity in usernames")] @@ -114,6 +116,7 @@ impl ResponseError for ServiceError { ServiceError::NotAUrl => StatusCode::BAD_REQUEST, ServiceError::WrongPassword => StatusCode::UNAUTHORIZED, ServiceError::UsernameNotFound => StatusCode::NOT_FOUND, + ServiceError::AccountNotFound => StatusCode::NOT_FOUND, ServiceError::ProfainityError => StatusCode::BAD_REQUEST, ServiceError::BlacklistError => StatusCode::BAD_REQUEST, diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 81d90437..55362cd6 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -131,7 +131,7 @@ pub async fn signin(name: &str, password: &str) -> (Arc, Login, ServiceRes // 2. signin let creds = Login { - username: name.into(), + login: name.into(), password: password.into(), }; let signin_resp = test::call_service( diff --git a/templates/auth/register/ts/index.ts b/templates/auth/register/ts/index.ts index b4caf316..1e3d7180 100644 --- a/templates/auth/register/ts/index.ts +++ b/templates/auth/register/ts/index.ts @@ -45,7 +45,7 @@ const registerUser = async (e: Event) => { ); const passwordCheck = passwordCheckElement.value; if (password != passwordCheck) { - return alert("passwords don't match, check again!"); + return createError("passwords don't match, check again!"); } let exists = await userExists();