Alot of user management
This commit is contained in:
@@ -5,9 +5,15 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.100"
|
anyhow = "1.0.100"
|
||||||
|
argon2 = "0.5.3"
|
||||||
axum = "0.8.7"
|
axum = "0.8.7"
|
||||||
serde = "1.0.228"
|
password-hash = "0.5.0"
|
||||||
|
rand_core = { version = "0.6", features = ["getrandom"] }
|
||||||
|
serde = { version = "1.0.228", features = ["derive", "serde_derive"] }
|
||||||
serde_json = "1.0.145"
|
serde_json = "1.0.145"
|
||||||
tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread"] }
|
||||||
|
tower = { version = "0.5.2", features = ["tokio", "tracing"] }
|
||||||
|
tower-http = { version = "0.6.7", features = ["cors"] }
|
||||||
tracing = { version = "0.1.43", features = ["max_level_debug"] }
|
tracing = { version = "0.1.43", features = ["max_level_debug"] }
|
||||||
tracing-subscriber = "0.3.22"
|
tracing-subscriber = "0.3.22"
|
||||||
|
validator = { version = "0.20.0", features = ["derive"] }
|
||||||
|
|||||||
15
src/backend/src/auth/mod.rs
Normal file
15
src/backend/src/auth/mod.rs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
use argon2::{
|
||||||
|
Argon2,
|
||||||
|
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng},
|
||||||
|
};
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
|
pub fn hash_password(password: &str) -> Result<String, password_hash::Error> {
|
||||||
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
let argon2 = Argon2::default();
|
||||||
|
let hash = argon2
|
||||||
|
.hash_password(password.as_bytes(), &salt)?
|
||||||
|
.to_string();
|
||||||
|
debug!("Hashed password {}", hash);
|
||||||
|
Ok(hash)
|
||||||
|
}
|
||||||
1
src/backend/src/domain/mod.rs
Normal file
1
src/backend/src/domain/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod user;
|
||||||
71
src/backend/src/domain/user.rs
Normal file
71
src/backend/src/domain/user.rs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use axum::response::IntoResponse;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::auth;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct NewUser {
|
||||||
|
username: String,
|
||||||
|
email: Option<String>,
|
||||||
|
password: String,
|
||||||
|
first_name: Option<String>,
|
||||||
|
last_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct InternalUser {
|
||||||
|
username: String,
|
||||||
|
email: Option<String>,
|
||||||
|
password_hash: String,
|
||||||
|
first_name: Option<String>,
|
||||||
|
last_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct User {
|
||||||
|
username: String,
|
||||||
|
email: Option<String>,
|
||||||
|
first_name: Option<String>,
|
||||||
|
last_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum UserConversionError {
|
||||||
|
HashFailed(password_hash::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<NewUser> for InternalUser {
|
||||||
|
type Error = UserConversionError;
|
||||||
|
fn try_from(value: NewUser) -> Result<Self, Self::Error> {
|
||||||
|
let password_hash =
|
||||||
|
auth::hash_password(&value.password).map_err(|e| UserConversionError::HashFailed(e))?;
|
||||||
|
Ok(Self {
|
||||||
|
username: value.username,
|
||||||
|
email: value.email,
|
||||||
|
password_hash: password_hash,
|
||||||
|
first_name: value.first_name,
|
||||||
|
last_name: value.last_name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InternalUser> for User {
|
||||||
|
fn from(value: InternalUser) -> Self {
|
||||||
|
Self {
|
||||||
|
username: value.username,
|
||||||
|
email: value.email,
|
||||||
|
first_name: value.first_name,
|
||||||
|
last_name: value.last_name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for UserConversionError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
UserConversionError::HashFailed(e) => write!(f, "failed to hash password: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,4 @@
|
|||||||
|
pub mod auth;
|
||||||
|
pub mod domain;
|
||||||
pub mod router;
|
pub mod router;
|
||||||
|
pub mod state;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{Ok, Result};
|
use anyhow::{Ok, Result};
|
||||||
use rustymine_daemon::router;
|
use rustymine_daemon::{router, state::AppState};
|
||||||
use tracing::Level;
|
use tracing::Level;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@@ -10,9 +12,12 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
tracing::subscriber::set_global_default(subscriber)?;
|
tracing::subscriber::set_global_default(subscriber)?;
|
||||||
|
|
||||||
let app_result = router::init_router().await?;
|
let state = Arc::new(AppState::new());
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
let app_result = router::init_router(state.clone()).await;
|
||||||
axum::serve(listener, app_result).await.unwrap();
|
|
||||||
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
|
||||||
|
|
||||||
|
axum::serve(listener, app_result).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,24 @@
|
|||||||
use axum::{extract::Request, middleware::Next, response::IntoResponse};
|
use axum::{
|
||||||
|
Router,
|
||||||
|
extract::Request,
|
||||||
|
http::{Method, header::AUTHORIZATION},
|
||||||
|
middleware::Next,
|
||||||
|
response::IntoResponse,
|
||||||
|
};
|
||||||
|
use tower_http::cors::{Any, CorsLayer};
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
pub async fn auth_middleware(request: Request, next: Next) -> impl IntoResponse {
|
pub async fn auth(request: Request, next: Next) -> impl IntoResponse {
|
||||||
debug!("auth_middleware entry");
|
debug!("auth_middleware entry");
|
||||||
let response = next.run(request).await;
|
let response = next.run(request).await;
|
||||||
debug!("auth_middleware exit");
|
debug!("auth_middleware exit");
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cors() -> CorsLayer {
|
||||||
|
debug!("Generating CorsLayer");
|
||||||
|
CorsLayer::new()
|
||||||
|
.allow_origin(Any)
|
||||||
|
.allow_methods([Method::GET, Method::POST])
|
||||||
|
.allow_headers([AUTHORIZATION])
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,33 @@
|
|||||||
pub mod middleware;
|
pub mod middleware;
|
||||||
use anyhow::Result;
|
pub mod user_routes;
|
||||||
use axum::{Json, Router, http::StatusCode, middleware::from_fn, routing::get};
|
|
||||||
use serde_json::{Value, json};
|
|
||||||
|
|
||||||
pub async fn init_router() -> Result<Router> {
|
use axum::{
|
||||||
let router = Router::new().route(
|
Json, Router,
|
||||||
"/api/ping",
|
http::StatusCode,
|
||||||
get(ping).layer(from_fn(middleware::auth_middleware)),
|
routing::{get, post},
|
||||||
);
|
};
|
||||||
Ok(router)
|
use serde_json::{Value, json};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tower::{Layer, ServiceBuilder};
|
||||||
|
|
||||||
|
use crate::state::AppState;
|
||||||
|
|
||||||
|
pub async fn init_router(app_state: Arc<AppState>) -> Router {
|
||||||
|
Router::new()
|
||||||
|
.route(
|
||||||
|
"/api/ping",
|
||||||
|
get(ping).layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.layer(middleware::cors())
|
||||||
|
.layer(axum::middleware::from_fn(middleware::auth)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/api/user/create",
|
||||||
|
post(user_routes::create)
|
||||||
|
.layer(ServiceBuilder::new().layer(middleware::cors()))
|
||||||
|
.with_state(app_state),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ping() -> Result<Json<Value>, StatusCode> {
|
async fn ping() -> Result<Json<Value>, StatusCode> {
|
||||||
|
|||||||
24
src/backend/src/router/user_routes.rs
Normal file
24
src/backend/src/router/user_routes.rs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use axum::{Json, extract::State, http::StatusCode};
|
||||||
|
use serde_json::json;
|
||||||
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
domain::user::{InternalUser, NewUser, User},
|
||||||
|
state::AppState,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn create(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
Json(new_user): Json<NewUser>,
|
||||||
|
) -> Result<Json<User>, StatusCode> {
|
||||||
|
let internal = InternalUser::try_from(new_user).map_err(|e| {
|
||||||
|
error!("Conversion to InternalUser failed: {e}");
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let user = User::from(internal);
|
||||||
|
|
||||||
|
Ok(Json(user))
|
||||||
|
}
|
||||||
15
src/backend/src/state.rs
Normal file
15
src/backend/src/state.rs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::domain::user::InternalUser;
|
||||||
|
|
||||||
|
pub struct AppState {
|
||||||
|
pub users: HashMap<String, InternalUser>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppState {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
users: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user