Compare commits
10 Commits
89b01a3921
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 400b63462a | |||
| 0796a3403f | |||
| 20a09a672a | |||
| 9bccfcf7c3 | |||
| c1fee82ba9 | |||
| 1ae9057baf | |||
| 2b31e36060 | |||
| 78b6215ffb | |||
|
|
ca5b5bfa96 | ||
|
|
1b3faeeefc |
10
README.md
10
README.md
@@ -8,7 +8,6 @@ A Rust-based Minecraft server manager.
|
|||||||
- [Roadmap](#roadmap)
|
- [Roadmap](#roadmap)
|
||||||
- [Contributing](#contributing)
|
- [Contributing](#contributing)
|
||||||
- [Use of AI](#use-of-ai)
|
- [Use of AI](#use-of-ai)
|
||||||
- [License](#license)
|
|
||||||
|
|
||||||
## Background
|
## Background
|
||||||
After a forced restart corrupted my previous server manager, [Crafty 4](https://gitlab.com/crafty-controller/crafty-4), I started looking for alternatives that were:
|
After a forced restart corrupted my previous server manager, [Crafty 4](https://gitlab.com/crafty-controller/crafty-4), I started looking for alternatives that were:
|
||||||
@@ -47,5 +46,10 @@ If you believe you can provide meaningful help, feel free to reach out directly:
|
|||||||
## Use of AI
|
## Use of AI
|
||||||
In the scope of this project AI is currently only being used to standardize command line output with the `info!, warn!, error!` and `debug!` macros from tracing.
|
In the scope of this project AI is currently only being used to standardize command line output with the `info!, warn!, error!` and `debug!` macros from tracing.
|
||||||
|
|
||||||
## License
|
---
|
||||||
[PolyForm Noncommercial License 1.0.0](LICENSE)
|
|
||||||
|
<div align="center">
|
||||||
|
<a href="./LICENSE">PolyForm Noncommercial License</a> • h3cx
|
||||||
|
|
||||||
|
Built on coffee, late nights, and a fully up-to-date Arch install (btw).
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -22,8 +22,10 @@ tower = { version = "0.5.2", features = ["tokio", "tracing"] }
|
|||||||
tower-http = { version = "0.6.7", features = ["cors"] }
|
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"
|
||||||
uuid = { version = "1.18.1", features = ["v4", "serde"] }
|
uuid = { version = "1.19.0", features = ["v4", "serde"] }
|
||||||
validator = { version = "0.20.0", features = ["derive"] }
|
validator = { version = "0.20.0", features = ["derive"] }
|
||||||
|
mineguard = {path = "../../../MineGuard/"}
|
||||||
|
axum-extra = { version = "0.12.2", features = ["cookie"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
chrono = "0.4.42"
|
chrono = "0.4.42"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
CREATE TABLE user_permissions(
|
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,
|
permissions JSON NOT NULL
|
||||||
login BOOL NOT NULL
|
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use std::i64;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use argon2::{
|
use argon2::{
|
||||||
Argon2, PasswordHash, PasswordVerifier,
|
Argon2, PasswordHash, PasswordVerifier,
|
||||||
password_hash::{PasswordHasher, SaltString, rand_core::OsRng},
|
password_hash::{PasswordHasher, SaltString, rand_core::OsRng},
|
||||||
};
|
};
|
||||||
use axum::{extract::State, http::StatusCode};
|
use axum::http::StatusCode;
|
||||||
use chrono::{Duration, Utc};
|
use chrono::{Duration, Utc};
|
||||||
use jsonwebtoken::{DecodingKey, EncodingKey, Header, TokenData, Validation, decode, encode};
|
use jsonwebtoken::{DecodingKey, EncodingKey, Header, TokenData, Validation, decode, encode};
|
||||||
|
|
||||||
@@ -26,8 +25,8 @@ pub fn verify_password(password: &str, password_hash: &str) -> Result<bool, pass
|
|||||||
let parsed_hash = PasswordHash::new(password_hash)?;
|
let parsed_hash = PasswordHash::new(password_hash)?;
|
||||||
let argon2 = Argon2::default();
|
let argon2 = Argon2::default();
|
||||||
match argon2.verify_password(password.as_bytes(), &parsed_hash) {
|
match argon2.verify_password(password.as_bytes(), &parsed_hash) {
|
||||||
Ok(_) => return Ok(true),
|
Ok(_) => Ok(true),
|
||||||
Err(password_hash::Error::Password) => return Ok(false),
|
Err(password_hash::Error::Password) => Ok(false),
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,8 +36,8 @@ pub fn gen_jwt(username: String) -> Result<String, StatusCode> {
|
|||||||
let secret: String = "verysafestring".to_string();
|
let secret: String = "verysafestring".to_string();
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
let expire: chrono::TimeDelta = Duration::hours(24);
|
let expire: chrono::TimeDelta = Duration::hours(24);
|
||||||
let exp = (now + expire).timestamp() as i64;
|
let exp = (now + expire).timestamp();
|
||||||
let iat = now.timestamp() as i64;
|
let iat = now.timestamp();
|
||||||
let claim = AuthClaims { iat, exp, username };
|
let claim = AuthClaims { iat, exp, username };
|
||||||
|
|
||||||
encode(
|
encode(
|
||||||
@@ -48,7 +47,7 @@ pub fn gen_jwt(username: String) -> Result<String, StatusCode> {
|
|||||||
)
|
)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!(error = %e, username = claim.username, "create jwt failed");
|
error!(error = %e, username = claim.username, "create jwt failed");
|
||||||
return StatusCode::INTERNAL_SERVER_ERROR;
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +60,7 @@ pub fn verify_jwt(token: String) -> Result<TokenData<AuthClaims>, StatusCode> {
|
|||||||
)
|
)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!(error = %e, "verify jwt failed");
|
error!(error = %e, "verify jwt failed");
|
||||||
return StatusCode::INTERNAL_SERVER_ERROR;
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
});
|
});
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
use axum::http::Method;
|
use axum::{
|
||||||
|
Json, RequestExt,
|
||||||
|
extract::{MatchedPath, Request, State},
|
||||||
|
http::{Method, StatusCode},
|
||||||
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::domain::user_prems::UserActions;
|
use crate::domain::user_prems::{UserActions, UserPermissions};
|
||||||
|
|
||||||
#[derive(Debug, Hash, Clone, PartialEq, Eq)]
|
#[derive(Debug, Hash, Clone, PartialEq, Eq)]
|
||||||
pub struct RouteKey {
|
pub struct RouteKey {
|
||||||
@@ -12,7 +16,7 @@ pub struct RouteKey {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AppCfg {
|
pub struct AppCfg {
|
||||||
pub db_path: String,
|
pub db_path: String,
|
||||||
pub route_perms: HashMap<RouteKey, Vec<UserActions>>,
|
pub route_perms: HashMap<RouteKey, UserPermissions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppCfg {
|
impl AppCfg {
|
||||||
@@ -27,6 +31,7 @@ impl AppCfg {
|
|||||||
&mut self,
|
&mut self,
|
||||||
method: Method,
|
method: Method,
|
||||||
path: impl Into<String>,
|
path: impl Into<String>,
|
||||||
|
root: bool,
|
||||||
perms: Vec<UserActions>,
|
perms: Vec<UserActions>,
|
||||||
) {
|
) {
|
||||||
let key = RouteKey {
|
let key = RouteKey {
|
||||||
@@ -34,37 +39,46 @@ impl AppCfg {
|
|||||||
path: path.into(),
|
path: path.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.route_perms.insert(key, perms);
|
let user_perms = UserPermissions {
|
||||||
|
root,
|
||||||
|
permissions: perms.into_iter().collect(), // Vec → HashSet
|
||||||
|
};
|
||||||
|
|
||||||
|
self.route_perms.insert(key, user_perms);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_route_perms(&self, method: &Method, path: &str) -> Option<&Vec<UserActions>> {
|
pub fn get_route_perms(&self, method: &Method, path: &str) -> Option<UserPermissions> {
|
||||||
let key = RouteKey {
|
let key = RouteKey {
|
||||||
method: method.clone(),
|
method: method.clone(),
|
||||||
path: path.to_string(),
|
path: path.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.route_perms.get(&key)
|
let perm = match self.route_perms.get(&key) {
|
||||||
}
|
Some(val) => val.clone(),
|
||||||
|
None => return None,
|
||||||
pub fn route_allows(&self, method: &Method, path: &str, user_perms: &[UserActions]) -> bool {
|
|
||||||
if user_perms.contains(&UserActions::Root) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let key = RouteKey {
|
|
||||||
method: method.clone(),
|
|
||||||
path: path.to_string(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let required = match self.route_perms.get(&key) {
|
Some(perm)
|
||||||
Some(perms) => perms,
|
}
|
||||||
None => return true, // no perms required → allow
|
|
||||||
|
pub async fn route_allows(
|
||||||
|
&self,
|
||||||
|
method: &Method,
|
||||||
|
path: &str,
|
||||||
|
user_perms: UserPermissions,
|
||||||
|
) -> Result<bool, StatusCode> {
|
||||||
|
let req_perms = match self.get_route_perms(method, path) {
|
||||||
|
Some(val) => val,
|
||||||
|
None => return Ok(false),
|
||||||
};
|
};
|
||||||
|
|
||||||
if required.contains(&UserActions::Root) {
|
if req_perms.root {
|
||||||
return false;
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
required.iter().all(|p| user_perms.contains(p))
|
Ok(req_perms
|
||||||
|
.permissions
|
||||||
|
.iter()
|
||||||
|
.all(|action| user_perms.permissions.contains(action)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
auth::{gen_jwt, verify_password},
|
auth::{gen_jwt, verify_password},
|
||||||
domain::{api::LoginData, user::InternalUser, user_prems::UserPermissions},
|
domain::{
|
||||||
|
api::LoginData,
|
||||||
|
user::InternalUser,
|
||||||
|
user_prems::{ExtUserPermissions, UserPermissions},
|
||||||
|
},
|
||||||
infra::db,
|
infra::db,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use axum::{extract::State, http::StatusCode};
|
use axum::http::StatusCode;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
@@ -17,14 +21,17 @@ use crate::{
|
|||||||
state::AppState,
|
state::AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn login(state: Arc<AppState>, login_data: LoginData) -> Result<String, StatusCode> {
|
pub async fn login(
|
||||||
|
state: Arc<AppState>,
|
||||||
|
login_data: LoginData,
|
||||||
|
) -> Result<(String, User), StatusCode> {
|
||||||
debug!(username = login_data.username.as_str(), "login started");
|
debug!(username = login_data.username.as_str(), "login started");
|
||||||
|
|
||||||
let user = db::user::get_by_username(&state.db_pool, &login_data.username)
|
let user = db::user::get_by_username(&state.db_pool, &login_data.username)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!(error = %e, username = login_data.username.as_str(), "fetch user during login failed");
|
error!(error = %e, username = login_data.username.as_str(), "fetch user during login failed");
|
||||||
return StatusCode::INTERNAL_SERVER_ERROR;
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
})?;
|
})?;
|
||||||
let user = match user {
|
let user = match user {
|
||||||
Some(value) => value,
|
Some(value) => value,
|
||||||
@@ -33,7 +40,7 @@ pub async fn login(state: Arc<AppState>, login_data: LoginData) -> Result<String
|
|||||||
|
|
||||||
let verify = verify_password(&login_data.password, &user.password_hash).map_err(|e| {
|
let verify = verify_password(&login_data.password, &user.password_hash).map_err(|e| {
|
||||||
error!(error = %e, username = login_data.username.as_str(), "verify password hash failed");
|
error!(error = %e, username = login_data.username.as_str(), "verify password hash failed");
|
||||||
return StatusCode::INTERNAL_SERVER_ERROR;
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !verify {
|
if !verify {
|
||||||
@@ -45,7 +52,7 @@ pub async fn login(state: Arc<AppState>, login_data: LoginData) -> Result<String
|
|||||||
StatusCode::INTERNAL_SERVER_ERROR
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(token)
|
Ok((token, User::from(user)))
|
||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
||||||
@@ -68,11 +75,7 @@ pub async fn create(state: Arc<AppState>, new_user: NewUser) -> Result<User, Sta
|
|||||||
StatusCode::INTERNAL_SERVER_ERROR
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let perms = db::perms::create(
|
let perms = db::perms::create(&state.db_pool, created_user.uuid, internal.permissions)
|
||||||
&state.db_pool,
|
|
||||||
created_user.uuid.clone(),
|
|
||||||
internal.permissions,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!(error = %e, "create user permissions entry failed");
|
error!(error = %e, "create user permissions entry failed");
|
||||||
@@ -89,11 +92,24 @@ pub async fn create(state: Arc<AppState>, new_user: NewUser) -> Result<User, Sta
|
|||||||
|
|
||||||
pub async fn get_all(state: Arc<AppState>) -> Result<Vec<User>, StatusCode> {
|
pub async fn get_all(state: Arc<AppState>) -> Result<Vec<User>, StatusCode> {
|
||||||
debug!("fetch all users started");
|
debug!("fetch all users started");
|
||||||
let users = db::user::get_safe_all(&state.db_pool).await.map_err(|e| {
|
let mut users = db::user::get_safe_all(&state.db_pool).await.map_err(|e| {
|
||||||
error!(error = %e, "fetch all users failed");
|
error!(error = %e, "fetch all users failed");
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
let perms = db::perms::get_all(&state.db_pool).await.map_err(|e| {
|
||||||
|
error!(error = %e, "fetch all permissions failed");
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
})?;
|
||||||
|
|
||||||
|
users.iter_mut().for_each(|u| {
|
||||||
|
let u_perms = match perms.iter().find(|p| p.uuid == u.uuid) {
|
||||||
|
Some(val) => UserPermissions::from(val.clone()),
|
||||||
|
None => UserPermissions::new(),
|
||||||
|
};
|
||||||
|
u.attach_permissions(u_perms);
|
||||||
|
});
|
||||||
|
|
||||||
Ok(users)
|
Ok(users)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,12 +164,9 @@ pub async fn get_by_uuid(state: Arc<AppState>, uuid: Uuid) -> anyhow::Result<Opt
|
|||||||
.await
|
.await
|
||||||
.context("failed to fetch user permissions")?;
|
.context("failed to fetch user permissions")?;
|
||||||
|
|
||||||
match perms {
|
if let Some(perms) = perms {
|
||||||
Some(perms) => {
|
|
||||||
user.attach_permissions(perms);
|
user.attach_permissions(perms);
|
||||||
}
|
}
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Some(user))
|
Ok(Some(user))
|
||||||
}
|
}
|
||||||
@@ -212,12 +225,9 @@ pub async fn get_by_username(
|
|||||||
.await
|
.await
|
||||||
.context("failed to fetch user permissions")?;
|
.context("failed to fetch user permissions")?;
|
||||||
|
|
||||||
match perms {
|
if let Some(perms) = perms {
|
||||||
Some(perms) => {
|
|
||||||
user.attach_permissions(perms);
|
user.attach_permissions(perms);
|
||||||
}
|
}
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Some(user))
|
Ok(Some(user))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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::UserPermissions;
|
use crate::domain::user_prems::{ExtUserPermissions, UserPermissions};
|
||||||
use crate::domain::validation;
|
use crate::domain::validation;
|
||||||
|
|
||||||
use crate::auth;
|
use crate::auth;
|
||||||
@@ -71,13 +71,13 @@ impl TryFrom<NewUser> for InternalNewUser {
|
|||||||
type Error = UserConversionError;
|
type Error = UserConversionError;
|
||||||
fn try_from(value: NewUser) -> Result<Self, Self::Error> {
|
fn try_from(value: NewUser) -> Result<Self, Self::Error> {
|
||||||
let password_hash =
|
let password_hash =
|
||||||
auth::hash_password(&value.password).map_err(|e| UserConversionError::HashFailed(e))?;
|
auth::hash_password(&value.password).map_err(UserConversionError::HashFailed)?;
|
||||||
let uuid = Uuid::new_v4();
|
let uuid = Uuid::new_v4();
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
uuid: uuid,
|
uuid,
|
||||||
username: value.username,
|
username: value.username,
|
||||||
email: value.email,
|
email: value.email,
|
||||||
password_hash: password_hash,
|
password_hash,
|
||||||
first_name: value.first_name,
|
first_name: value.first_name,
|
||||||
last_name: value.last_name,
|
last_name: value.last_name,
|
||||||
permissions: value.permissions.clone(),
|
permissions: value.permissions.clone(),
|
||||||
@@ -108,12 +108,12 @@ 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 = UserPermissions::from(permissions);
|
self.permissions = permissions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl User {
|
impl User {
|
||||||
pub fn attach_permissions(&mut self, permissions: UserPermissions) {
|
pub fn attach_permissions(&mut self, permissions: UserPermissions) {
|
||||||
self.permissions = UserPermissions::from(permissions);
|
self.permissions = permissions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +1,88 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::prelude::FromRow;
|
use sqlx::{prelude::FromRow, types::Json};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
pub enum UserActions {
|
pub enum UserActions {
|
||||||
Root,
|
|
||||||
ManageUsers,
|
ManageUsers,
|
||||||
Login,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, FromRow)]
|
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||||
pub struct UserPermissions {
|
pub struct UserPermissions {
|
||||||
pub root: bool,
|
pub root: bool,
|
||||||
pub manage_users: bool,
|
pub permissions: HashSet<UserActions>,
|
||||||
pub login: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for UserPermissions {
|
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||||
fn default() -> Self {
|
pub struct ExtUserPermissions {
|
||||||
|
pub uuid: Uuid,
|
||||||
|
pub root: bool,
|
||||||
|
pub permissions: HashSet<UserActions>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, FromRow)]
|
||||||
|
pub struct UserPermissionsRow {
|
||||||
|
pub root: bool,
|
||||||
|
pub permissions: Json<HashSet<UserActions>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, FromRow)]
|
||||||
|
pub struct ExtUserPermissionsRow {
|
||||||
|
pub uuid: Uuid,
|
||||||
|
pub root: bool,
|
||||||
|
pub permissions: Json<HashSet<UserActions>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<UserPermissions> for UserPermissionsRow {
|
||||||
|
fn from(value: UserPermissions) -> Self {
|
||||||
Self {
|
Self {
|
||||||
root: false,
|
root: value.root,
|
||||||
manage_users: false,
|
permissions: Json(value.permissions),
|
||||||
login: false,
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<UserPermissionsRow> for UserPermissions {
|
||||||
|
fn from(value: UserPermissionsRow) -> Self {
|
||||||
|
Self {
|
||||||
|
root: value.root,
|
||||||
|
permissions: value.permissions.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ExtUserPermissionsRow> for ExtUserPermissions {
|
||||||
|
fn from(value: ExtUserPermissionsRow) -> Self {
|
||||||
|
Self {
|
||||||
|
uuid: value.uuid,
|
||||||
|
root: value.root,
|
||||||
|
permissions: value.permissions.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ExtUserPermissions> for UserPermissions {
|
||||||
|
fn from(value: ExtUserPermissions) -> Self {
|
||||||
|
Self {
|
||||||
|
root: value.root,
|
||||||
|
permissions: value.permissions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserPermissions {
|
impl UserPermissions {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
root: false,
|
||||||
|
permissions: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn root() -> Self {
|
pub fn root() -> Self {
|
||||||
Self {
|
Self {
|
||||||
root: true,
|
root: true,
|
||||||
manage_users: true,
|
permissions: HashSet::new(),
|
||||||
login: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ use anyhow::Result;
|
|||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{domain::user_prems::UserPermissions, prelude::*};
|
use crate::{
|
||||||
|
domain::user_prems::{
|
||||||
|
ExtUserPermissions, ExtUserPermissionsRow, UserPermissions, UserPermissionsRow,
|
||||||
|
},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
@@ -10,29 +15,29 @@ pub async fn create(
|
|||||||
new_perms: UserPermissions,
|
new_perms: UserPermissions,
|
||||||
) -> Result<UserPermissions> {
|
) -> Result<UserPermissions> {
|
||||||
debug!(user_uuid = %uuid, "insert user permissions started");
|
debug!(user_uuid = %uuid, "insert user permissions started");
|
||||||
let perms = sqlx::query_as::<_, UserPermissions>(
|
let insert = UserPermissionsRow::from(new_perms);
|
||||||
|
let perms = sqlx::query_as::<_, UserPermissionsRow>(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO user_permissions (uuid, root, manage_users, login)
|
INSERT INTO user_permissions (uuid, root, permissions)
|
||||||
VALUES ($1, $2, $3, $4)
|
VALUES ($1, $2, $3)
|
||||||
RETURNING uuid, root, manage_users, login
|
RETURNING root, permissions
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(uuid)
|
.bind(uuid)
|
||||||
.bind(new_perms.root)
|
.bind(insert.root)
|
||||||
.bind(new_perms.manage_users)
|
.bind(insert.permissions)
|
||||||
.bind(new_perms.login)
|
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
debug!(user_uuid = %uuid, "insert user permissions completed");
|
debug!(user_uuid = %uuid, "insert user permissions completed");
|
||||||
Ok(perms)
|
Ok(UserPermissions::from(perms))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<Option<UserPermissions>> {
|
pub async fn get_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<Option<UserPermissions>> {
|
||||||
debug!(user_uuid = %uuid, "fetch user permissions by uuid started");
|
debug!(user_uuid = %uuid, "fetch user permissions by uuid started");
|
||||||
let perms = sqlx::query_as::<_, UserPermissions>(
|
let perms = sqlx::query_as::<_, UserPermissionsRow>(
|
||||||
r#"
|
r#"
|
||||||
SELECT uuid, root, manage_users, login
|
SELECT uuid, root, permissions
|
||||||
FROM user_permissions
|
FROM user_permissions
|
||||||
WHERE uuid = $1
|
WHERE uuid = $1
|
||||||
"#,
|
"#,
|
||||||
@@ -42,7 +47,10 @@ pub async fn get_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<Option<UserPermiss
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
debug!(user_uuid = %uuid, "fetch user permissions by uuid completed");
|
debug!(user_uuid = %uuid, "fetch user permissions by uuid completed");
|
||||||
Ok(perms)
|
match perms {
|
||||||
|
Some(val) => Ok(Some(UserPermissions::from(val))),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn exists_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<bool> {
|
pub async fn exists_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<bool> {
|
||||||
@@ -52,7 +60,7 @@ pub async fn exists_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<bool> {
|
|||||||
SELECT EXISTS(
|
SELECT EXISTS(
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM user_permissions
|
FROM user_permissions
|
||||||
WHERE uuis = $1
|
WHERE uuid = $1
|
||||||
)
|
)
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
@@ -63,3 +71,23 @@ pub async fn exists_by_uuid(pool: &PgPool, uuid: Uuid) -> Result<bool> {
|
|||||||
debug!(user_uuid = %uuid, "check user permissions existence completed");
|
debug!(user_uuid = %uuid, "check user permissions existence completed");
|
||||||
Ok(exists)
|
Ok(exists)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_all(pool: &PgPool) -> Result<Vec<ExtUserPermissions>> {
|
||||||
|
debug!("fetch all internal users started");
|
||||||
|
let users = sqlx::query_as::<_, ExtUserPermissionsRow>(
|
||||||
|
r#"
|
||||||
|
SELECT uuid, root, permissions
|
||||||
|
FROM user_permissions
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let clean = users
|
||||||
|
.iter()
|
||||||
|
.map(|v| ExtUserPermissions::from(v.clone()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
debug!("fetch all internal users completed");
|
||||||
|
Ok(clean)
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use rustymine_daemon::{
|
|||||||
router,
|
router,
|
||||||
state::{AppState, check_root},
|
state::{AppState, check_root},
|
||||||
};
|
};
|
||||||
use tracing::{Level, debug, info};
|
use tracing::{Level, info};
|
||||||
|
|
||||||
pub const APP_NAME: &str = env!("CARGO_PKG_NAME");
|
pub const APP_NAME: &str = env!("CARGO_PKG_NAME");
|
||||||
pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
|
pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
@@ -63,8 +63,14 @@ async fn main() -> Result<()> {
|
|||||||
route_perms: HashMap::new(),
|
route_perms: HashMap::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
config.insert_route_perms(Method::GET, "/api/login", vec![UserActions::Login]);
|
config.insert_route_perms(Method::GET, "/api/users", false, vec![]);
|
||||||
config.insert_route_perms(Method::GET, "/api/users", vec![UserActions::Login]);
|
config.insert_route_perms(Method::GET, "/api/users/{uuid}", false, vec![]);
|
||||||
|
config.insert_route_perms(
|
||||||
|
Method::POST,
|
||||||
|
"/api/users",
|
||||||
|
false,
|
||||||
|
vec![UserActions::ManageUsers],
|
||||||
|
);
|
||||||
|
|
||||||
let state = Arc::new(AppState::new(config).await);
|
let state = Arc::new(AppState::new(config).await);
|
||||||
check_root(state.clone()).await;
|
check_root(state.clone()).await;
|
||||||
|
|||||||
@@ -5,85 +5,96 @@ use axum::{
|
|||||||
Extension,
|
Extension,
|
||||||
extract::{MatchedPath, Request, State},
|
extract::{MatchedPath, Request, State},
|
||||||
http::{self, Method, StatusCode, header::AUTHORIZATION},
|
http::{self, Method, StatusCode, header::AUTHORIZATION},
|
||||||
middleware::{Next, from_fn_with_state},
|
middleware::Next,
|
||||||
response::Response,
|
response::Response,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use axum_extra::extract::CookieJar;
|
||||||
use tower_http::cors::{Any, CorsLayer};
|
use tower_http::cors::{Any, CorsLayer};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::{auth::verify_jwt, infra::db, state::AppState};
|
use crate::{auth::verify_jwt, state::AppState};
|
||||||
|
|
||||||
pub async fn auth(
|
pub async fn auth(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
|
jar: CookieJar,
|
||||||
mut req: Request,
|
mut req: Request,
|
||||||
next: Next,
|
next: Next,
|
||||||
) -> Result<Response, StatusCode> {
|
) -> Result<Response, StatusCode> {
|
||||||
let request_method = req.method().clone();
|
let method = req.method().clone();
|
||||||
let request_path = req.uri().path().to_string();
|
let path = req.uri().path().to_string();
|
||||||
|
|
||||||
debug!(method = ?request_method, path = request_path, "authenticate request started");
|
debug!(?method, path, "authenticate request started");
|
||||||
|
|
||||||
// 1) Extract Authorization header
|
// 1) Try JWT from cookie first
|
||||||
|
let token_from_cookie = jar
|
||||||
|
.get("auth_token")
|
||||||
|
.map(|cookie| cookie.value().to_owned());
|
||||||
|
|
||||||
|
// 2) If no cookie, fall back to Authorization: Bearer ...
|
||||||
|
let token = match token_from_cookie {
|
||||||
|
Some(t) => t,
|
||||||
|
None => {
|
||||||
let auth_header = req
|
let auth_header = req
|
||||||
.headers()
|
.headers()
|
||||||
.get(http::header::AUTHORIZATION)
|
.get(http::header::AUTHORIZATION)
|
||||||
.ok_or(StatusCode::FORBIDDEN)?; // no header at all
|
.ok_or(StatusCode::FORBIDDEN)?;
|
||||||
|
|
||||||
let auth_header = auth_header.to_str().map_err(|e| {
|
let auth_header = auth_header.to_str().map_err(|e| {
|
||||||
error!(error = %e, method = ?request_method, path = request_path, "authorization header parse failed");
|
error!(
|
||||||
|
error = %e,
|
||||||
|
?method,
|
||||||
|
path,
|
||||||
|
"authorization header parse failed"
|
||||||
|
);
|
||||||
StatusCode::FORBIDDEN
|
StatusCode::FORBIDDEN
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// 2) Expect "Bearer <token>"
|
|
||||||
let mut parts = auth_header.split_whitespace();
|
let mut parts = auth_header.split_whitespace();
|
||||||
let (scheme, token) = match (parts.next(), parts.next()) {
|
match (parts.next(), parts.next()) {
|
||||||
(Some(scheme), Some(token)) if scheme.eq_ignore_ascii_case("bearer") => (scheme, token),
|
(Some(scheme), Some(token)) if scheme.eq_ignore_ascii_case("bearer") => {
|
||||||
|
token.to_owned()
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// either wrong scheme or missing token
|
warn!(?method, path, "authorization header missing bearer token");
|
||||||
warn!(method = ?request_method, path = request_path, "authorization header missing bearer token");
|
|
||||||
return Err(StatusCode::UNAUTHORIZED);
|
return Err(StatusCode::UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3) Verify JWT
|
// 3) Verify JWT
|
||||||
let token_data = verify_jwt(token.to_string())?; // verify_jwt(&str) -> Result<TokenData<AuthClaims>, StatusCode>
|
let token_data = verify_jwt(token).map_err(|e| {
|
||||||
|
warn!(error = %e, ?method, path, "invalid jwt");
|
||||||
|
StatusCode::UNAUTHORIZED
|
||||||
|
})?;
|
||||||
let username = &token_data.claims.username;
|
let username = &token_data.claims.username;
|
||||||
|
|
||||||
// 4) Load current user from DB
|
// 4) Load user from DB
|
||||||
let current_user = match user_routines::get_by_username(state, username)
|
let current_user = user_routines::get_by_username(state, username)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!(error = %e, method = ?request_method, path = request_path, username, "fetch user for auth failed");
|
error!(
|
||||||
return StatusCode::INTERNAL_SERVER_ERROR;
|
error = %e,
|
||||||
})? {
|
?method,
|
||||||
Some(user) => user,
|
path,
|
||||||
None => {
|
username,
|
||||||
error!(method = ?request_method, path = request_path, username, "authenticated user missing in database");
|
"fetch user for auth failed"
|
||||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
);
|
||||||
}
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
};
|
})?
|
||||||
// 5) Attach user to request extensions so handlers can grab it
|
.ok_or_else(|| {
|
||||||
|
error!(
|
||||||
|
?method,
|
||||||
|
path, username, "authenticated user missing in database"
|
||||||
|
);
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// 5) Attach user to request extensions
|
||||||
req.extensions_mut().insert(current_user);
|
req.extensions_mut().insert(current_user);
|
||||||
|
|
||||||
// 6) Continue down the stack
|
// 6) Continue the chain
|
||||||
Ok(next.run(req).await)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn perms(
|
|
||||||
State(state): State<Arc<AppState>>,
|
|
||||||
Extension(user): Extension<InternalUser>,
|
|
||||||
mut req: Request,
|
|
||||||
next: Next,
|
|
||||||
) -> Result<Response, StatusCode> {
|
|
||||||
let method: Method = req.method().clone();
|
|
||||||
|
|
||||||
let path = req
|
|
||||||
.extensions()
|
|
||||||
.get::<MatchedPath>()
|
|
||||||
.map(|p| p.as_str().to_string())
|
|
||||||
.ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;
|
|
||||||
|
|
||||||
Ok(next.run(req).await)
|
Ok(next.run(req).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,3 +105,37 @@ pub fn cors() -> CorsLayer {
|
|||||||
.allow_methods([Method::GET, Method::POST])
|
.allow_methods([Method::GET, Method::POST])
|
||||||
.allow_headers([AUTHORIZATION])
|
.allow_headers([AUTHORIZATION])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn permissions(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
Extension(user): Extension<InternalUser>,
|
||||||
|
req: Request,
|
||||||
|
next: Next,
|
||||||
|
) -> Result<Response, StatusCode> {
|
||||||
|
let request_method = req.method().clone();
|
||||||
|
let request_path = req.uri().path().to_string();
|
||||||
|
|
||||||
|
debug!(method = ?request_method, path = request_path, "permissions request started");
|
||||||
|
debug!("Calling user {}", user.username.clone());
|
||||||
|
|
||||||
|
if user.permissions.root {
|
||||||
|
return Ok(next.run(req).await);
|
||||||
|
}
|
||||||
|
|
||||||
|
let method = req.method();
|
||||||
|
|
||||||
|
let path = req
|
||||||
|
.extensions()
|
||||||
|
.get::<MatchedPath>()
|
||||||
|
.map(|p| p.as_str())
|
||||||
|
.ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
match state
|
||||||
|
.config
|
||||||
|
.route_allows(method, path, user.permissions.clone())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(true) => Ok(next.run(req).await),
|
||||||
|
_ => Err(StatusCode::UNAUTHORIZED),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,12 +4,10 @@ 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::{Layer, ServiceBuilder};
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
@@ -24,6 +22,13 @@ macro_rules! middleware {
|
|||||||
axum::middleware::from_fn_with_state($state, crate::router::middleware::auth),
|
axum::middleware::from_fn_with_state($state, crate::router::middleware::auth),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
(cors_auth_perms, $state:expr) => {
|
||||||
|
(
|
||||||
|
crate::router::middleware::cors(),
|
||||||
|
axum::middleware::from_fn_with_state($state, crate::router::middleware::auth),
|
||||||
|
axum::middleware::from_fn_with_state($state, crate::router::middleware::permissions),
|
||||||
|
)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn init_router(app_state: Arc<AppState>) -> Router {
|
pub async fn init_router(app_state: Arc<AppState>) -> Router {
|
||||||
@@ -34,16 +39,16 @@ pub async fn init_router(app_state: Arc<AppState>) -> Router {
|
|||||||
.route(
|
.route(
|
||||||
"/api/users",
|
"/api/users",
|
||||||
post(user_routes::create)
|
post(user_routes::create)
|
||||||
.layer(middleware!(cors_auth, app_state.clone()))
|
.layer(middleware!(cors_auth_perms, app_state.clone()))
|
||||||
.with_state(app_state.clone())
|
.with_state(app_state.clone())
|
||||||
.get(user_routes::get_all)
|
.get(user_routes::get_all)
|
||||||
.layer(middleware!(cors_auth, app_state.clone()))
|
.layer(middleware!(cors_auth_perms, app_state.clone()))
|
||||||
.with_state(app_state.clone()),
|
.with_state(app_state.clone()),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/api/users/{uuid}",
|
"/api/users/{uuid}",
|
||||||
get(user_routes::get_uuid)
|
get(user_routes::get_uuid)
|
||||||
.layer(middleware!(cors_auth, app_state.clone()))
|
.layer(middleware!(cors_auth_perms, app_state.clone()))
|
||||||
.with_state(app_state.clone()),
|
.with_state(app_state.clone()),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
@@ -51,6 +56,18 @@ pub async fn init_router(app_state: Arc<AppState>) -> Router {
|
|||||||
post(user_routes::login)
|
post(user_routes::login)
|
||||||
.layer(middleware!(cors))
|
.layer(middleware!(cors))
|
||||||
.with_state(app_state.clone()),
|
.with_state(app_state.clone()),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/api/logout",
|
||||||
|
post(user_routes::logout)
|
||||||
|
.layer(middleware!(cors_auth, app_state.clone()))
|
||||||
|
.with_state(app_state.clone()),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/api/me",
|
||||||
|
get(user_routes::me)
|
||||||
|
.layer(middleware!(cors_auth, app_state.clone()))
|
||||||
|
.with_state(app_state.clone()),
|
||||||
);
|
);
|
||||||
|
|
||||||
info!("router initialization completed");
|
info!("router initialization completed");
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
use crate::{domain::api::LoginData, prelude::*};
|
use crate::{
|
||||||
|
domain::{api::LoginData, user::InternalUser},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -8,10 +11,14 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use axum::{
|
use axum::{
|
||||||
Json,
|
Extension, Json,
|
||||||
extract::{Path, State},
|
extract::{Path, State},
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
};
|
};
|
||||||
|
use axum_extra::extract::{
|
||||||
|
CookieJar,
|
||||||
|
cookie::{Cookie, SameSite},
|
||||||
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
@@ -43,8 +50,35 @@ pub async fn get_uuid(
|
|||||||
|
|
||||||
pub async fn login(
|
pub async fn login(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
|
jar: CookieJar,
|
||||||
Json(login_data): Json<LoginData>,
|
Json(login_data): Json<LoginData>,
|
||||||
) -> Result<Json<String>, StatusCode> {
|
) -> Result<(CookieJar, Json<User>), StatusCode> {
|
||||||
let result = core::user_routines::login(state, login_data).await?;
|
let (jwt, user) = core::user_routines::login(state, login_data).await?;
|
||||||
Ok(Json(result))
|
|
||||||
|
let cookie = Cookie::build(("auth_token", jwt))
|
||||||
|
.http_only(true)
|
||||||
|
.secure(false)
|
||||||
|
.same_site(SameSite::None)
|
||||||
|
.path("/")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let jar = jar.add(cookie);
|
||||||
|
|
||||||
|
Ok((jar, Json(user)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn logout(jar: CookieJar) -> Result<CookieJar, StatusCode> {
|
||||||
|
let cookie = Cookie::build(("auth_token", ""))
|
||||||
|
.path("/")
|
||||||
|
.http_only(true)
|
||||||
|
.build();
|
||||||
|
let jar = jar.remove(cookie);
|
||||||
|
|
||||||
|
Ok(jar)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn me(Extension(user): Extension<InternalUser>) -> Result<Json<User>, StatusCode> {
|
||||||
|
let clean = User::from(user);
|
||||||
|
|
||||||
|
Ok(Json(clean))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
use std::{collections::HashMap, process::exit, sync::Arc};
|
use std::{process::exit, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{core, domain::user::NewUser, prelude::*};
|
||||||
core,
|
|
||||||
domain::{
|
|
||||||
user::{InternalNewUser, NewUser},
|
|
||||||
user_prems::UserActions,
|
|
||||||
},
|
|
||||||
prelude::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
@@ -40,10 +33,7 @@ impl AppState {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
info!("database ready after connect and migrate");
|
info!("database ready after connect and migrate");
|
||||||
|
|
||||||
Self {
|
Self { db_pool, config }
|
||||||
db_pool: db_pool,
|
|
||||||
config: config,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user