user-auth store test2
This commit is contained in:
3
backend/Cargo.lock
generated
3
backend/Cargo.lock
generated
@@ -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",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
62
backend/orm/src/userstore.rs
Normal file
62
backend/orm/src/userstore.rs
Normal 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user