User Authentication and Other fixes #1

Merged
vinod merged 30 commits from user-auth into main 2023-02-05 09:01:08 +00:00
6 changed files with 89 additions and 50 deletions
Showing only changes of commit 08c41e2db9 - Show all commits

3
backend/Cargo.lock generated
View File

@@ -643,8 +643,11 @@ dependencies = [
name = "booksman-orm" name = "booksman-orm"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"axum 0.6.1",
"axum-login",
"chrono", "chrono",
"entity", "entity",
"eyre",
"sea-orm", "sea-orm",
"serde", "serde",
] ]

View File

@@ -14,14 +14,13 @@ use std::str::FromStr;
use std::io; use std::io;
use tower::ServiceBuilder; use tower::ServiceBuilder;
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
use tokio::sync::RwLock;
use tower_http::cors::{Any,CorsLayer}; use tower_http::cors::{Any,CorsLayer};
use tower_http::trace::TraceLayer; use tower_http::trace::TraceLayer;
use booksman_search::BookMeili; use booksman_search::BookMeili;
use booksman_search; use booksman_search;
use booksman_orm::{ use booksman_orm::{
sea_orm::{Database, DatabaseConnection}, sea_orm::{Database, DatabaseConnection},
Mutation as MutationCore, Query as QueryCore, Mutation as MutationCore, Query as QueryCore
// BookAndMeta, // BookAndMeta,
}; };
use meilisearch_sdk::client::Client; use meilisearch_sdk::client::Client;
@@ -32,29 +31,9 @@ use migration::{Migrator, MigratorTrait};
use chrono::Local; use chrono::Local;
use axum_login::{ use axum_login::{
axum_sessions::{async_session::MemoryStore as SessionMemoryStore, SessionLayer}, axum_sessions::{async_session::MemoryStore as SessionMemoryStore, SessionLayer},
memory_store::MemoryStore as AuthMemoryStore,
secrecy::SecretVec,
AuthLayer, AuthUser, RequireAuthorizationLayer, AuthLayer, AuthUser, RequireAuthorizationLayer,
}; };
use rand::Rng; use rand::Rng;
use std::sync::Arc;
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
struct User {
id: i32,
password_hash: String,
name: String,
}
impl AuthUser for User {
fn get_id(&self) -> String {
format!("{}", self.id)
}
fn get_password_hash(&self) -> SecretVec<u8> {
SecretVec::new(self.password_hash.clone().into())
}
}
#[derive(Deserialize, Serialize, Debug, Clone)] #[derive(Deserialize, Serialize, Debug, Clone)]
struct BookUI { struct BookUI {
@@ -230,7 +209,7 @@ struct Opt {
static_dir: String, static_dir: String,
} }
type AuthContext = axum_login::extractors::AuthContext<User, AuthMemoryStore<User>>; type AuthContext = axum_login::extractors::AuthContext<booksman_orm::AxumUser, booksman_orm::AxumUserStore>;
#[tokio::main] #[tokio::main]
pub async fn main() { pub async fn main() {
@@ -253,9 +232,6 @@ pub async fn main() {
let secret = rand::thread_rng().gen::<[u8; 64]>(); let secret = rand::thread_rng().gen::<[u8; 64]>();
let session_store = SessionMemoryStore::new();
let session_layer = SessionLayer::new(session_store, &secret).with_secure(false);
let conn = Database::connect(db_url) let conn = Database::connect(db_url)
.await .await
.expect("Database connection failed"); .expect("Database connection failed");
@@ -263,21 +239,11 @@ pub async fn main() {
Migrator::up(&conn, None).await.unwrap(); Migrator::up(&conn, None).await.unwrap();
let session_store = SessionMemoryStore::new();
let session_layer = SessionLayer::new(session_store, &secret).with_secure(false);
let store = Arc::new(RwLock::new(HashMap::default()));
let usersmodels : Vec<user::Model> = QueryCore::list_all_users(&conn).await.unwrap_or(Vec::new());
//let users : Vec<User> = get_users_seaorm(conn);
//let user = User::get_rusty_user();
for usermodel in usersmodels.iter() {
let user = User{
id: usermodel.id,
name: usermodel.user_name.clone().unwrap(),
password_hash : usermodel.password_hash.clone(),
};
store.write().await.insert(user.get_id(), user);
}
let user_store = AuthMemoryStore::new(&store); let user_store = booksman_orm::AxumUserStore::new(&conn);
let auth_layer = AuthLayer::new(user_store, &secret); let auth_layer = AuthLayer::new(user_store, &secret);
let meili_client = Client::new(meili_url, meili_key); let meili_client = Client::new(meili_url, meili_key);
@@ -300,7 +266,6 @@ pub async fn main() {
// .merge(SpaRouter::new("/assets", opt.static_dir)) // .merge(SpaRouter::new("/assets", opt.static_dir))
.layer(auth_layer) .layer(auth_layer)
.layer(session_layer) .layer(session_layer)
.layer(Extension(store))
.layer(ServiceBuilder::new().layer(TraceLayer::new_for_http())) .layer(ServiceBuilder::new().layer(TraceLayer::new_for_http()))
.layer(Extension(conn)) .layer(Extension(conn))
.layer(Extension(meili_client)) .layer(Extension(meili_client))
@@ -332,9 +297,8 @@ pub async fn main() {
#[axum_macros::debug_handler] #[axum_macros::debug_handler]
async fn register_handler(mut auth: AuthContext, Extension(ref conn): Extension<DatabaseConnection>, async fn register_handler(Extension(ref conn): Extension<DatabaseConnection>,
Extension(ref store): Extension<RwLock<HashMap<i32, User>>>, Json(user_sent): Json<booksman_orm::AxumUser>) -> impl IntoResponse {
Json(user_sent): Json<User>) -> impl IntoResponse {
// add to db // add to db
let user: user::Model = user::Model{ let user: user::Model = user::Model{
id: user_sent.id, id: user_sent.id,
@@ -342,8 +306,8 @@ Json(user_sent): Json<User>) -> impl IntoResponse {
password_hash: user_sent.clone().password_hash, password_hash: user_sent.clone().password_hash,
}; };
let created_user = MutationCore::create_user(conn, user).await.expect("Failed to create user"); let created_user = MutationCore::create_user(conn, user).await.expect("Failed to create user");
store.write().await.insert(created_user.last_insert_id, user_sent.clone());
auth.login(&user_sent).await.unwrap(); //auth.login(&user_sent_updated).await.unwrap();
return "success"; return "success";
} }
@@ -364,7 +328,7 @@ async fn list_users(
} }
return Json(users); return Json(users);
} }
async fn login_handler(mut auth: AuthContext, Json(user_sent): Json<User>) -> impl IntoResponse { async fn login_handler(mut auth: AuthContext, Json(user_sent): Json<booksman_orm::AxumUser>) -> impl IntoResponse {
auth.login(&user_sent).await.unwrap(); auth.login(&user_sent).await.unwrap();
return "success"; return "success";
} }
@@ -382,7 +346,7 @@ async fn list_users(
async fn create_by_isbn( async fn create_by_isbn(
Extension(ref conn): Extension<DatabaseConnection>, Extension(ref conn): Extension<DatabaseConnection>,
Extension(ref meili_client): Extension<Client>, Extension(ref meili_client): Extension<Client>,
Extension(user): Extension<User>, Extension(user): Extension<booksman_orm::AxumUser>,
axum::extract::Query(params): axum::extract::Query<HashMap<String, String>>, axum::extract::Query(params): axum::extract::Query<HashMap<String, String>>,
) -> impl IntoResponse { ) -> impl IntoResponse {
@@ -636,7 +600,7 @@ let mut vec = Vec::with_capacity(12);
async fn delete_book( async fn delete_book(
Extension(ref conn): Extension<DatabaseConnection>, Extension(ref conn): Extension<DatabaseConnection>,
Extension(ref meili_client): Extension<Client>, Extension(ref meili_client): Extension<Client>,
Extension(user): Extension<User>, Extension(user): Extension<booksman_orm::AxumUser>,
Path(id): Path<i32>, Path(id): Path<i32>,
) -> impl IntoResponse { ) -> impl IntoResponse {
MutationCore::delete_book(conn, id) MutationCore::delete_book(conn, id)
@@ -766,7 +730,7 @@ return Json(res);
async fn create_book( async fn create_book(
Extension(ref conn): Extension<DatabaseConnection>, Extension(ref conn): Extension<DatabaseConnection>,
Extension(ref meili_client): Extension<Client>, Extension(ref meili_client): Extension<Client>,
Extension(user): Extension<User>, Extension(user): Extension<booksman_orm::AxumUser>,
Json(doc_sent_orig): Json<BookUI>, Json(doc_sent_orig): Json<BookUI>,
) -> impl IntoResponse { ) -> impl IntoResponse {
println!("Creating book"); println!("Creating book");
@@ -912,7 +876,7 @@ async fn create_book(
async fn update_book( async fn update_book(
Extension(ref conn): Extension<DatabaseConnection>, Extension(ref conn): Extension<DatabaseConnection>,
Extension(ref meili_client): Extension<Client>, Extension(ref meili_client): Extension<Client>,
Extension(user): Extension<User>, Extension(user): Extension<booksman_orm::AxumUser>,
Json(doc_sent): Json<BookUI>, Json(doc_sent): Json<BookUI>,
) -> impl IntoResponse { ) -> impl IntoResponse {
println!("Updating book"); println!("Updating book");

View File

@@ -8,6 +8,9 @@ publish = false
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
entity = { path = "../entity" } entity = { path = "../entity" }
chrono = "0.4" chrono = "0.4"
axum-login = "0.4"
axum = "^0.6"
eyre = "0.6.8"
[dependencies.sea-orm] [dependencies.sea-orm]
version = "^0.10.6" # sea-orm version version = "^0.10.6" # sea-orm version

View File

@@ -1,7 +1,9 @@
mod mutation; mod mutation;
mod query; mod query;
mod userstore;
pub use mutation::*; pub use mutation::*;
pub use query::*; pub use query::*;
pub use userstore::*;
pub use sea_orm; pub use sea_orm;

View File

@@ -26,6 +26,11 @@ impl Query {
Book::find_by_id(id).one(db).await Book::find_by_id(id).one(db).await
} }
pub async fn find_user_by_id(db: &DbConn, id: i32) -> Result<Option<user::Model>, DbErr> {
User::find_by_id(id).one(db).await
}
pub async fn list_all_users(db: &DbConn) -> Result<Vec<user::Model>, DbErr> { pub async fn list_all_users(db: &DbConn) -> Result<Vec<user::Model>, DbErr> {
User::find().all(db).await User::find().all(db).await
} }

View File

@@ -0,0 +1,62 @@
use sea_orm::*;
use axum::async_trait;
use axum_login::{secrecy::SecretVec, AuthUser, UserStore};
use crate::Query;
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AxumUser {
pub id: i32,
pub password_hash: String,
pub name: String,
}
type Result<T = ()> = std::result::Result<T, eyre::Error>;
impl<Role> AuthUser<Role> for AxumUser
where
Role: PartialOrd + PartialEq + Clone + Send + Sync + 'static,
{
fn get_id(&self) -> String {
format!("{}", self.id)
}
fn get_password_hash(&self) -> axum_login::secrecy::SecretVec<u8> {
SecretVec::new(self.password_hash.clone().into())
}
}
#[derive(Debug, Clone)]
pub struct AxumUserStore {
conn: DatabaseConnection,
}
impl AxumUserStore {
pub fn new(conn: &DatabaseConnection) -> Self {
Self { conn: conn.clone() }
}
}
#[async_trait]
impl<Role> UserStore<Role> for AxumUserStore
where
Role: PartialOrd + PartialEq + Clone + Send + Sync + 'static,
{
type User = AxumUser;
async fn load_user(&self, user_id: &str) -> Result<Option<Self::User>> {
// my user id is a Vec<u8>, so it's stored base64 encoded
let id: i32 = user_id.parse().unwrap();
let user = Query::find_user_by_id(&self.conn, id).await?;
match user {
Some(u) => Ok(Some(AxumUser {
id: u.id,
password_hash: u.password_hash,
name: u.user_name.unwrap(),
})),
None => Ok(None),
}
}
}