sign in with email

This commit is contained in:
realaravinth 2021-06-29 19:42:07 +05:30
parent c581d8d0a3
commit d5aceb60b4
No known key found for this signature in database
GPG Key ID: AD9F0F08E855ED88
7 changed files with 68 additions and 27 deletions

2
Cargo.lock generated
View File

@ -441,7 +441,7 @@ dependencies = [
[[package]] [[package]]
name = "argon2-creds" name = "argon2-creds"
version = "0.2.1" 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 = [ dependencies = [
"ammonia", "ammonia",
"derive_builder 0.10.2", "derive_builder 0.10.2",

View File

@ -40,6 +40,7 @@ futures = "0.3.14"
sqlx = { version = "0.4.0", features = [ "runtime-actix-rustls", "postgres", "time", "offline" ] } 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 = { branch = "master", git = "https://github.com/realaravinth/argon2-creds"}
#argon2-creds = { version="*", path = "../../argon2-creds/" }
config = "0.11" config = "0.11"
validator = { version = "0.13", features = ["derive"]} validator = { version = "0.13", features = ["derive"]}

View File

@ -19,6 +19,7 @@ use actix_identity::Identity;
use actix_web::http::header; use actix_web::http::header;
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
//use futures::{future::TryFutureExt, join};
use super::mcaptcha::get_random; use super::mcaptcha::get_random;
use crate::errors::*; use crate::errors::*;
@ -60,7 +61,9 @@ pub mod runners {
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Login { 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, pub password: String,
} }
@ -70,28 +73,59 @@ pub mod runners {
} }
/// returns Ok(()) when everything checks out and the user is authenticated. Erros otherwise /// 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<String> {
use argon2_creds::Config; use argon2_creds::Config;
use sqlx::Error::RowNotFound; use sqlx::Error::RowNotFound;
let rec = sqlx::query_as!( let verify = |stored: &str, received: &str| {
Password, if Config::verify(&stored, &received)? {
r#"SELECT password FROM mcaptcha_users WHERE name = ($1)"#, Ok(())
&payload.username, } else {
) Err(ServiceError::WrongPassword)
.fetch_one(&data.db) }
.await; };
match rec { if payload.login.contains("@") {
Ok(s) => { #[derive(Clone, Debug)]
if Config::verify(&s.password, &payload.password)? { struct EmailLogin {
Ok(()) name: String,
} else { password: String,
Err(ServiceError::WrongPassword) }
}
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<runners::Login>, payload: web::Json<runners::Login>,
data: AppData, data: AppData,
) -> ServiceResult<impl Responder> { ) -> ServiceResult<impl Responder> {
runners::login_runner(&payload, &data).await?; let username = runners::login_runner(payload.into_inner(), &data).await?;
id.remember(payload.into_inner().username); id.remember(username);
Ok(HttpResponse::Ok()) Ok(HttpResponse::Ok())
} }

View File

@ -57,6 +57,9 @@ async fn auth_works() {
let (_, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await; let (_, _, signin_resp) = register_and_signin(NAME, EMAIL, PASSWORD).await;
let cookies = get_cookie!(signin_resp); let cookies = get_cookie!(signin_resp);
// Sign in with email
signin(EMAIL, PASSWORD).await;
// 2. check if duplicate username is allowed // 2. check if duplicate username is allowed
let msg = Register { let msg = Register {
username: NAME.into(), username: NAME.into(),
@ -76,7 +79,7 @@ async fn auth_works() {
// 3. sigining in with non-existent user // 3. sigining in with non-existent user
let mut creds = Login { let mut creds = Login {
username: "nonexistantuser".into(), login: "nonexistantuser".into(),
password: msg.password.clone(), password: msg.password.clone(),
}; };
bad_post_req_test( bad_post_req_test(
@ -84,13 +87,13 @@ async fn auth_works() {
PASSWORD, PASSWORD,
ROUTES.auth.login, ROUTES.auth.login,
&creds, &creds,
ServiceError::UsernameNotFound, ServiceError::AccountNotFound,
StatusCode::NOT_FOUND, StatusCode::NOT_FOUND,
) )
.await; .await;
// 4. trying to signin with wrong password // 4. trying to signin with wrong password
creds.username = NAME.into(); creds.login = NAME.into();
creds.password = NAME.into(); creds.password = NAME.into();
bad_post_req_test( bad_post_req_test(

View File

@ -50,6 +50,8 @@ pub enum ServiceError {
WrongPassword, WrongPassword,
#[display(fmt = "Username not found")] #[display(fmt = "Username not found")]
UsernameNotFound, UsernameNotFound,
#[display(fmt = "Account not found")]
AccountNotFound,
/// when the value passed contains profainity /// when the value passed contains profainity
#[display(fmt = "Can't allow profanity in usernames")] #[display(fmt = "Can't allow profanity in usernames")]
@ -114,6 +116,7 @@ impl ResponseError for ServiceError {
ServiceError::NotAUrl => StatusCode::BAD_REQUEST, ServiceError::NotAUrl => StatusCode::BAD_REQUEST,
ServiceError::WrongPassword => StatusCode::UNAUTHORIZED, ServiceError::WrongPassword => StatusCode::UNAUTHORIZED,
ServiceError::UsernameNotFound => StatusCode::NOT_FOUND, ServiceError::UsernameNotFound => StatusCode::NOT_FOUND,
ServiceError::AccountNotFound => StatusCode::NOT_FOUND,
ServiceError::ProfainityError => StatusCode::BAD_REQUEST, ServiceError::ProfainityError => StatusCode::BAD_REQUEST,
ServiceError::BlacklistError => StatusCode::BAD_REQUEST, ServiceError::BlacklistError => StatusCode::BAD_REQUEST,

View File

@ -131,7 +131,7 @@ pub async fn signin(name: &str, password: &str) -> (Arc<Data>, Login, ServiceRes
// 2. signin // 2. signin
let creds = Login { let creds = Login {
username: name.into(), login: name.into(),
password: password.into(), password: password.into(),
}; };
let signin_resp = test::call_service( let signin_resp = test::call_service(

View File

@ -45,7 +45,7 @@ const registerUser = async (e: Event) => {
); );
const passwordCheck = passwordCheckElement.value; const passwordCheck = passwordCheckElement.value;
if (password != passwordCheck) { if (password != passwordCheck) {
return alert("passwords don't match, check again!"); return createError("passwords don't match, check again!");
} }
let exists = await userExists(); let exists = await userExists();