Continued permissions and auth endpoint UNTESTED

This commit is contained in:
2025-12-01 23:09:25 +01:00
parent 0981bfb04b
commit e7f14da0ca
14 changed files with 378 additions and 92 deletions

View File

@@ -8,6 +8,7 @@ anyhow = "1.0.100"
argon2 = "0.5.3" argon2 = "0.5.3"
axum = "0.8.7" axum = "0.8.7"
chrono = "0.4.42" chrono = "0.4.42"
jsonwebtoken = { version = "10.2.0", features = ["rust_crypto"] }
lazy_static = "1.5.0" lazy_static = "1.5.0"
password-hash = "0.5.0" password-hash = "0.5.0"
rand_core = { version = "0.6", features = ["getrandom"] } rand_core = { version = "0.6", features = ["getrandom"] }

View File

@@ -1,4 +1,4 @@
CREATE TABLE user_permissions ( CREATE TABLE users (
uuid UUID PRIMARY KEY, uuid UUID PRIMARY KEY,
username VARCHAR NOT NULL UNIQUE, username VARCHAR NOT NULL UNIQUE,
email VARCHAR UNIQUE, email VARCHAR UNIQUE,

View File

@@ -1,4 +1,4 @@
CREATE TABLE users ( CREATE TABLE user_permissions(
uuid UUID PRIMARY KEY, uuid UUID PRIMARY KEY,
root BOOL NOT NULL, root BOOL NOT NULL,
manage_users BOOL NOT NULL, manage_users BOOL NOT NULL,

View File

@@ -1,8 +1,16 @@
use crate::prelude::*;
use std::i64;
use anyhow::Result;
use argon2::{ use argon2::{
Argon2, Argon2, PasswordHash, PasswordVerifier,
password_hash::{PasswordHasher, SaltString, rand_core::OsRng}, password_hash::{PasswordHasher, SaltString, rand_core::OsRng},
}; };
use tracing::debug; use axum::{extract::State, http::StatusCode};
use chrono::{Duration, Utc};
use jsonwebtoken::{DecodingKey, EncodingKey, Header, TokenData, Validation, decode, encode};
use crate::domain::api::AuthClaims;
pub fn hash_password(password: &str) -> Result<String, password_hash::Error> { pub fn hash_password(password: &str) -> Result<String, password_hash::Error> {
let salt = SaltString::generate(&mut OsRng); let salt = SaltString::generate(&mut OsRng);
@@ -13,3 +21,47 @@ pub fn hash_password(password: &str) -> Result<String, password_hash::Error> {
debug!("password hashed"); debug!("password hashed");
Ok(hash) Ok(hash)
} }
pub fn verify_password(password: &str, password_hash: &str) -> Result<bool, password_hash::Error> {
let parsed_hash = PasswordHash::new(password_hash)?;
let argon2 = Argon2::default();
match argon2.verify_password(password.as_bytes(), &parsed_hash) {
Ok(_) => return Ok(true),
Err(password_hash::Error::Password) => return Ok(false),
Err(e) => Err(e),
}
}
pub fn gen_jwt(username: String) -> Result<String, StatusCode> {
// TODO: Update secret to .env file
let secret: String = "verysafestring".to_string();
let now = Utc::now();
let expire: chrono::TimeDelta = Duration::hours(24);
let exp = (now + expire).timestamp() as i64;
let iat = now.timestamp() as i64;
let claim = AuthClaims { iat, exp, username };
encode(
&Header::default(),
&claim,
&EncodingKey::from_secret(secret.as_ref()),
)
.map_err(|e| {
error!("Failed to create JWT: {}", e);
return StatusCode::INTERNAL_SERVER_ERROR;
})
}
pub fn verify_jwt(token: String) -> Result<TokenData<AuthClaims>, StatusCode> {
let secret = "verysafestring".to_string();
let result: Result<TokenData<AuthClaims>, StatusCode> = decode(
&token,
&DecodingKey::from_secret(secret.as_ref()),
&Validation::default(),
)
.map_err(|e| {
error!("Failed to verify JWT: {}", e);
return StatusCode::INTERNAL_SERVER_ERROR;
});
result
}

View File

@@ -1,11 +1,12 @@
use crate::{ use crate::{
domain::{user::InternalUser, user_prems::UserPermissions}, auth::{gen_jwt, verify_password},
domain::{api::LoginData, user::InternalUser, user_prems::UserPermissions},
infra::db, infra::db,
prelude::*, prelude::*,
}; };
use std::sync::Arc; use std::sync::Arc;
use axum::http::StatusCode; use axum::{extract::State, http::StatusCode};
use uuid::Uuid; use uuid::Uuid;
use validator::Validate; use validator::Validate;
@@ -16,6 +17,35 @@ use crate::{
state::AppState, state::AppState,
}; };
pub async fn login(state: Arc<AppState>, login_data: LoginData) -> Result<String, StatusCode> {
let user = db::user::get_by_username(&state.db_pool, &login_data.username)
.await
.map_err(|e| {
error!("Failed fetching user during login: {}", e);
return StatusCode::INTERNAL_SERVER_ERROR;
})?;
let user = match user {
Some(value) => value,
None => return Err(StatusCode::UNAUTHORIZED),
};
let verify = verify_password(&login_data.password, &user.password_hash).map_err(|e| {
error!("Failed to verify password hash: {}", e);
return StatusCode::INTERNAL_SERVER_ERROR;
})?;
if !verify {
return Err(StatusCode::UNAUTHORIZED);
}
let token = gen_jwt(user.username.clone()).map_err(|e| {
error!("Failed to generate JWT: {}", e);
StatusCode::INTERNAL_SERVER_ERROR
})?;
Ok(token)
}
pub async fn create(state: Arc<AppState>, new_user: NewUser) -> Result<User, StatusCode> { pub async fn create(state: Arc<AppState>, new_user: NewUser) -> Result<User, StatusCode> {
debug!("create user started"); debug!("create user started");
@@ -29,13 +59,28 @@ pub async fn create(state: Arc<AppState>, new_user: NewUser) -> Result<User, Sta
StatusCode::INTERNAL_SERVER_ERROR StatusCode::INTERNAL_SERVER_ERROR
})?; })?;
let created_user = db::user::create(&state.db_pool, internal) let mut created_user = db::user::create(&state.db_pool, internal.clone())
.await .await
.map_err(|e| { .map_err(|e| {
error!(error = %e, "create user failed"); error!(error = %e, "create user failed");
StatusCode::INTERNAL_SERVER_ERROR StatusCode::INTERNAL_SERVER_ERROR
})?; })?;
let perms = db::perms::create(
&state.db_pool,
created_user.uuid.clone(),
internal.permissions,
)
.await
.map_err(|e| {
error!(error = %e, "create user permissions entry failed");
StatusCode::INTERNAL_SERVER_ERROR
})?;
created_user.attach_permissions(perms);
let created_user = created_user;
info!(user_uuid = %created_user.uuid, "user created"); info!(user_uuid = %created_user.uuid, "user created");
Ok(User::from(created_user)) Ok(User::from(created_user))
} }
@@ -55,17 +100,40 @@ pub async fn get_safe_by_uuid(
uuid: Uuid, uuid: Uuid,
) -> Result<Option<User>, StatusCode> { ) -> Result<Option<User>, StatusCode> {
debug!(user_uuid = %uuid, "fetch user by uuid started"); debug!(user_uuid = %uuid, "fetch user by uuid started");
let user = db::user::get_safe_by_uuid(&state.db_pool, uuid.clone())
let mut user = match db::user::get_safe_by_uuid(&state.db_pool, uuid)
.await .await
.map_err(|e| { .map_err(|e| {
error!(error = %e, user_uuid = %uuid, "fetch user failed"); error!(error = %e, user_uuid = %uuid, "fetch user failed");
StatusCode::INTERNAL_SERVER_ERROR StatusCode::INTERNAL_SERVER_ERROR
})? {
Some(u) => u,
None => return Ok(None),
};
let perms = db::perms::get_by_uuid(&state.db_pool, uuid)
.await
.map_err(|e| {
error!(error = %e, user_uuid = %uuid, "fetch permissions failed");
StatusCode::INTERNAL_SERVER_ERROR
})?; })?;
Ok(user)
match perms {
Some(perms) => {
user.attach_permissions(perms);
}
None => {
error!(user_uuid = %uuid, "permissions missing for existing user");
return Err(StatusCode::INTERNAL_SERVER_ERROR);
}
}
Ok(Some(user))
} }
pub async fn get_by_uuid(state: Arc<AppState>, uuid: Uuid) -> anyhow::Result<Option<InternalUser>> { pub async fn get_by_uuid(state: Arc<AppState>, uuid: Uuid) -> anyhow::Result<Option<InternalUser>> {
debug!(user_uuid = %uuid, "fetch internal user started"); debug!(user_uuid = %uuid, "fetch internal user started");
let mut user = match db::user::get_by_uuid(&state.db_pool, uuid) let mut user = match db::user::get_by_uuid(&state.db_pool, uuid)
.await .await
.context("failed to fetch user by uuid")? .context("failed to fetch user by uuid")?
@@ -74,48 +142,80 @@ pub async fn get_by_uuid(state: Arc<AppState>, uuid: Uuid) -> anyhow::Result<Opt
None => return Ok(None), None => return Ok(None),
}; };
let perms_exist = db::perms::exists_by_uuid(&state.db_pool, user.uuid) let perms = db::perms::get_by_uuid(&state.db_pool, user.uuid)
.await .await
.context("failed to check if user permissions exist")?; .context("failed to fetch user permissions")?;
if perms_exist { match perms {
if let Some(perms) = db::perms::get_by_uuid(&state.db_pool, user.uuid) Some(perms) => {
.await
.context("failed to fetch user permissions")?
{
user.attach_permissions(perms); user.attach_permissions(perms);
} }
None => {}
} }
let user = user;
Ok(Some(user)) Ok(Some(user))
} }
pub async fn set_perms( pub async fn get_safe_by_username(
state: Arc<AppState>, state: Arc<AppState>,
user_perms: UserPermissions, username: &str,
) -> Result<(), StatusCode> { ) -> Result<Option<User>, StatusCode> {
debug!(user_uuid = %user_perms.uuid, "assign permissions started"); debug!(%username, "fetch user by username started");
let exists = db::user::exists_by_uuid(&state.db_pool, user_perms.uuid)
let mut user = match db::user::get_safe_by_username(&state.db_pool, username)
.await .await
.map_err(|e| { .map_err(|e| {
error!(error = %e, user_uuid = %user_perms.uuid, "verify user exists failed"); error!(error = %e, %username, "fetch user by username failed");
StatusCode::INTERNAL_SERVER_ERROR
})? {
Some(u) => u,
None => return Ok(None),
};
let perms = db::perms::get_by_uuid(&state.db_pool, user.uuid)
.await
.map_err(|e| {
error!(error = %e, user_uuid = %user.uuid, "fetch permissions failed");
StatusCode::INTERNAL_SERVER_ERROR StatusCode::INTERNAL_SERVER_ERROR
})?; })?;
if !exists { match perms {
warn!(user_uuid = %user_perms.uuid, "assign permissions skipped for missing user"); Some(perms) => {
return Err(StatusCode::BAD_REQUEST); user.attach_permissions(perms);
}
None => {
error!(user_uuid = %user.uuid, "permissions missing for existing user");
return Err(StatusCode::INTERNAL_SERVER_ERROR);
}
} }
db::perms::create(&state.db_pool, user_perms) Ok(Some(user))
.await }
.map_err(|e| {
error!(error = %e, "create user permissions entry failed"); pub async fn get_by_username(
StatusCode::INTERNAL_SERVER_ERROR state: Arc<AppState>,
})?; username: &str,
) -> anyhow::Result<Option<InternalUser>> {
info!("user permissions assigned"); debug!(%username, "fetch internal user by username started");
Ok(())
let mut user = match db::user::get_by_username(&state.db_pool, username)
.await
.context("failed to fetch user by username")?
{
Some(u) => u,
None => return Ok(None),
};
let perms = db::perms::get_by_uuid(&state.db_pool, user.uuid)
.await
.context("failed to fetch user permissions")?;
match perms {
Some(perms) => {
user.attach_permissions(perms);
}
None => {}
}
Ok(Some(user))
} }

View File

@@ -0,0 +1,14 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize)]
pub struct LoginData {
pub username: String,
pub password: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AuthClaims {
pub exp: i64,
pub iat: i64,
pub username: String,
}

View File

@@ -1,3 +1,4 @@
pub mod api;
pub mod user; pub mod user;
pub mod user_prems; pub mod user_prems;
pub mod validation; pub mod validation;

View File

@@ -5,7 +5,7 @@ use sqlx::prelude::FromRow;
use uuid::Uuid; use uuid::Uuid;
use validator::Validate; use validator::Validate;
use crate::domain::user_prems::{MemberUserPermissions, UserPermissions}; use crate::domain::user_prems::UserPermissions;
use crate::domain::validation; use crate::domain::validation;
use crate::auth; use crate::auth;
@@ -25,7 +25,7 @@ pub struct NewUser {
first_name: Option<String>, first_name: Option<String>,
#[validate(length(min = 1, max = 64))] #[validate(length(min = 1, max = 64))]
last_name: Option<String>, last_name: Option<String>,
permissions: Option<MemberUserPermissions>, permissions: UserPermissions,
} }
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
@@ -36,7 +36,7 @@ pub struct InternalNewUser {
pub password_hash: String, pub password_hash: String,
pub first_name: Option<String>, pub first_name: Option<String>,
pub last_name: Option<String>, pub last_name: Option<String>,
pub permissions: Option<MemberUserPermissions>, pub permissions: UserPermissions,
} }
#[derive(Debug, Clone, Deserialize, FromRow)] #[derive(Debug, Clone, Deserialize, FromRow)]
@@ -48,16 +48,18 @@ pub struct InternalUser {
pub first_name: Option<String>, pub first_name: Option<String>,
pub last_name: Option<String>, pub last_name: Option<String>,
#[sqlx(skip)] #[sqlx(skip)]
pub permissions: Option<MemberUserPermissions>, pub permissions: UserPermissions,
} }
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] #[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct User { pub struct User {
uuid: Uuid, pub uuid: Uuid,
username: String, pub username: String,
email: Option<String>, pub email: Option<String>,
first_name: Option<String>, pub first_name: Option<String>,
last_name: Option<String>, pub last_name: Option<String>,
#[sqlx(skip)]
pub permissions: UserPermissions,
} }
#[derive(Debug)] #[derive(Debug)]
@@ -91,6 +93,7 @@ impl From<InternalUser> for User {
email: value.email, email: value.email,
first_name: value.first_name, first_name: value.first_name,
last_name: value.last_name, last_name: value.last_name,
permissions: value.permissions.clone(),
} }
} }
} }
@@ -105,6 +108,11 @@ impl Display for UserConversionError {
impl InternalUser { impl InternalUser {
pub fn attach_permissions(&mut self, permissions: UserPermissions) { pub fn attach_permissions(&mut self, permissions: UserPermissions) {
self.permissions = Some(MemberUserPermissions::from(permissions)); self.permissions = UserPermissions::from(permissions);
}
}
impl User {
pub fn attach_permissions(&mut self, permissions: UserPermissions) {
self.permissions = UserPermissions::from(permissions);
} }
} }

View File

@@ -1,4 +1,4 @@
use serde::Deserialize; use serde::{Deserialize, Serialize};
use sqlx::prelude::FromRow; use sqlx::prelude::FromRow;
use uuid::Uuid; use uuid::Uuid;
@@ -9,27 +9,19 @@ pub enum UserActions {
Login, Login,
} }
#[derive(Debug, Clone, Deserialize, FromRow)] #[derive(Debug, Clone, Deserialize, Serialize, FromRow)]
pub struct UserPermissions { pub struct UserPermissions {
pub uuid: Uuid,
pub root: bool, pub root: bool,
pub manage_users: bool, pub manage_users: bool,
pub login: bool, pub login: bool,
} }
#[derive(Debug, Clone, Deserialize, FromRow)] impl Default for UserPermissions {
pub struct MemberUserPermissions { fn default() -> Self {
pub root: bool,
pub manage_users: bool,
pub login: bool,
}
impl From<UserPermissions> for MemberUserPermissions {
fn from(value: UserPermissions) -> Self {
Self { Self {
root: value.root, root: false,
manage_users: value.manage_users, manage_users: false,
login: value.login, login: false,
} }
} }
} }

View File

@@ -4,8 +4,12 @@ use uuid::Uuid;
use crate::{domain::user_prems::UserPermissions, prelude::*}; use crate::{domain::user_prems::UserPermissions, prelude::*};
pub async fn create(pool: &PgPool, new_perms: UserPermissions) -> Result<UserPermissions> { pub async fn create(
debug!(user_uuid = %new_perms.uuid, "insert user permissions started"); pool: &PgPool,
uuid: Uuid,
new_perms: UserPermissions,
) -> Result<UserPermissions> {
debug!(user_uuid = %uuid, "insert user permissions started");
let perms = sqlx::query_as::<_, UserPermissions>( let perms = sqlx::query_as::<_, UserPermissions>(
r#" r#"
INSERT INTO user_permissions (uuid, root, manage_users, login) INSERT INTO user_permissions (uuid, root, manage_users, login)
@@ -13,14 +17,14 @@ pub async fn create(pool: &PgPool, new_perms: UserPermissions) -> Result<UserPer
RETURNING uuid, root, manage_users, login RETURNING uuid, root, manage_users, login
"#, "#,
) )
.bind(new_perms.uuid) .bind(uuid)
.bind(new_perms.root) .bind(new_perms.root)
.bind(new_perms.manage_users) .bind(new_perms.manage_users)
.bind(new_perms.login) .bind(new_perms.login)
.fetch_one(pool) .fetch_one(pool)
.await?; .await?;
debug!(user_uuid = %perms.uuid, "insert user permissions completed"); debug!(user_uuid = %uuid, "insert user permissions completed");
Ok(perms) Ok(perms)
} }

View File

@@ -43,6 +43,21 @@ pub async fn get_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<Option<InternalUse
Ok(user) Ok(user)
} }
pub async fn get_by_username(pool: &PgPool, username: &str) -> Result<Option<InternalUser>> {
debug!(username = %username, "fetch user by username started");
let user = sqlx::query_as::<_, InternalUser>(
r#"
SELECT uuid, username, email, password_hash, first_name, last_name FROM users WHERE username = $1
"#,
)
.bind(username)
.fetch_optional(pool)
.await?;
debug!(username = %username, "fetch user by username completed");
Ok(user)
}
pub async fn get_safe_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<Option<User>> { pub async fn get_safe_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<Option<User>> {
debug!(user_uuid = %uuid, "fetch safe user by uuid started"); debug!(user_uuid = %uuid, "fetch safe user by uuid started");
let user = sqlx::query_as::<_, User>( let user = sqlx::query_as::<_, User>(
@@ -58,6 +73,21 @@ pub async fn get_safe_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<Option<User>>
Ok(user) Ok(user)
} }
pub async fn get_safe_by_username(pool: &PgPool, username: &str) -> Result<Option<User>> {
debug!(username = %username, "fetch safe user by username started");
let user = sqlx::query_as::<_, User>(
r#"
SELECT uuid, username, email, first_name, last_name FROM users WHERE uuid = $1
"#,
)
.bind(username)
.fetch_optional(pool)
.await?;
debug!(username = %username, "fetch safe user by username completed");
Ok(user)
}
pub async fn get_all(pool: &PgPool) -> Result<Vec<InternalUser>> { pub async fn get_all(pool: &PgPool) -> Result<Vec<InternalUser>> {
debug!("fetch all internal users started"); debug!("fetch all internal users started");
let users = sqlx::query_as::<_, InternalUser>( let users = sqlx::query_as::<_, InternalUser>(
@@ -108,3 +138,22 @@ pub async fn exists_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<bool> {
debug!(user_uuid = %uuid, "check user existence completed"); debug!(user_uuid = %uuid, "check user existence completed");
Ok(exists) Ok(exists)
} }
pub async fn exists_by_username(pool: &PgPool, username: &str) -> Result<bool> {
debug!(username = %username, "check user existence started");
let exists = sqlx::query_scalar::<_, bool>(
r#"
SELECT EXISTS(
SELECT 1
FROM users
WHERE uuid = $1
)
"#,
)
.bind(username)
.fetch_one(pool)
.await?;
debug!(username = %username, "check user existence completed");
Ok(exists)
}

View File

@@ -1,21 +1,63 @@
use crate::{core::user_routines, prelude::*};
use std::sync::Arc;
use axum::{ use axum::{
extract::Request, extract::{Request, State},
http::{Method, header::AUTHORIZATION}, http::{self, Method, StatusCode, header::AUTHORIZATION},
middleware::Next, middleware::{Next, from_fn_with_state},
response::IntoResponse, response::Response,
}; };
use tower_http::cors::{Any, CorsLayer}; use tower_http::cors::{Any, CorsLayer};
use tracing::debug; use tracing::debug;
pub async fn auth(request: Request, next: Next) -> impl IntoResponse { use crate::{auth::verify_jwt, infra::db, state::AppState};
let method = request.method().clone();
let uri = request.uri().path().to_owned();
debug!(%method, uri, "auth middleware started"); pub async fn auth(
let response = next.run(request).await; State(state): State<Arc<AppState>>,
let status = response.status(); mut req: Request,
debug!(%method, uri, %status, "auth middleware completed"); next: Next,
response ) -> Result<Response, StatusCode> {
// 1) Extract Authorization header
let auth_header = req
.headers()
.get(http::header::AUTHORIZATION)
.ok_or(StatusCode::FORBIDDEN)?; // no header at all
let auth_header = auth_header.to_str().map_err(|e| {
error!("Failed to parse Authorization header: {}", e);
StatusCode::FORBIDDEN
})?;
// 2) Expect "Bearer <token>"
let mut parts = auth_header.split_whitespace();
let (scheme, token) = match (parts.next(), parts.next()) {
(Some(scheme), Some(token)) if scheme.eq_ignore_ascii_case("bearer") => (scheme, token),
_ => {
// either wrong scheme or missing token
return Err(StatusCode::UNAUTHORIZED);
}
};
// 3) Verify JWT
let token_data = verify_jwt(token.to_string())?; // verify_jwt(&str) -> Result<TokenData<AuthClaims>, StatusCode>
let username = &token_data.claims.username;
// 4) Load current user from DB
let current_user = match user_routines::get_by_username(state, username)
.await
.map_err(|e| {
error!("Error when fetching user via routine: {}", e);
return StatusCode::INTERNAL_SERVER_ERROR;
})? {
Some(user) => user,
None => return Err(StatusCode::INTERNAL_SERVER_ERROR),
};
// 5) Attach user to request extensions so handlers can grab it
req.extensions_mut().insert(current_user);
// 6) Continue down the stack
Ok(next.run(req).await)
} }
pub fn cors() -> CorsLayer { pub fn cors() -> CorsLayer {

View File

@@ -4,39 +4,54 @@ pub mod user_routes;
use axum::{ use axum::{
Json, Router, Json, Router,
http::StatusCode, http::StatusCode,
middleware::from_fn_with_state,
routing::{get, post}, routing::{get, post},
}; };
use serde_json::{Value, json}; use serde_json::{Value, json};
use std::sync::Arc; use std::sync::Arc;
use tower::ServiceBuilder; use tower::{Layer, ServiceBuilder};
use crate::prelude::*; use crate::prelude::*;
use crate::state::AppState; use crate::state::AppState;
macro_rules! middleware {
(cors) => {
crate::router::middleware::cors()
};
(cors_auth, $state:expr) => {
(
crate::router::middleware::cors(),
axum::middleware::from_fn_with_state($state, crate::router::middleware::auth),
)
};
}
pub async fn init_router(app_state: Arc<AppState>) -> Router { pub async fn init_router(app_state: Arc<AppState>) -> Router {
info!("router initialization started"); info!("router initialization started");
let router = Router::new() let router = Router::new()
.route( .route("/api/ping", get(ping).layer(middleware!(cors)))
"/api/ping",
get(ping).layer(
ServiceBuilder::new()
.layer(middleware::cors())
.layer(axum::middleware::from_fn(middleware::auth)),
),
)
.route( .route(
"/api/users", "/api/users",
post(user_routes::create) post(user_routes::create)
.layer(ServiceBuilder::new().layer(middleware::cors())) .layer(middleware!(cors_auth, app_state.clone()))
.with_state(app_state.clone()) .with_state(app_state.clone())
.get(user_routes::get_all) .get(user_routes::get_all)
.layer(ServiceBuilder::new().layer(middleware::cors())) .layer(middleware!(cors_auth, app_state.clone()))
.with_state(app_state.clone()), .with_state(app_state.clone()),
) )
.route("/api/users/{uuid}", get(user_routes::get_uuid)) .route(
.layer(ServiceBuilder::new().layer(middleware::cors())) "/api/users/{uuid}",
.with_state(app_state.clone()); get(user_routes::get_uuid)
.layer(middleware!(cors_auth, app_state.clone()))
.with_state(app_state.clone()),
)
.route(
"/api/login",
post(user_routes::login)
.layer(middleware!(cors))
.with_state(app_state.clone()),
);
info!("router initialization completed"); info!("router initialization completed");
router router

View File

@@ -1,4 +1,4 @@
use crate::prelude::*; use crate::{domain::api::LoginData, prelude::*};
use std::sync::Arc; use std::sync::Arc;
use crate::{ use crate::{
@@ -40,3 +40,11 @@ pub async fn get_uuid(
debug!("get user by uuid route completed"); debug!("get user by uuid route completed");
Ok(Json(user)) Ok(Json(user))
} }
pub async fn login(
State(state): State<Arc<AppState>>,
Json(login_data): Json<LoginData>,
) -> Result<Json<String>, StatusCode> {
let result = core::user_routines::login(state, login_data).await?;
Ok(Json(result))
}