Working with httpauth, trying to use extractors
This commit is contained in:
@@ -17,7 +17,6 @@ pub mod messages;
|
||||
pub mod options;
|
||||
pub mod races;
|
||||
pub mod spells;
|
||||
pub mod users;
|
||||
pub mod schema;
|
||||
|
||||
type Pool = r2d2::Pool<ConnectionManager<PgConnection>>;
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
mod model;
|
||||
mod routes;
|
||||
|
||||
pub use model::*;
|
||||
pub use routes::init_routes;
|
||||
@@ -1,162 +0,0 @@
|
||||
use std::{future::{ready, Ready, Future}, pin::Pin};
|
||||
use actix_identity::Identity;
|
||||
use actix_web::{FromRequest, Error as ActixError, HttpRequest, dev::Payload, error::{ErrorUnauthorized, ErrorInternalServerError}};
|
||||
use argon2::{password_hash::{rand_core::OsRng, PasswordHasher, PasswordVerifier, SaltString, Error as HashError}, Argon2, PasswordHash};
|
||||
use diesel::prelude::*;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use siren::ServiceError;
|
||||
|
||||
use crate::db::schema::users;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RegisterUser {
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
pub first_name: String,
|
||||
pub last_name: String,
|
||||
}
|
||||
|
||||
impl RegisterUser {
|
||||
pub fn convert_to_insert(self) -> Result<InsertUser, ServiceError> {
|
||||
let hash = hash(self.password.as_bytes())?;
|
||||
Ok(InsertUser {
|
||||
email: self.email,
|
||||
hash,
|
||||
role: "user".to_string(),
|
||||
first_name: self.first_name,
|
||||
last_name: self.last_name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct LoginAuth {
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct LoggedUser {
|
||||
pub email: String
|
||||
}
|
||||
|
||||
impl FromRequest for LoggedUser {
|
||||
type Error = ActixError;
|
||||
// type Future = Ready<Result<LoggedUser, ActixError>>;
|
||||
// type Future = std::pin::Pin<Box<dyn std::future::Future<Output = Result<LoggedUser, ActixError>>>>;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
|
||||
|
||||
fn from_request(req: &HttpRequest, pl: &mut Payload) -> Self::Future {
|
||||
// if let Ok(identity) = Identity::from_request(req, pl).into_inner() {
|
||||
// if let Ok(user_json) = identity.id() {
|
||||
// if let Ok(user) = serde_json::from_str(&user_json) {
|
||||
// return ready(Ok(user));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// std::future::ready(Err(
|
||||
// ActixError::from(ServiceError {
|
||||
// status: 401,
|
||||
// message: "Unauthorized".to_string(),
|
||||
// })
|
||||
// ))
|
||||
let identity = Identity::extract(req).into_inner();
|
||||
Box::pin(async move {
|
||||
process_req_auth_data(identity).await
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_req_auth_data(identity: Result<Identity, ActixError>) -> Result<LoggedUser, ActixError> {
|
||||
let id = identity
|
||||
.map_err(|_| ErrorUnauthorized("You are not logged in; 1"))?
|
||||
.id()
|
||||
.map_err(|_| ErrorUnauthorized("You are not logged in; 3"))?;
|
||||
let logged_user = match serde_json::from_str::<LoggedUser>(&id) {
|
||||
Ok(user) => user,
|
||||
Err(err) => return Err(ErrorInternalServerError(err))
|
||||
};
|
||||
|
||||
let user = QueryUser::get_by_email(&logged_user.email)
|
||||
.map_err(|_| ErrorUnauthorized("You are not logged in; 3"))?;
|
||||
|
||||
Ok(LoggedUser { email: user.email })
|
||||
}
|
||||
|
||||
#[derive(Debug, Queryable, QueryableByName, Serialize, Deserialize)]
|
||||
#[diesel(table_name = users)]
|
||||
pub struct QueryUser {
|
||||
pub email: String,
|
||||
pub hash: String,
|
||||
pub role: String,
|
||||
pub first_name: String,
|
||||
pub last_name: String,
|
||||
}
|
||||
|
||||
impl QueryUser {
|
||||
pub fn get_by_email(email: &str) -> Result<QueryUser, ServiceError> {
|
||||
let mut conn = crate::db::connection()?;
|
||||
let user = users::table
|
||||
.filter(users::email.eq(email))
|
||||
.first(&mut conn)?;
|
||||
Ok(user)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Insertable, AsChangeset, Serialize, Deserialize)]
|
||||
#[diesel(table_name = users)]
|
||||
pub struct InsertUser {
|
||||
pub email: String,
|
||||
pub hash: String,
|
||||
pub role: String,
|
||||
pub first_name: String,
|
||||
pub last_name: String,
|
||||
}
|
||||
|
||||
impl InsertUser {
|
||||
pub fn insert(user: Self) -> Result<QueryUser, ServiceError> {
|
||||
let mut conn = crate::db::connection()?;
|
||||
let user = diesel::insert_into(users::table)
|
||||
.values(user)
|
||||
.get_result(&mut conn)?;
|
||||
Ok(user)
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/Sirneij/rust-auth/blob/main/backend/src/routes/users/login.rs
|
||||
// https://dev.to/sirneij/authentication-system-using-rust-actix-web-and-sveltekit-user-registration-580h
|
||||
// https://github.com/actix/actix-extras/blob/master/actix-session/examples/basic.rs
|
||||
// maybe https://github.com/actix/actix-extras/blob/master/actix-identity/examples/identity.rs
|
||||
// https://www.lpalmieri.com/posts/session-based-authentication-in-rust/#3-3-1-postgres
|
||||
|
||||
// pub async fn validator(req: ServiceRequest, credentials: BearerAuth) -> Result<ServiceRequest, (ActixError, ServiceRequest)> {
|
||||
// let token = credentials.token();
|
||||
// println!("{:?}", req);
|
||||
// match validate_token(token) {
|
||||
// Ok(res) => {
|
||||
// if res {
|
||||
// Ok(req)
|
||||
// } else {
|
||||
// Err((ActixError::from(actix_web::error::ErrorUnauthorized("Invalid token")), req))
|
||||
// }
|
||||
// },
|
||||
// Err(err) => {
|
||||
// Err((ActixError::from(actix_web::error::ErrorUnauthorized(err)), req))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn validate_token(token: &str) -> Result<bool, ServiceError> {
|
||||
// println!("Validating token: {}", token);
|
||||
// Ok(true)
|
||||
// }
|
||||
|
||||
pub fn hash(password: &[u8]) -> Result<String, HashError> {
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
Ok(Argon2::default().hash_password(password, &salt)?.to_string())
|
||||
}
|
||||
|
||||
pub fn verify(hash: &str, password: &[u8]) -> Result<(), HashError> {
|
||||
let parsed_hash = PasswordHash::new(hash)?;
|
||||
Ok(Argon2::default().verify_password(password, &parsed_hash)?)
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
use actix_identity::Identity;
|
||||
use actix_web::{get, post, web, HttpResponse, HttpRequest, ResponseError, HttpMessage};
|
||||
use siren::ServiceError;
|
||||
|
||||
use crate::db::users::{LoginAuth, RegisterUser, InsertUser, QueryUser, verify, LoggedUser};
|
||||
|
||||
#[post("/register")]
|
||||
async fn register(user: web::Json<RegisterUser>) -> HttpResponse {
|
||||
let register_user = user.0;
|
||||
let insert_user: InsertUser = match register_user.convert_to_insert() {
|
||||
Ok(user) => user,
|
||||
Err(err) => return ResponseError::error_response(&err)
|
||||
};
|
||||
match InsertUser::insert(insert_user) {
|
||||
Ok(_) => {
|
||||
HttpResponse::Created().finish()
|
||||
},
|
||||
Err(err) => {
|
||||
// Obfuscate the service error message to prevent leaking database details
|
||||
if err.status == 409 {
|
||||
return HttpResponse::Conflict().finish();
|
||||
} else {
|
||||
return ResponseError::error_response(&err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/login")]
|
||||
async fn login(req: HttpRequest, auth: web::Json<LoginAuth>) -> HttpResponse {
|
||||
let email = auth.email.clone();
|
||||
|
||||
let query_user = match QueryUser::get_by_email(&email) {
|
||||
Ok(query_user) => query_user,
|
||||
Err(err) => return ResponseError::error_response(&err)
|
||||
};
|
||||
let hash = query_user.hash;
|
||||
let password = auth.password.as_bytes();
|
||||
match verify(&hash, password) {
|
||||
Ok(_) => {
|
||||
let user = LoggedUser {
|
||||
email: email.clone()
|
||||
};
|
||||
let user_string = serde_json::to_string(&user).unwrap();
|
||||
match Identity::login(&req.extensions(), user_string) {
|
||||
Ok(_) => HttpResponse::Ok().finish(),
|
||||
Err(err) => return ResponseError::error_response(&err)
|
||||
}
|
||||
},
|
||||
Err(err) => ResponseError::error_response(&ServiceError {
|
||||
status: 401,
|
||||
message: err.to_string()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/logout")]
|
||||
async fn logout(identity: Identity) -> HttpResponse {
|
||||
identity.logout();
|
||||
HttpResponse::Ok().finish()
|
||||
}
|
||||
|
||||
#[get("/ping")]
|
||||
async fn ping(user: LoggedUser) -> HttpResponse {
|
||||
HttpResponse::Ok().json(user)
|
||||
}
|
||||
|
||||
pub fn init_routes(config: &mut web::ServiceConfig) {
|
||||
config.service(web::scope("users")
|
||||
.service(register)
|
||||
.service(login)
|
||||
.service(logout)
|
||||
.service(ping));
|
||||
}
|
||||
Reference in New Issue
Block a user