refactor: structure of an hexagonal architecture
Created a kernel crate to store models and future action implementations. Will be useful to create admin cli.
This commit is contained in:
parent
69af48bb62
commit
3713cc2443
87 changed files with 834 additions and 474 deletions
22
lib/kernel/Cargo.toml
Normal file
22
lib/kernel/Cargo.toml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "kernel"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
utils = { path = "../utils" }
|
||||
|
||||
log = { workspace = true }
|
||||
env_logger = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
fully_pub = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
sqlx = { workspace = true }
|
||||
dotenvy = { workspace = true }
|
||||
|
||||
uuid = { workspace = true }
|
||||
url = { workspace = true }
|
||||
0
lib/kernel/src/actions/mod.rs
Normal file
0
lib/kernel/src/actions/mod.rs
Normal file
0
lib/kernel/src/actions/user.rs
Normal file
0
lib/kernel/src/actions/user.rs
Normal file
4
lib/kernel/src/consts.rs
Normal file
4
lib/kernel/src/consts.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
pub const DEFAULT_DB_PATH: &str = "/var/lib/minauthator/minauthator.db";
|
||||
pub const DEFAULT_ASSETS_PATH: &str = "/usr/local/lib/minauthator/assets";
|
||||
pub const DEFAULT_CONFIG_PATH: &str = "/etc/minauthator/config.yaml";
|
||||
|
||||
51
lib/kernel/src/context.rs
Normal file
51
lib/kernel/src/context.rs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
use std::{env, fs};
|
||||
use anyhow::{Result, Context, anyhow};
|
||||
use fully_pub::fully_pub;
|
||||
|
||||
use log::info;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
use crate::{
|
||||
consts::{DEFAULT_CONFIG_PATH, DEFAULT_DB_PATH}, database::prepare_database, models::config::Config, repositories::storage::Storage
|
||||
};
|
||||
|
||||
/// get server config
|
||||
fn get_config(path: String) -> Result<Config> {
|
||||
let inp_def_yaml = fs::read_to_string(path)
|
||||
.expect("Should have been able to read the the config file");
|
||||
|
||||
toml::from_str(&inp_def_yaml)
|
||||
.map_err(|e| anyhow!("Failed to parse config, {:?}", e))
|
||||
}
|
||||
|
||||
#[fully_pub]
|
||||
struct StartKernelConfig {
|
||||
config_path: Option<String>,
|
||||
database_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[fully_pub]
|
||||
struct AppSecrets {
|
||||
jwt_secret: String
|
||||
}
|
||||
|
||||
pub async fn get_kernel_context(start_config: StartKernelConfig) -> Result<(Config, AppSecrets, Storage)> {
|
||||
env_logger::init();
|
||||
let _ = dotenvy::dotenv();
|
||||
|
||||
let database_path = &start_config.database_path.unwrap_or(DEFAULT_DB_PATH.to_string());
|
||||
info!("Using database file at {}", database_path);
|
||||
let storage = prepare_database(database_path).await.context("Could not prepare db.")?;
|
||||
|
||||
let config_path = start_config.config_path.unwrap_or(DEFAULT_CONFIG_PATH.to_string());
|
||||
info!("Using config file at {}", &config_path);
|
||||
let config: Config = get_config(config_path)
|
||||
.expect("Cannot get config.");
|
||||
|
||||
dotenvy::dotenv().context("loading .env")?;
|
||||
let secrets = AppSecrets {
|
||||
jwt_secret: env::var("APP_JWT_SECRET").context("Expecting APP_JWT_SECRET env var.")?
|
||||
};
|
||||
|
||||
Ok((config, secrets, storage))
|
||||
}
|
||||
21
lib/kernel/src/database.rs
Normal file
21
lib/kernel/src/database.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use anyhow::{Context, Result};
|
||||
use sqlx::{sqlite::{SqliteConnectOptions, SqlitePoolOptions}, ConnectOptions};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::repositories::storage::Storage;
|
||||
|
||||
pub async fn prepare_database(sqlite_db_path: &str) -> Result<Storage> {
|
||||
let conn_str = format!("sqlite:{}", sqlite_db_path);
|
||||
|
||||
let pool = SqlitePoolOptions::new()
|
||||
.max_connections(50)
|
||||
.connect_with(
|
||||
SqliteConnectOptions::from_str(&conn_str)?
|
||||
.log_statements(log::LevelFilter::Trace)
|
||||
)
|
||||
.await
|
||||
.context("could not connect to database_url")?;
|
||||
|
||||
Ok(Storage(pool))
|
||||
}
|
||||
|
||||
7
lib/kernel/src/lib.rs
Normal file
7
lib/kernel/src/lib.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
pub mod models;
|
||||
pub mod database;
|
||||
pub mod consts;
|
||||
pub mod context;
|
||||
pub mod actions;
|
||||
pub mod repositories;
|
||||
|
||||
33
lib/kernel/src/models/authorization.rs
Normal file
33
lib/kernel/src/models/authorization.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use fully_pub::fully_pub;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::types::Json;
|
||||
use strum_macros::{Display, EnumIter, EnumString};
|
||||
|
||||
#[derive(
|
||||
Clone, Debug, Serialize, Deserialize, PartialEq,
|
||||
sqlx::Type,
|
||||
Display, EnumString, EnumIter
|
||||
)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum AuthorizationScope {
|
||||
UserReadBasic,
|
||||
UserReadRoles
|
||||
}
|
||||
|
||||
#[derive(sqlx::FromRow, Deserialize, Serialize, Debug)]
|
||||
#[fully_pub]
|
||||
struct Authorization {
|
||||
/// uuid
|
||||
id: String,
|
||||
user_id: String,
|
||||
/// app_id
|
||||
client_id: String,
|
||||
scopes: Json<Vec<AuthorizationScope>>,
|
||||
|
||||
/// defined in https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2
|
||||
code: String,
|
||||
last_used_at: Option<DateTime<Utc>>,
|
||||
created_at: DateTime<Utc>
|
||||
}
|
||||
|
||||
76
lib/kernel/src/models/config.rs
Normal file
76
lib/kernel/src/models/config.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
use fully_pub::fully_pub;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const fn _default_true() -> bool { true }
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[fully_pub]
|
||||
/// Instance branding/customization config
|
||||
struct InstanceConfig {
|
||||
base_uri: String,
|
||||
name: String,
|
||||
logo_uri: Option<String>
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[fully_pub]
|
||||
enum AppAuthorizeFlow {
|
||||
/// user must grant the app
|
||||
Explicit,
|
||||
/// authorized by default for all scopes
|
||||
Implicit
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[fully_pub]
|
||||
enum AppVisibility {
|
||||
/// app is public (visible to non-signed in user), useful for app discovery
|
||||
Public,
|
||||
/// app is visible to all signed-in users
|
||||
Internal,
|
||||
/// app will be only visible when the user reach the login endpoint
|
||||
Private
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[fully_pub]
|
||||
struct Application {
|
||||
slug: String,
|
||||
name: String,
|
||||
description: String,
|
||||
logo_uri: Option<String>,
|
||||
client_id: String,
|
||||
client_secret: String,
|
||||
allowed_redirect_uris: Vec<String>,
|
||||
authorize_flow: AppAuthorizeFlow,
|
||||
visibility: AppVisibility,
|
||||
login_uri: String
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[fully_pub]
|
||||
struct Role {
|
||||
slug: String,
|
||||
name: String,
|
||||
description: Option<String>,
|
||||
#[serde(default = "_default_true")]
|
||||
default: bool
|
||||
}
|
||||
|
||||
// todo: Role hierarchy https://en.wikipedia.org/wiki/Role_hierarchy
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[fully_pub]
|
||||
/// Configuration of this Minauthator instance
|
||||
struct Config {
|
||||
instance: InstanceConfig,
|
||||
applications: Vec<Application>,
|
||||
roles: Vec<Role>
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[fully_pub]
|
||||
struct AppSecrets {
|
||||
jwt_secret: String
|
||||
}
|
||||
3
lib/kernel/src/models/mod.rs
Normal file
3
lib/kernel/src/models/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
pub mod config;
|
||||
pub mod user;
|
||||
pub mod authorization;
|
||||
31
lib/kernel/src/models/user.rs
Normal file
31
lib/kernel/src/models/user.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
use fully_pub::fully_pub;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::types::Json;
|
||||
|
||||
#[derive(sqlx::Type, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(strum_macros::Display)]
|
||||
#[fully_pub]
|
||||
enum UserStatus {
|
||||
Active,
|
||||
Disabled
|
||||
}
|
||||
|
||||
#[derive(sqlx::FromRow, Deserialize, Serialize, Debug)]
|
||||
#[fully_pub]
|
||||
struct User {
|
||||
/// uuid
|
||||
id: String,
|
||||
handle: String,
|
||||
full_name: Option<String>,
|
||||
email: Option<String>,
|
||||
website: Option<String>,
|
||||
picture: Option<Vec<u8>>, // embeded blob to store profile pic
|
||||
password_hash: Option<String>, // argon2 password hash
|
||||
status: UserStatus,
|
||||
roles: Json<Vec<String>>,
|
||||
activation_token: Option<String>,
|
||||
|
||||
last_login_at: Option<DateTime<Utc>>,
|
||||
created_at: DateTime<Utc>
|
||||
}
|
||||
2
lib/kernel/src/repositories/mod.rs
Normal file
2
lib/kernel/src/repositories/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub mod storage;
|
||||
pub mod users;
|
||||
7
lib/kernel/src/repositories/storage.rs
Normal file
7
lib/kernel/src/repositories/storage.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
use fully_pub::fully_pub;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
|
||||
/// storage interface
|
||||
#[fully_pub]
|
||||
#[derive(Clone, Debug)]
|
||||
struct Storage(Pool<Sqlite>);
|
||||
14
lib/kernel/src/repositories/users.rs
Normal file
14
lib/kernel/src/repositories/users.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// user repositories
|
||||
|
||||
use crate::models::user::User;
|
||||
|
||||
use super::storage::Storage;
|
||||
use anyhow::{Result, Context};
|
||||
|
||||
async fn get_user_by_id(storage: &Storage, user_id: &str) -> Result<User> {
|
||||
sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = $1")
|
||||
.bind(user_id)
|
||||
.fetch_one(&storage.0)
|
||||
.await
|
||||
.context("To get user from claim")
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue