├── crates
├── libs
│ ├── lib-web
│ │ ├── src
│ │ │ ├── utils
│ │ │ │ ├── mod.rs
│ │ │ │ └── token.rs
│ │ │ ├── routes
│ │ │ │ ├── mod.rs
│ │ │ │ └── routes_static.rs
│ │ │ ├── handlers
│ │ │ │ ├── mod.rs
│ │ │ │ ├── handlers_login.rs
│ │ │ │ └── handlers_rpc.rs
│ │ │ ├── middleware
│ │ │ │ ├── mod.rs
│ │ │ │ ├── mw_req_stamp.rs
│ │ │ │ ├── mw_res_map.rs
│ │ │ │ └── mw_auth.rs
│ │ │ ├── lib.rs
│ │ │ ├── log
│ │ │ │ └── mod.rs
│ │ │ └── error.rs
│ │ └── Cargo.toml
│ ├── lib-auth
│ │ ├── src
│ │ │ ├── lib.rs
│ │ │ ├── pwd
│ │ │ │ ├── scheme
│ │ │ │ │ ├── error.rs
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ ├── scheme_01.rs
│ │ │ │ │ └── scheme_02.rs
│ │ │ │ ├── error.rs
│ │ │ │ └── mod.rs
│ │ │ ├── token
│ │ │ │ ├── error.rs
│ │ │ │ └── mod.rs
│ │ │ └── config.rs
│ │ └── Cargo.toml
│ ├── lib-rpc-core
│ │ ├── src
│ │ │ ├── utils
│ │ │ │ ├── mod.rs
│ │ │ │ └── macro_utils.rs
│ │ │ ├── lib.rs
│ │ │ ├── prelude.rs
│ │ │ ├── rpc_result.rs
│ │ │ ├── rpc_params.rs
│ │ │ └── error.rs
│ │ └── Cargo.toml
│ ├── lib-core
│ │ ├── src
│ │ │ ├── model
│ │ │ │ ├── acs
│ │ │ │ │ └── mod.rs
│ │ │ │ ├── modql_utils.rs
│ │ │ │ ├── store
│ │ │ │ │ ├── dbx
│ │ │ │ │ │ ├── error.rs
│ │ │ │ │ │ └── mod.rs
│ │ │ │ │ └── mod.rs
│ │ │ │ ├── conv_user.rs
│ │ │ │ ├── base
│ │ │ │ │ ├── utils.rs
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ ├── macro_utils.rs
│ │ │ │ │ └── crud_fns.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── error.rs
│ │ │ │ ├── conv_msg.rs
│ │ │ │ ├── user.rs
│ │ │ │ ├── conv.rs
│ │ │ │ └── agent.rs
│ │ │ ├── lib.rs
│ │ │ ├── ctx
│ │ │ │ ├── error.rs
│ │ │ │ └── mod.rs
│ │ │ ├── config.rs
│ │ │ └── _dev_utils
│ │ │ │ ├── dev_db.rs
│ │ │ │ └── mod.rs
│ │ └── Cargo.toml
│ └── lib-utils
│ │ ├── Cargo.toml
│ │ └── src
│ │ ├── lib.rs
│ │ ├── b64.rs
│ │ ├── envs.rs
│ │ └── time.rs
├── services
│ └── web-server
│ │ ├── src
│ │ ├── web
│ │ │ ├── mod.rs
│ │ │ ├── rpcs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── agent_rpc.rs
│ │ │ │ └── conv_rpc.rs
│ │ │ ├── routes_login.rs
│ │ │ └── routes_rpc.rs
│ │ ├── error.rs
│ │ ├── config.rs
│ │ └── main.rs
│ │ ├── Cargo.toml
│ │ └── examples
│ │ └── quick_dev.rs
└── tools
│ └── gen-key
│ ├── Cargo.toml
│ └── src
│ └── main.rs
├── rustfmt.toml
├── web-folder
└── index.html
├── sql
└── dev_initial
│ ├── 00-recreate-db.sql
│ ├── 02-dev-seed.sql
│ └── 01-create-schema.sql
├── LICENSE-MIT
├── .cargo
└── config.toml
├── .gitignore
├── Cargo.toml
├── README.md
└── LICENSE-APACHE
/crates/libs/lib-web/src/utils/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod token;
--------------------------------------------------------------------------------
/crates/libs/lib-web/src/routes/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod routes_static;
--------------------------------------------------------------------------------
/crates/libs/lib-web/src/handlers/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod handlers_login;
2 | pub mod handlers_rpc;
--------------------------------------------------------------------------------
/crates/libs/lib-web/src/middleware/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod mw_auth;
2 | pub mod mw_req_stamp;
3 | pub mod mw_res_map;
--------------------------------------------------------------------------------
/crates/libs/lib-auth/src/lib.rs:
--------------------------------------------------------------------------------
1 | mod config;
2 | pub mod pwd;
3 | pub mod token;
4 |
5 | use config::auth_config;
6 |
--------------------------------------------------------------------------------
/crates/libs/lib-rpc-core/src/utils/mod.rs:
--------------------------------------------------------------------------------
1 | // region: --- Modules
2 |
3 | mod macro_utils;
4 |
5 | // endregion: --- Modules
6 |
--------------------------------------------------------------------------------
/crates/services/web-server/src/web/mod.rs:
--------------------------------------------------------------------------------
1 | // region: --- Modules
2 | pub mod routes_login;
3 | pub mod routes_rpc;
4 | pub mod rpcs;
5 |
6 | // endregion: --- Modules
7 |
--------------------------------------------------------------------------------
/crates/libs/lib-core/src/model/acs/mod.rs:
--------------------------------------------------------------------------------
1 | //! TO BE IMPLEMENTED
2 | //!
3 | //! `acs` Access Control System based on PBAC (Privilege Based Access Control)
4 | //!
5 | //! (more to come)
6 |
--------------------------------------------------------------------------------
/crates/libs/lib-web/src/lib.rs:
--------------------------------------------------------------------------------
1 | mod error;
2 |
3 | pub use error::Error;
4 |
5 | pub mod handlers;
6 | pub mod log;
7 | pub mod middleware;
8 | pub mod routes;
9 | pub mod utils;
10 |
--------------------------------------------------------------------------------
/rustfmt.toml:
--------------------------------------------------------------------------------
1 | # rustfmt doc - https://rust-lang.github.io/rustfmt/
2 |
3 | hard_tabs = true
4 | edition = "2021"
5 |
6 | # For recording
7 | max_width = 85
8 | # chain_width = 40
9 | # array_width = 40
--------------------------------------------------------------------------------
/crates/libs/lib-core/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod config;
2 | pub mod ctx;
3 | pub mod model;
4 |
5 | // #[cfg(test)] // Commented during early development.
6 | pub mod _dev_utils;
7 |
8 | use config::core_config;
9 |
--------------------------------------------------------------------------------
/web-folder/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | AWESOME-APP Web
7 |
8 |
9 |
10 | Hello World!
11 |
12 |
13 |
--------------------------------------------------------------------------------
/crates/tools/gen-key/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "gen-key"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | # -- App Crates
8 | lib-utils = { path = "../../libs/lib-utils"}
9 | # -- Others
10 | rand = "0.8"
11 |
--------------------------------------------------------------------------------
/crates/libs/lib-core/src/model/modql_utils.rs:
--------------------------------------------------------------------------------
1 | use time::serde::rfc3339;
2 |
3 | pub fn time_to_sea_value(
4 | json_value: serde_json::Value,
5 | ) -> modql::filter::SeaResult {
6 | Ok(rfc3339::deserialize(json_value)?.into())
7 | }
8 |
--------------------------------------------------------------------------------
/crates/libs/lib-utils/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "lib-utils"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | doctest = false
8 |
9 | [lints]
10 | workspace = true
11 |
12 | [dependencies]
13 | base64 = "0.22"
14 | time = { workspace = true }
15 |
--------------------------------------------------------------------------------
/crates/libs/lib-rpc-core/src/lib.rs:
--------------------------------------------------------------------------------
1 | // region: --- Modules
2 |
3 | mod error;
4 | mod rpc_params;
5 | mod rpc_result;
6 | mod utils;
7 |
8 | pub use self::error::{Error, Result};
9 | pub use rpc_params::*;
10 |
11 | pub mod prelude;
12 |
13 | // endregion: --- Modules
14 |
--------------------------------------------------------------------------------
/crates/libs/lib-utils/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! The utils module is designed to export independent sub-modules to the application code.
2 | //!
3 | //! Note: Even if the util sub-modules consist of a single file, they contain their own errors
4 | //! for improved compartmentalization.
5 | //!
6 |
7 | pub mod b64;
8 | pub mod envs;
9 | pub mod time;
10 |
--------------------------------------------------------------------------------
/crates/services/web-server/src/web/rpcs/mod.rs:
--------------------------------------------------------------------------------
1 | // region: --- Modules
2 |
3 | pub mod agent_rpc;
4 | pub mod conv_rpc;
5 |
6 | use rpc_router::{Router, RouterBuilder};
7 |
8 | // endregion: --- Modules
9 |
10 | pub fn all_rpc_router_builder() -> RouterBuilder {
11 | Router::builder()
12 | .extend(agent_rpc::rpc_router_builder())
13 | .extend(conv_rpc::rpc_router_builder())
14 | }
15 |
--------------------------------------------------------------------------------
/crates/services/web-server/src/web/routes_login.rs:
--------------------------------------------------------------------------------
1 | use axum::routing::post;
2 | use axum::Router;
3 | use lib_core::model::ModelManager;
4 | use lib_web::handlers::handlers_login;
5 |
6 | pub fn routes(mm: ModelManager) -> Router {
7 | Router::new()
8 | .route("/api/login", post(handlers_login::api_login_handler))
9 | .route("/api/logoff", post(handlers_login::api_logoff_handler))
10 | .with_state(mm)
11 | }
12 |
--------------------------------------------------------------------------------
/sql/dev_initial/00-recreate-db.sql:
--------------------------------------------------------------------------------
1 | -- DEV ONLY - Brute Force DROP DB (for local dev and unit test)
2 | SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE
3 | usename = 'app_user' OR datname = 'app_db';
4 | DROP DATABASE IF EXISTS app_db;
5 | DROP USER IF EXISTS app_user;
6 |
7 | -- DEV ONLY - Dev only password (for local dev and unit test).
8 | CREATE USER app_user PASSWORD 'dev_only_pwd';
9 | CREATE DATABASE app_db owner app_user ENCODING = 'UTF-8';
--------------------------------------------------------------------------------
/crates/tools/gen-key/src/main.rs:
--------------------------------------------------------------------------------
1 | pub type Result = core::result::Result;
2 | pub type Error = Box; // Ok for tools.
3 |
4 | use lib_utils::b64::b64u_encode;
5 | use rand::RngCore;
6 |
7 | fn main() -> Result<()> {
8 | let mut key = [0u8; 64]; // 512 bits = 64 bytes
9 | rand::thread_rng().fill_bytes(&mut key);
10 | println!("\nGenerated key from rand::thread_rng():\n{key:?}");
11 |
12 | let b64u = b64u_encode(key);
13 | println!("\nKey b64u encoded:\n{b64u}");
14 |
15 | Ok(())
16 | }
17 |
--------------------------------------------------------------------------------
/crates/libs/lib-rpc-core/src/prelude.rs:
--------------------------------------------------------------------------------
1 | //! This is a prelude for all .._rpc modules to avoid redundant imports.
2 | //! NOTE: This is only for the `rpcs` module and sub-modules.
3 |
4 | pub use crate::generate_common_rpc_fns;
5 | pub use crate::rpc_result::DataRpcResult;
6 | pub use crate::Result;
7 | pub use crate::{ParamsForCreate, ParamsForUpdate, ParamsIded, ParamsList};
8 | pub use lib_core::ctx::Ctx;
9 | pub use lib_core::model::ModelManager;
10 | pub use paste::paste;
11 | pub use rpc_router::{router_builder, RouterBuilder};
12 |
--------------------------------------------------------------------------------
/crates/libs/lib-core/src/ctx/error.rs:
--------------------------------------------------------------------------------
1 | use serde::Serialize;
2 |
3 | pub type Result = core::result::Result;
4 |
5 | #[derive(Debug, Serialize)]
6 | pub enum Error {
7 | CtxCannotNewRootCtx,
8 | }
9 |
10 | // region: --- Error Boilerplate
11 | impl core::fmt::Display for Error {
12 | fn fmt(
13 | &self,
14 | fmt: &mut core::fmt::Formatter,
15 | ) -> core::result::Result<(), core::fmt::Error> {
16 | write!(fmt, "{self:?}")
17 | }
18 | }
19 |
20 | impl std::error::Error for Error {}
21 | // endregion: --- Error Boilerplate
22 |
--------------------------------------------------------------------------------
/sql/dev_initial/02-dev-seed.sql:
--------------------------------------------------------------------------------
1 | -- root user (at id = 0)
2 | INSERT INTO "user"
3 | (id, typ, username, cid, ctime, mid, mtime) VALUES
4 | (0, 'Sys', 'root', 0, now(), 0, now());
5 |
6 | -- User demo1
7 | INSERT INTO "user"
8 | (username, cid, ctime, mid, mtime) VALUES
9 | ('demo1', 0, now(), 0, now());
10 |
11 | -- Agent mock-01 (with 'parrot' model) (id: 100)
12 | INSERT INTO "agent"
13 | (id, owner_id, name, cid, ctime, mid, mtime) VALUES
14 | (100, 0, 'mock-01', 0, now(), 0, now());
15 |
16 |
--------------------------------------------------------------------------------
/crates/services/web-server/src/error.rs:
--------------------------------------------------------------------------------
1 | use derive_more::From;
2 | use lib_core::model;
3 |
4 | pub type Result = core::result::Result;
5 |
6 | #[derive(Debug, From)]
7 | pub enum Error {
8 | // -- Modules
9 | #[from]
10 | Model(model::Error),
11 | }
12 |
13 | // region: --- Error Boilerplate
14 | impl core::fmt::Display for Error {
15 | fn fmt(
16 | &self,
17 | fmt: &mut core::fmt::Formatter,
18 | ) -> core::result::Result<(), core::fmt::Error> {
19 | write!(fmt, "{self:?}")
20 | }
21 | }
22 |
23 | impl std::error::Error for Error {}
24 | // endregion: --- Error Boilerplate
25 |
--------------------------------------------------------------------------------
/crates/libs/lib-auth/src/pwd/scheme/error.rs:
--------------------------------------------------------------------------------
1 | use serde::Serialize;
2 |
3 | pub type Result = core::result::Result;
4 |
5 | #[derive(Debug, Serialize)]
6 | pub enum Error {
7 | Key,
8 | Salt,
9 | Hash,
10 | PwdValidate,
11 | SchemeNotFound(String),
12 | }
13 |
14 | // region: --- Error Boilerplate
15 | impl core::fmt::Display for Error {
16 | fn fmt(
17 | &self,
18 | fmt: &mut core::fmt::Formatter,
19 | ) -> core::result::Result<(), core::fmt::Error> {
20 | write!(fmt, "{self:?}")
21 | }
22 | }
23 |
24 | impl std::error::Error for Error {}
25 | // endregion: --- Error Boilerplate
26 |
--------------------------------------------------------------------------------
/crates/services/web-server/src/web/rpcs/agent_rpc.rs:
--------------------------------------------------------------------------------
1 | use lib_rpc_core::prelude::*;
2 | use lib_core::model::agent::{
3 | Agent, AgentBmc, AgentFilter, AgentForCreate, AgentForUpdate,
4 | };
5 |
6 | pub fn rpc_router_builder() -> RouterBuilder {
7 | router_builder!(
8 | // Same as RpcRouter::new().add...
9 | create_agent,
10 | get_agent,
11 | list_agents,
12 | update_agent,
13 | delete_agent,
14 | )
15 | }
16 |
17 | generate_common_rpc_fns!(
18 | Bmc: AgentBmc,
19 | Entity: Agent,
20 | ForCreate: AgentForCreate,
21 | ForUpdate: AgentForUpdate,
22 | Filter: AgentFilter,
23 | Suffix: agent
24 | );
25 |
--------------------------------------------------------------------------------
/crates/libs/lib-web/src/routes/routes_static.rs:
--------------------------------------------------------------------------------
1 | use axum::handler::HandlerWithoutStateExt;
2 | use axum::http::StatusCode;
3 | use axum::routing::{any_service, MethodRouter};
4 | use tower_http::services::ServeDir;
5 |
6 | // Note: Here we can just return a MethodRouter rather than a full Router
7 | // since ServeDir is a service.
8 | pub fn serve_dir(web_folder: &'static String) -> MethodRouter {
9 | async fn handle_404() -> (StatusCode, &'static str) {
10 | (StatusCode::NOT_FOUND, "Resource not found.")
11 | }
12 |
13 | any_service(
14 | ServeDir::new(web_folder)
15 | .not_found_service(handle_404.into_service()),
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/crates/services/web-server/src/config.rs:
--------------------------------------------------------------------------------
1 | use lib_utils::envs::get_env;
2 | use std::sync::OnceLock;
3 |
4 | pub fn web_config() -> &'static WebConfig {
5 | static INSTANCE: OnceLock = OnceLock::new();
6 |
7 | INSTANCE.get_or_init(|| {
8 | WebConfig::load_from_env().unwrap_or_else(|ex| {
9 | panic!("FATAL - WHILE LOADING CONF - Cause: {ex:?}")
10 | })
11 | })
12 | }
13 |
14 | #[allow(non_snake_case)]
15 | pub struct WebConfig {
16 | pub WEB_FOLDER: String,
17 | }
18 |
19 | impl WebConfig {
20 | fn load_from_env() -> lib_utils::envs::Result {
21 | Ok(WebConfig {
22 | WEB_FOLDER: get_env("SERVICE_WEB_FOLDER")?,
23 | })
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/crates/libs/lib-auth/src/token/error.rs:
--------------------------------------------------------------------------------
1 | use serde::Serialize;
2 |
3 | pub type Result = core::result::Result;
4 |
5 | #[derive(Debug, Serialize)]
6 | pub enum Error {
7 | HmacFailNewFromSlice,
8 |
9 | InvalidFormat,
10 | CannotDecodeIdent,
11 | CannotDecodeExp,
12 | SignatureNotMatching,
13 | ExpNotIso,
14 | Expired,
15 | }
16 |
17 | // region: --- Error Boilerplate
18 | impl core::fmt::Display for Error {
19 | fn fmt(
20 | &self,
21 | fmt: &mut core::fmt::Formatter,
22 | ) -> core::result::Result<(), core::fmt::Error> {
23 | write!(fmt, "{self:?}")
24 | }
25 | }
26 |
27 | impl std::error::Error for Error {}
28 | // endregion: --- Error Boilerplate
29 |
--------------------------------------------------------------------------------
/crates/libs/lib-rpc-core/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "lib-rpc-core"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | doctest = false
8 |
9 | [lints]
10 | workspace = true
11 |
12 | [dependencies]
13 | # -- App Libs
14 | lib-core = { path = "../../libs/lib-core", features = ["with-rpc"] }
15 | # -- Async
16 | tokio = { version = "1", features = ["full"] }
17 | futures = "0.3"
18 | # -- Json
19 | serde = { version = "1", features = ["derive"] }
20 | serde_json = "1"
21 | serde_with = { workspace = true }
22 | # -- Data
23 | modql = { workspace = true }
24 | # -- Rpc
25 | rpc-router = { workspace = true }
26 | # -- Others
27 | paste = "1"
28 | derive_more = { workspace = true }
29 |
--------------------------------------------------------------------------------
/crates/libs/lib-auth/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "lib-auth"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | doctest = false
8 |
9 | [lints]
10 | workspace = true
11 |
12 | [dependencies]
13 | # -- App Libs
14 | lib-utils = { path = "../../libs/lib-utils"}
15 | # -- Async
16 | tokio = { version = "1", features = ["full"] }
17 | # -- Json
18 | serde = { version = "1", features = ["derive"] }
19 | # -- Hashing (pwd-scheme01 & Token)
20 | hmac = "0.12"
21 | sha2 = "0.10"
22 | blake3 = "1.5.5"
23 | # -- Hashing (pwd-scheme02)
24 | argon2 = {version="0.5", features=["std"]}
25 | # -- Others
26 | uuid = {version = "1", features = ["v4","fast-rng",]}
27 | lazy-regex = "3"
28 | derive_more = { workspace = true }
29 | enum_dispatch = "0.3"
30 |
--------------------------------------------------------------------------------
/crates/libs/lib-auth/src/pwd/error.rs:
--------------------------------------------------------------------------------
1 | use crate::pwd::scheme;
2 | use derive_more::From;
3 | use serde::Serialize;
4 |
5 | pub type Result = core::result::Result;
6 |
7 | #[derive(Debug, Serialize, From)]
8 | pub enum Error {
9 | PwdWithSchemeFailedParse,
10 |
11 | FailSpawnBlockForValidate,
12 | FailSpawnBlockForHash,
13 |
14 | // -- Modules
15 | #[from]
16 | Scheme(scheme::Error),
17 | }
18 |
19 | // region: --- Error Boilerplate
20 | impl core::fmt::Display for Error {
21 | fn fmt(
22 | &self,
23 | fmt: &mut core::fmt::Formatter,
24 | ) -> core::result::Result<(), core::fmt::Error> {
25 | write!(fmt, "{self:?}")
26 | }
27 | }
28 |
29 | impl std::error::Error for Error {}
30 | // endregion: --- Error Boilerplate
31 |
--------------------------------------------------------------------------------
/crates/services/web-server/src/web/routes_rpc.rs:
--------------------------------------------------------------------------------
1 | use crate::web::rpcs::all_rpc_router_builder;
2 | use axum::routing::post;
3 | use axum::Router;
4 | use lib_core::model::ModelManager;
5 | use lib_web::handlers::handlers_rpc;
6 |
7 | /// Build the Axum router for '/api/rpc'
8 | /// Note: This will build the `rpc-router::Router` that will be used by the
9 | /// rpc_axum_handler
10 | pub fn routes(mm: ModelManager) -> Router {
11 | // Build the combined Rpc Router (from `rpc-router` crate)
12 | let rpc_router = all_rpc_router_builder()
13 | // Add the common resources for all rpc calls
14 | .append_resource(mm)
15 | .build();
16 |
17 | // Build the Axum Router for '/rpc'
18 | Router::new()
19 | .route("/rpc", post(handlers_rpc::rpc_axum_handler))
20 | .with_state(rpc_router)
21 | }
22 |
--------------------------------------------------------------------------------
/crates/libs/lib-core/src/config.rs:
--------------------------------------------------------------------------------
1 | use lib_utils::envs::get_env;
2 | use std::sync::OnceLock;
3 |
4 | pub fn core_config() -> &'static CoreConfig {
5 | static INSTANCE: OnceLock = OnceLock::new();
6 |
7 | INSTANCE.get_or_init(|| {
8 | CoreConfig::load_from_env().unwrap_or_else(|ex| {
9 | panic!("FATAL - WHILE LOADING CONF - Cause: {ex:?}")
10 | })
11 | })
12 | }
13 |
14 | #[allow(non_snake_case)]
15 | pub struct CoreConfig {
16 | // -- Db
17 | pub DB_URL: String,
18 |
19 | // -- Web
20 | pub WEB_FOLDER: String,
21 | }
22 |
23 | impl CoreConfig {
24 | fn load_from_env() -> lib_utils::envs::Result {
25 | Ok(CoreConfig {
26 | // -- Db
27 | DB_URL: get_env("SERVICE_DB_URL")?,
28 |
29 | // -- Web
30 | WEB_FOLDER: get_env("SERVICE_WEB_FOLDER")?,
31 | })
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/crates/libs/lib-core/src/model/store/dbx/error.rs:
--------------------------------------------------------------------------------
1 | use derive_more::From;
2 | use serde::Serialize;
3 | use serde_with::{serde_as, DisplayFromStr};
4 |
5 | pub type Result = core::result::Result;
6 |
7 | #[serde_as]
8 | #[derive(Debug, Serialize, From)]
9 | pub enum Error {
10 | TxnCantCommitNoOpenTxn,
11 | CannotBeginTxnWithTxnFalse,
12 | CannotCommitTxnWithTxnFalse,
13 | NoTxn,
14 |
15 | // -- Externals
16 | #[from]
17 | Sqlx(#[serde_as(as = "DisplayFromStr")] sqlx::Error),
18 | }
19 |
20 | // region: --- Error Boilerplate
21 |
22 | impl core::fmt::Display for Error {
23 | fn fmt(
24 | &self,
25 | fmt: &mut core::fmt::Formatter,
26 | ) -> core::result::Result<(), core::fmt::Error> {
27 | write!(fmt, "{self:?}")
28 | }
29 | }
30 |
31 | impl std::error::Error for Error {}
32 |
33 | // endregion: --- Error Boilerplate
34 |
--------------------------------------------------------------------------------
/crates/libs/lib-web/src/utils/token.rs:
--------------------------------------------------------------------------------
1 |
2 | pub use crate::error::ClientError;
3 | pub use crate::error::{Error, Result};
4 | use lib_auth::token::generate_web_token;
5 | use tower_cookies::{Cookie, Cookies};
6 | use uuid::Uuid;
7 |
8 | // endregion: --- Modules
9 |
10 | pub(crate) const AUTH_TOKEN: &str = "auth-token";
11 |
12 | pub(crate) fn set_token_cookie(cookies: &Cookies, user: &str, salt: Uuid) -> Result<()> {
13 | let token = generate_web_token(user, salt)?;
14 |
15 | let mut cookie = Cookie::new(AUTH_TOKEN, token.to_string());
16 | cookie.set_http_only(true);
17 | cookie.set_path("/");
18 |
19 | cookies.add(cookie);
20 |
21 | Ok(())
22 | }
23 |
24 | pub(crate) fn remove_token_cookie(cookies: &Cookies) -> Result<()> {
25 | let mut cookie = Cookie::from(AUTH_TOKEN);
26 | cookie.set_path("/");
27 |
28 | cookies.remove(cookie);
29 |
30 | Ok(())
31 | }
32 |
--------------------------------------------------------------------------------
/crates/libs/lib-rpc-core/src/rpc_result.rs:
--------------------------------------------------------------------------------
1 | //! The `lib_rpc::response` module normalizes the JSON-RPC `.result` format for various
2 | //! JSON-RPC APIs.
3 | //!
4 | //! The primary type is the simple DataRpcResult, which contains only a `data` property.
5 | //!
6 | //! Notes:
7 | //! - In the future, we may introduce types like `DataRpcResult` that include metadata
8 | //! about the returned list data (e.g., pagination information).
9 | //! - Although the struct is named with `Result`, it is not a typical Rust result. Instead,
10 | //! it represents the `.result` property of a JSON-RPC response.
11 | //!
12 |
13 | use serde::Serialize;
14 |
15 | #[derive(Serialize)]
16 | pub struct DataRpcResult
17 | where
18 | T: Serialize,
19 | {
20 | data: T,
21 | }
22 |
23 | impl From for DataRpcResult
24 | where
25 | T: Serialize,
26 | {
27 | fn from(val: T) -> Self {
28 | Self { data: val }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/crates/libs/lib-auth/src/config.rs:
--------------------------------------------------------------------------------
1 | use lib_utils::envs::{get_env_b64u_as_u8s, get_env_parse};
2 | use std::sync::OnceLock;
3 |
4 | pub fn auth_config() -> &'static AuthConfig {
5 | static INSTANCE: OnceLock = OnceLock::new();
6 |
7 | INSTANCE.get_or_init(|| {
8 | AuthConfig::load_from_env().unwrap_or_else(|ex| {
9 | panic!("FATAL - WHILE LOADING CONF - Cause: {ex:?}")
10 | })
11 | })
12 | }
13 |
14 | #[allow(non_snake_case)]
15 | pub struct AuthConfig {
16 | // -- Crypt
17 | pub PWD_KEY: Vec,
18 |
19 | pub TOKEN_KEY: Vec,
20 | pub TOKEN_DURATION_SEC: f64,
21 | }
22 |
23 | impl AuthConfig {
24 | fn load_from_env() -> lib_utils::envs::Result {
25 | Ok(AuthConfig {
26 | // -- Crypt
27 | PWD_KEY: get_env_b64u_as_u8s("SERVICE_PWD_KEY")?,
28 |
29 | TOKEN_KEY: get_env_b64u_as_u8s("SERVICE_TOKEN_KEY")?,
30 | TOKEN_DURATION_SEC: get_env_parse("SERVICE_TOKEN_DURATION_SEC")?,
31 | })
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/crates/libs/lib-core/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "lib-core"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | doctest = false
8 |
9 | [lints]
10 | workspace = true
11 |
12 | [features]
13 | with-rpc = ["rpc-router"]
14 |
15 | [dependencies]
16 | # -- App Libs
17 | lib-utils = { path = "../../libs/lib-utils"}
18 | lib-auth = { path = "../../libs/lib-auth"}
19 | # -- Async
20 | tokio = { version = "1", features = ["full"] }
21 | # -- Json
22 | serde = { version = "1", features = ["derive"] }
23 | serde_json = "1"
24 | serde_with = { workspace = true }
25 | # -- Data
26 | sqlx = { workspace = true }
27 | sea-query = { workspace = true }
28 | sea-query-binder = { workspace = true }
29 | modql = { workspace = true }
30 | # -- Tracing
31 | tracing = "0.1"
32 | # -- Others
33 | uuid = {version = "1", features = ["v4","fast-rng",]}
34 | time = { workspace = true }
35 | derive_more = { workspace = true }
36 |
37 | # -- Feature: with-rpc
38 | rpc-router = { workspace = true, optional = true }
39 |
40 | [dev-dependencies]
41 | serial_test = "3"
--------------------------------------------------------------------------------
/crates/libs/lib-web/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "lib-web"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | # -- App Libs
8 | lib-utils = { path = "../../libs/lib-utils"}
9 | lib-rpc-core = { path = "../../libs/lib-rpc-core"}
10 | lib-auth = { path = "../../libs/lib-auth"}
11 | lib-core = { path = "../../libs/lib-core"}
12 |
13 | # -- Async
14 | tokio = { version = "1", features = ["full"] }
15 | # -- Json
16 | serde = { version = "1", features = ["derive"] }
17 | serde_json = "1"
18 | serde_with = { workspace = true }
19 | # -- Web
20 | axum = { workspace = true }
21 | tower-http = { workspace = true }
22 | tower-cookies = { workspace = true }
23 | # -- Tracing
24 | tracing = "0.1"
25 | tracing-subscriber = { version = "0.3", features = ["env-filter"] }
26 | # -- Rpc
27 | rpc-router = { workspace = true }
28 | # -- Others
29 | time = { workspace = true }
30 | uuid = {version = "1", features = ["v4","fast-rng",]}
31 | strum_macros = "0.26"
32 | derive_more = { workspace = true }
33 |
34 | [lints]
35 | workspace = true
36 |
--------------------------------------------------------------------------------
/crates/services/web-server/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "web-server"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | # -- App Libs
8 | lib-utils = { path = "../../libs/lib-utils"}
9 | lib-rpc-core = { path = "../../libs/lib-rpc-core"}
10 | lib-auth = { path = "../../libs/lib-auth"}
11 | lib-core = { path = "../../libs/lib-core"}
12 | lib-web = { path = "../../libs/lib-web"}
13 | # -- Async
14 | tokio = { version = "1", features = ["full"] }
15 | # -- Json
16 | serde = { version = "1", features = ["derive"] }
17 | serde_json = "1"
18 | serde_with = { workspace = true }
19 | # -- Web
20 | axum = { workspace = true }
21 | tower-http = { workspace = true }
22 | tower-cookies = { workspace = true }
23 | # -- Tracing
24 | tracing = "0.1"
25 | tracing-subscriber = { version = "0.3", features = ["env-filter"] }
26 | # -- Rpc
27 | rpc-router = { workspace = true }
28 | # -- Others
29 | time = { workspace = true }
30 | uuid = {version = "1", features = ["v4","fast-rng",]}
31 | strum_macros = "0.26"
32 | derive_more = { workspace = true }
33 |
34 | [dev-dependencies]
35 | httpc-test = "0.1"
36 |
--------------------------------------------------------------------------------
/crates/libs/lib-utils/src/b64.rs:
--------------------------------------------------------------------------------
1 | use base64::engine::{general_purpose, Engine};
2 |
3 | pub fn b64u_encode(content: impl AsRef<[u8]>) -> String {
4 | general_purpose::URL_SAFE_NO_PAD.encode(content)
5 | }
6 |
7 | pub fn b64u_decode(b64u: &str) -> Result> {
8 | general_purpose::URL_SAFE_NO_PAD
9 | .decode(b64u)
10 | .map_err(|_| Error::FailToB64uDecode)
11 | }
12 |
13 | pub fn b64u_decode_to_string(b64u: &str) -> Result {
14 | b64u_decode(b64u)
15 | .ok()
16 | .and_then(|r| String::from_utf8(r).ok())
17 | .ok_or(Error::FailToB64uDecode)
18 | }
19 |
20 | // region: --- Error
21 |
22 | pub type Result = core::result::Result;
23 |
24 | #[derive(Debug)]
25 | pub enum Error {
26 | FailToB64uDecode,
27 | }
28 |
29 | // region: --- Error Boilerplate
30 | impl core::fmt::Display for Error {
31 | fn fmt(
32 | &self,
33 | fmt: &mut core::fmt::Formatter,
34 | ) -> core::result::Result<(), core::fmt::Error> {
35 | write!(fmt, "{self:?}")
36 | }
37 | }
38 |
39 | impl std::error::Error for Error {}
40 | // endregion: --- Error Boilerplate
41 |
42 | // endregion: --- Error
43 |
--------------------------------------------------------------------------------
/crates/libs/lib-utils/src/envs.rs:
--------------------------------------------------------------------------------
1 | use crate::b64::b64u_decode;
2 | use std::env;
3 | use std::str::FromStr;
4 |
5 | pub fn get_env(name: &'static str) -> Result {
6 | env::var(name).map_err(|_| Error::MissingEnv(name))
7 | }
8 |
9 | pub fn get_env_parse(name: &'static str) -> Result {
10 | let val = get_env(name)?;
11 | val.parse::().map_err(|_| Error::WrongFormat(name))
12 | }
13 |
14 | pub fn get_env_b64u_as_u8s(name: &'static str) -> Result> {
15 | b64u_decode(&get_env(name)?).map_err(|_| Error::WrongFormat(name))
16 | }
17 |
18 | // region: --- Error
19 | pub type Result = core::result::Result;
20 |
21 | #[derive(Debug)]
22 | pub enum Error {
23 | MissingEnv(&'static str),
24 | WrongFormat(&'static str),
25 | }
26 |
27 | // region: --- Error Boilerplate
28 | impl core::fmt::Display for Error {
29 | fn fmt(
30 | &self,
31 | fmt: &mut core::fmt::Formatter,
32 | ) -> core::result::Result<(), core::fmt::Error> {
33 | write!(fmt, "{self:?}")
34 | }
35 | }
36 |
37 | impl std::error::Error for Error {}
38 | // endregion: --- Error Boilerplate
39 |
40 | // endregion: --- Error
41 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2023 Jeremy Chone
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/crates/libs/lib-auth/src/pwd/scheme/mod.rs:
--------------------------------------------------------------------------------
1 | // region: --- Modules
2 |
3 | mod error;
4 | mod scheme_01;
5 | mod scheme_02;
6 |
7 | pub use self::error::{Error, Result};
8 |
9 | use crate::pwd::ContentToHash;
10 | use enum_dispatch::enum_dispatch;
11 |
12 | // endregion: --- Modules
13 |
14 | pub const DEFAULT_SCHEME: &str = "02";
15 |
16 | #[derive(Debug)]
17 | pub enum SchemeStatus {
18 | Ok, // The pwd uses the latest scheme. All good.
19 | Outdated, // The pwd uses an old scheme.
20 | }
21 |
22 | #[enum_dispatch]
23 | pub trait Scheme {
24 | fn hash(&self, to_hash: &ContentToHash) -> Result;
25 |
26 | fn validate(&self, to_hash: &ContentToHash, pwd_ref: &str) -> Result<()>;
27 | }
28 |
29 | #[enum_dispatch(Scheme)]
30 | pub enum SchemeDispatcher {
31 | Scheme01(scheme_01::Scheme01),
32 | Scheme02(scheme_02::Scheme02),
33 | }
34 |
35 | pub fn get_scheme(scheme_name: &str) -> Result {
36 | match scheme_name {
37 | "01" => Ok(SchemeDispatcher::Scheme01(scheme_01::Scheme01)),
38 | "02" => Ok(SchemeDispatcher::Scheme02(scheme_02::Scheme02)),
39 | _ => Err(Error::SchemeNotFound(scheme_name.to_string())),
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | # Cargo config file.
2 | # See: https://doc.rust-lang.org/cargo/reference/config.html
3 |
4 | # Environments variables set for all `cargo ...` commands.
5 | [env]
6 |
7 | # Scope down tracing, to filter out external lib tracing.
8 | RUST_LOG="web_server=debug,lib_core=debug,lib_web=debug,lib_auth=debug,lib_utils=debug"
9 |
10 | # -- Service Environment Variables
11 | # IMPORTANT:
12 | # For cargo commands only.
13 | # For deployed env, should be managed by container
14 | # (e.g., Kubernetes).
15 |
16 | ## -- Secrets
17 | # Keys and passwords below are for localhost dev ONLY.
18 | # e.g., "welcome" type of passwords.
19 | # i.e., Encryption not needed.
20 |
21 | SERVICE_DB_URL="postgres://app_user:dev_only_pwd@localhost/app_db"
22 |
23 | SERVICE_PWD_KEY="CKUGFOD9_2Qf6Pn3ZFRYgPYb8ht4vKqEG9PGMXTB7497bT0367DjoaD6ydFnEVaIRda0kKeBZVCT5Hb62m2sCA"
24 |
25 | SERVICE_TOKEN_KEY="9FoHBmkyxbgu_xFoQK7e0jz3RMNVJWgfvbVn712FBNH9LLaAWS3CS6Zpcg6RveiObvCUb6a2z-uAiLjhLh2igw"
26 | SERVICE_TOKEN_DURATION_SEC="1800" # 30 minutes
27 |
28 | ## -- ConfigMap
29 |
30 | # This will be relative to Cargo.toml
31 | # In deployed images, probably use absolute path.
32 | SERVICE_WEB_FOLDER="web-folder/"
--------------------------------------------------------------------------------
/crates/libs/lib-core/src/ctx/mod.rs:
--------------------------------------------------------------------------------
1 | // region: --- Modules
2 |
3 | mod error;
4 |
5 | pub use self::error::{Error, Result};
6 |
7 | // endregion: --- Modules
8 |
9 | #[cfg_attr(feature = "with-rpc", derive(rpc_router::RpcResource))]
10 | #[derive(Clone, Debug)]
11 | pub struct Ctx {
12 | user_id: i64,
13 |
14 | /// Note: For the future ACS (Access Control System)
15 | conv_id: Option,
16 | }
17 |
18 | // Constructors.
19 | impl Ctx {
20 | pub fn root_ctx() -> Self {
21 | Ctx {
22 | user_id: 0,
23 | conv_id: None,
24 | }
25 | }
26 |
27 | pub fn new(user_id: i64) -> Result {
28 | if user_id == 0 {
29 | Err(Error::CtxCannotNewRootCtx)
30 | } else {
31 | Ok(Self {
32 | user_id,
33 | conv_id: None,
34 | })
35 | }
36 | }
37 |
38 | /// Note: For the future ACS (Access Control System)
39 | pub fn add_conv_id(&self, conv_id: i64) -> Ctx {
40 | let mut ctx = self.clone();
41 | ctx.conv_id = Some(conv_id);
42 | ctx
43 | }
44 | }
45 |
46 | // Property Accessors.
47 | impl Ctx {
48 | pub fn user_id(&self) -> i64 {
49 | self.user_id
50 | }
51 |
52 | //. /// Note: For the future ACS (Access Control System)
53 | pub fn conv_id(&self) -> Option {
54 | self.conv_id
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/crates/libs/lib-utils/src/time.rs:
--------------------------------------------------------------------------------
1 | use time::{Duration, OffsetDateTime};
2 |
3 | pub use time::format_description::well_known::Rfc3339;
4 |
5 | pub fn now_utc() -> OffsetDateTime {
6 | OffsetDateTime::now_utc()
7 | }
8 |
9 | pub fn format_time(time: OffsetDateTime) -> String {
10 | time.format(&Rfc3339).unwrap() // TODO: need to check if safe.
11 | }
12 |
13 | pub fn now_utc_plus_sec_str(sec: f64) -> String {
14 | let new_time = now_utc() + Duration::seconds_f64(sec);
15 | format_time(new_time)
16 | }
17 |
18 | pub fn parse_utc(moment: &str) -> Result {
19 | OffsetDateTime::parse(moment, &Rfc3339)
20 | .map_err(|_| Error::FailToDateParse(moment.to_string()))
21 | }
22 |
23 | // region: --- Error
24 |
25 | pub type Result = core::result::Result;
26 |
27 | #[derive(Debug)]
28 | pub enum Error {
29 | FailToDateParse(String),
30 | }
31 |
32 | // region: --- Error Boilerplate
33 | impl core::fmt::Display for Error {
34 | fn fmt(
35 | &self,
36 | fmt: &mut core::fmt::Formatter,
37 | ) -> core::result::Result<(), core::fmt::Error> {
38 | write!(fmt, "{self:?}")
39 | }
40 | }
41 |
42 | impl std::error::Error for Error {}
43 | // endregion: --- Error Boilerplate
44 |
45 | // endregion: --- Error
46 |
--------------------------------------------------------------------------------
/crates/libs/lib-core/src/model/conv_user.rs:
--------------------------------------------------------------------------------
1 | use crate::model::base::DbBmc;
2 | use lib_utils::time::Rfc3339;
3 | use modql::field::Fields;
4 | use serde::{Deserialize, Serialize};
5 | use serde_with::serde_as;
6 | use sqlx::FromRow;
7 | use time::OffsetDateTime;
8 |
9 | // region: --- Types
10 |
11 | #[serde_as]
12 | #[derive(Debug, Clone, Fields, FromRow, Serialize)]
13 | pub struct ConvUser {
14 | pub id: i64,
15 |
16 | // -- FK
17 | pub conv_id: i64,
18 | pub user_id: i64,
19 |
20 | // -- Timestamps
21 | // creator user_id and time
22 | pub cid: i64,
23 | #[serde_as(as = "Rfc3339")]
24 | pub ctime: OffsetDateTime,
25 | // last modifier user_id and time
26 | pub mid: i64,
27 | #[serde_as(as = "Rfc3339")]
28 | pub mtime: OffsetDateTime,
29 | }
30 |
31 | #[derive(Fields, Deserialize)]
32 | pub struct ConvUserForCreate {
33 | pub conv_id: i64,
34 | pub user_id: i64,
35 | }
36 |
37 | // endregion: --- Types
38 |
39 | // region: --- ConvUser
40 |
41 | pub struct ConvUserBmc;
42 |
43 | impl DbBmc for ConvUserBmc {
44 | const TABLE: &'static str = "conv_user";
45 | }
46 |
47 | // Note: This is not implemented yet. It will likely be similar to `ConvMsg`, meaning it will be
48 | // managed by the `ConvBmc` container entity.
49 |
50 | // endregion: --- ConvUser
51 |
--------------------------------------------------------------------------------
/crates/libs/lib-web/src/middleware/mw_req_stamp.rs:
--------------------------------------------------------------------------------
1 | use crate::error::{Error, Result};
2 | use axum::body::Body;
3 | use axum::extract::FromRequestParts;
4 | use axum::http::request::Parts;
5 | use axum::http::Request;
6 | use axum::middleware::Next;
7 | use axum::response::Response;
8 | use lib_utils::time::now_utc;
9 | use time::OffsetDateTime;
10 | use tracing::debug;
11 | use uuid::Uuid;
12 |
13 | #[derive(Debug, Clone)]
14 | pub struct ReqStamp {
15 | pub uuid: Uuid,
16 | pub time_in: OffsetDateTime,
17 | }
18 |
19 | pub async fn mw_req_stamp_resolver(
20 | mut req: Request,
21 | next: Next,
22 | ) -> Result {
23 | debug!("{:<12} - mw_req_stamp_resolver", "MIDDLEWARE");
24 |
25 | let time_in = now_utc();
26 | let uuid = Uuid::new_v4();
27 |
28 | req.extensions_mut().insert(ReqStamp { uuid, time_in });
29 |
30 | Ok(next.run(req).await)
31 | }
32 |
33 | // region: --- ReqStamp Extractor
34 | impl FromRequestParts for ReqStamp {
35 | type Rejection = Error;
36 |
37 | async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result {
38 | debug!("{:<12} - ReqStamp", "EXTRACTOR");
39 |
40 | parts
41 | .extensions
42 | .get::()
43 | .cloned()
44 | .ok_or(Error::ReqStampNotInReqExt)
45 | }
46 | }
47 | // endregion: --- ReqStamp Extractor
48 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # -- Base
2 | .*
3 | !.gitignore
4 |
5 | _*
6 | # '_' in src dir, ok.
7 | !**/src/**/_*
8 |
9 | *.lock
10 | *.lockb
11 | *.log
12 |
13 | # -- Rust
14 | target/
15 | # !Cargo.lock # commented by default
16 | !.cargo/
17 |
18 | # -- Devai
19 | # By default ignore all devai
20 | *.devai
21 |
22 | # Uncomment beow to allow commiting .devai/custom .devai
23 | # Only allow .devai/custom and .devai/Config.toml
24 | # Note: Here the starting `/` will just include the top .devai.
25 | # Remove the starting `/` to include all .devai/custom even if their in a sub dir
26 | # !/.devai/
27 | # .devai/*
28 | # !.devai/custom/
29 | # !.devai/custom/**
30 | # !.devai/Config.toml
31 |
32 | # -- Safety net
33 |
34 | dist/
35 | out/
36 |
37 | # Doc Files
38 | *.pdf
39 | *.docx
40 | *.xlsx
41 | *.pptx
42 | *.doc
43 | *.xls
44 | *.ppt
45 | *.page
46 |
47 | # Data Files
48 | *.db3
49 | *.parquet
50 | *.map
51 | *.zip
52 | *.gz
53 | *.tar
54 | *.tgz
55 | *.vsix
56 |
57 | # Videos
58 | *.mov
59 | *.mp4
60 | *.webm
61 | *.ogg
62 | *.avi
63 |
64 | # Images
65 | *.icns
66 | *.ico
67 | *.jpeg
68 | *.jpg
69 | *.png
70 | *.bmp
71 |
72 | # -- Nodejs
73 | node_modules/
74 | !.mocharc.yaml
75 | report.*.json
76 |
77 | # -- Python
78 | __pycache__/
79 |
80 |
81 | # -- others
82 | # Allows .env (make sure only dev info)
83 | # !.env # Commented by default
84 |
85 | # Allow vscode
86 | # !.vscode # Commented by default
87 |
--------------------------------------------------------------------------------
/crates/services/web-server/src/web/rpcs/conv_rpc.rs:
--------------------------------------------------------------------------------
1 | use lib_core::model::conv::{
2 | Conv, ConvBmc, ConvFilter, ConvForCreate, ConvForUpdate,
3 | };
4 | use lib_core::model::conv_msg::{ConvMsg, ConvMsgForCreate};
5 | use lib_rpc_core::prelude::*;
6 |
7 | pub fn rpc_router_builder() -> RouterBuilder {
8 | router_builder!(
9 | // Same as RpcRouter::new().add...
10 | create_conv,
11 | get_conv,
12 | list_convs,
13 | update_conv,
14 | delete_conv,
15 | add_conv_msg,
16 | )
17 | }
18 |
19 | generate_common_rpc_fns!(
20 | Bmc: ConvBmc,
21 | Entity: Conv,
22 | ForCreate: ConvForCreate,
23 | ForUpdate: ConvForUpdate,
24 | Filter: ConvFilter,
25 | Suffix: conv
26 | );
27 |
28 | /// Returns conv_msg
29 | pub async fn add_conv_msg(
30 | ctx: Ctx,
31 | mm: ModelManager,
32 | params: ParamsForCreate,
33 | ) -> Result> {
34 | let ParamsForCreate { data: msg_c } = params;
35 |
36 | let msg_id = ConvBmc::add_msg(&ctx, &mm, msg_c).await?;
37 | let msg = ConvBmc::get_msg(&ctx, &mm, msg_id).await?;
38 |
39 | Ok(msg.into())
40 | }
41 |
42 | /// Returns conv_msg
43 | #[allow(unused)]
44 | pub async fn get_conv_msg(
45 | ctx: Ctx,
46 | mm: ModelManager,
47 | params: ParamsIded,
48 | ) -> Result> {
49 | let ParamsIded { id: msg_id } = params;
50 |
51 | let msg = ConvBmc::get_msg(&ctx, &mm, msg_id).await?;
52 |
53 | Ok(msg.into())
54 | }
55 |
--------------------------------------------------------------------------------
/crates/libs/lib-core/src/model/store/mod.rs:
--------------------------------------------------------------------------------
1 | // region: --- Modules
2 |
3 | pub(in crate::model) mod dbx;
4 |
5 | use crate::core_config;
6 | use sqlx::postgres::PgPoolOptions;
7 | use sqlx::{Pool, Postgres};
8 |
9 | // endregion: --- Modules
10 |
11 | pub type Db = Pool;
12 |
13 | pub async fn new_db_pool() -> sqlx::Result {
14 | // * See NOTE 1) below
15 | let max_connections = if cfg!(test) { 1 } else { 5 };
16 |
17 | PgPoolOptions::new()
18 | .max_connections(max_connections)
19 | .connect(&core_config().DB_URL)
20 | .await
21 | }
22 |
23 | // NOTE 1) This is not an ideal situation; however, with sqlx 0.7.1, when executing `cargo test`, some tests that use sqlx fail at a
24 | // rather low level (in the tokio scheduler). It appears to be a low-level thread/async issue, as removing/adding
25 | // tests causes different tests to fail. The cause remains uncertain, but setting max_connections to 1 resolves the issue.
26 | // The good news is that max_connections still function normally for a regular run.
27 | // This issue is likely due to the unique requirements unit tests impose on their execution, and therefore,
28 | // while not ideal, it should serve as an acceptable temporary solution.
29 | // It's a very challenging issue to investigate and narrow down. The alternative would have been to stick with sqlx 0.6.x, which
30 | // is potentially less ideal and might lead to confusion as to why we are maintaining the older version in this blueprint.
31 |
--------------------------------------------------------------------------------
/crates/libs/lib-core/src/model/base/utils.rs:
--------------------------------------------------------------------------------
1 | use crate::model::base::{CommonIden, DbBmc, TimestampIden};
2 | use lib_utils::time::now_utc;
3 | use modql::field::{SeaField, SeaFields};
4 | use sea_query::IntoIden;
5 |
6 | /// This method must be called when a model controller intends to create its entity.
7 | pub fn prep_fields_for_create(fields: &mut SeaFields, user_id: i64)
8 | where
9 | MC: DbBmc,
10 | {
11 | if MC::has_owner_id() {
12 | fields.push(SeaField::new(CommonIden::OwnerId.into_iden(), user_id));
13 | }
14 | if MC::has_timestamps() {
15 | add_timestamps_for_create(fields, user_id);
16 | }
17 | }
18 |
19 | /// This method must be calledwhen a Model Controller plans to update its entity.
20 | pub fn prep_fields_for_update(fields: &mut SeaFields, user_id: i64)
21 | where
22 | MC: DbBmc,
23 | {
24 | if MC::has_timestamps() {
25 | add_timestamps_for_update(fields, user_id);
26 | }
27 | }
28 |
29 | /// Update the timestamps info for create
30 | /// (e.g., cid, ctime, and mid, mtime will be updated with the same values)
31 | fn add_timestamps_for_create(fields: &mut SeaFields, user_id: i64) {
32 | let now = now_utc();
33 | fields.push(SeaField::new(TimestampIden::Cid, user_id));
34 | fields.push(SeaField::new(TimestampIden::Ctime, now));
35 |
36 | fields.push(SeaField::new(TimestampIden::Mid, user_id));
37 | fields.push(SeaField::new(TimestampIden::Mtime, now));
38 | }
39 |
40 | /// Update the timestamps info only for update.
41 | /// (.e.g., only mid, mtime will be udpated)
42 | fn add_timestamps_for_update(fields: &mut SeaFields, user_id: i64) {
43 | let now = now_utc();
44 | fields.push(SeaField::new(TimestampIden::Mid, user_id));
45 | fields.push(SeaField::new(TimestampIden::Mtime, now));
46 | }
47 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace.lints.rust]
2 | unsafe_code = "forbid"
3 | # unused = { level = "allow", priority = -1 } # For exploratory dev.
4 |
5 | [workspace]
6 | resolver = "2"
7 | members = [
8 | # -- Application Libraries
9 | "crates/libs/lib-utils", # e.g., base64, time.
10 | "crates/libs/lib-rpc-core", # e.g., core rpc utils (using rpc-router crate)
11 | "crates/libs/lib-auth", # e.g., for pwd, token.
12 | "crates/libs/lib-core", # e.g., model, ctx, config.
13 | "crates/libs/lib-web", # e.g., logging, common middleware etc
14 |
15 | # -- Application Services
16 | "crates/services/web-server",
17 |
18 | # -- Tools
19 | "crates/tools/gen-key",
20 | ]
21 |
22 | # NOTE: Only the crates that are utilized in two or more sub-crates and benefit from global management
23 | # are handled in workspace.dependencies. Other strategies may also be valid.
24 | [workspace.dependencies]
25 | # -- Serde
26 | serde_with = {version = "3", features = ["time_0_3"] }
27 | # -- Data
28 | # Note: we lock modql version during rcs
29 | modql = { version = "0.4.1", features = ["with-sea-query"]}
30 | sqlx = { version = "0.8", features = [ "macros", "runtime-tokio", "postgres", "uuid" ] }
31 | sea-query = "0.32"
32 | sea-query-binder = { version = "0.7", features = ["sqlx-postgres", "with-uuid", "with-time" ] }
33 | # -- JSON-RPC
34 | # Lock to specific version during 0.1.x
35 | rpc-router = { version = "=0.2.0-alpha.1" }
36 | # -- Web
37 | axum = {version = "0.8", features = ["macros"]}
38 | tower-http = { version = "0.6", features = ["fs"] }
39 | tower-cookies = "0.11"
40 | # -- Others
41 | time = {version = "0.3", features = ["formatting", "parsing", "serde"]}
42 | derive_more = {version = "1.0.0-beta", features = ["from", "display"] }
43 |
--------------------------------------------------------------------------------
/crates/libs/lib-core/src/model/base/mod.rs:
--------------------------------------------------------------------------------
1 | // region: --- Modules
2 |
3 | mod crud_fns;
4 | mod macro_utils;
5 | mod utils;
6 |
7 | // -- Flatten hierarchy for user code.
8 | pub use crud_fns::*;
9 | pub use utils::*;
10 |
11 | use modql::SIden;
12 | use sea_query::{Iden, IntoIden, TableRef};
13 |
14 | // endregion: --- Modules
15 |
16 | // region: --- Consts
17 |
18 | const LIST_LIMIT_DEFAULT: i64 = 1000;
19 | const LIST_LIMIT_MAX: i64 = 5000;
20 |
21 | // endregion: --- Consts
22 |
23 | // region: --- SeaQuery Idens
24 |
25 | #[derive(Iden)]
26 | pub enum CommonIden {
27 | Id,
28 | OwnerId,
29 | }
30 |
31 | #[derive(Iden)]
32 | pub enum TimestampIden {
33 | Cid,
34 | Ctime,
35 | Mid,
36 | Mtime,
37 | }
38 |
39 | // endregion: --- SeaQuery Idens
40 |
41 | /// The DbBmc trait must be implemented for the Bmc struct of an entity.
42 | /// It specifies meta information such as the table name,
43 | /// whether the table has timestamp columns (cid, ctime, mid, mtime), and more as the
44 | /// code evolves.
45 | ///
46 | /// Note: This trait should not be confused with the BaseCrudBmc trait, which provides
47 | /// common default CRUD BMC functions for a given Bmc/Entity.
48 | pub trait DbBmc {
49 | const TABLE: &'static str;
50 |
51 | fn table_ref() -> TableRef {
52 | TableRef::Table(SIden(Self::TABLE).into_iden())
53 | }
54 |
55 | /// Specifies that the table for this Bmc has timestamps (cid, ctime, mid, mtime) columns.
56 | /// This will allow the code to update those as needed.
57 | ///
58 | /// default: true
59 | fn has_timestamps() -> bool {
60 | true
61 | }
62 |
63 | /// Specifies if the entity table managed by this BMC
64 | /// has an `owner_id` column that needs to be set on create (by default ctx.user_id).
65 | ///
66 | /// default: false
67 | fn has_owner_id() -> bool {
68 | false
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/crates/libs/lib-rpc-core/src/rpc_params.rs:
--------------------------------------------------------------------------------
1 | //! Base constructs for the typed RPC Params that will be used in their respective
2 | //! rpc handler functions (e.g., `project_rpc::create_project` and `project_rpc::list_projects`).
3 | //!
4 | //! Most of these base constructs use generics for their respective data elements, allowing
5 | //! each rpc handler function to receive the exact desired type.
6 | //!
7 | //! `IntoParams` or `IntoDefaultRpcParams` are implemented to ensure these Params conform to the
8 | //! `RpcRouter` (i.e., `rpc::router`) model.
9 |
10 | use modql::filter::ListOptions;
11 | use rpc_router::{IntoDefaultRpcParams, IntoParams};
12 | use serde::de::DeserializeOwned;
13 | use serde::Deserialize;
14 | use serde_with::{serde_as, OneOrMany};
15 |
16 | /// Params structure for any RPC Create call.
17 | #[derive(Deserialize)]
18 | pub struct ParamsForCreate {
19 | pub data: D,
20 | }
21 |
22 | impl IntoParams for ParamsForCreate where D: DeserializeOwned + Send {}
23 |
24 | /// Params structure for any RPC Update call.
25 | #[derive(Deserialize)]
26 | pub struct ParamsForUpdate {
27 | pub id: i64,
28 | pub data: D,
29 | }
30 |
31 | impl IntoParams for ParamsForUpdate where D: DeserializeOwned + Send {}
32 |
33 | /// Params structure for any RPC Update call.
34 | #[derive(Deserialize)]
35 | pub struct ParamsIded {
36 | pub id: i64,
37 | }
38 | impl IntoParams for ParamsIded {}
39 |
40 | /// Params structure for any RPC List call.
41 | #[serde_as]
42 | #[derive(Deserialize, Default)]
43 | pub struct ParamsList
44 | where
45 | F: DeserializeOwned,
46 | {
47 | #[serde_as(deserialize_as = "Option>")]
48 | pub filters: Option>,
49 | pub list_options: Option,
50 | }
51 |
52 | impl IntoDefaultRpcParams for ParamsList where
53 | D: DeserializeOwned + Send + Default
54 | {
55 | }
56 |
--------------------------------------------------------------------------------
/crates/libs/lib-rpc-core/src/error.rs:
--------------------------------------------------------------------------------
1 | //! This module encompasses errors for all `lib_rpc` modules and rpc handlers.
2 | //! Variants from our application's library errors can be added as required by the handlers.
3 | //!
4 | //! # Note on the `rpc-router::Error` scheme
5 | //!
6 | //! - When used in an rpc handler with the `rpc-router` crate,
7 | //! this type will be encapsulated as a `rpc-router::Error::Handler(RpcHandlerError)` within a
8 | //! "TypeMap" and can subsequently be retrieved (see `web-server::web::Error` for reference).
9 | //!
10 | //! - For this application error to be utilized in the `rpc-router`, it must
11 | //! implement the `IntoRpcHandlerError` trait. This trait has a suitable default implementation,
12 | //! so simply adding `impl rpc_router::IntoRpcHandlerError for Error {}` would suffice.
13 | //!
14 | //! - Alternatively, the `#[derive(RpcHandlerError)]` annotation can be used as demonstrated here, which will
15 | //! automatically provide the `impl rpc_router::IntoRpcHandlerError for Error {}` for this type.
16 |
17 | use derive_more::From;
18 | use rpc_router::RpcHandlerError;
19 | use serde::Serialize;
20 | use serde_with::{serde_as, DisplayFromStr};
21 |
22 | pub type Result = core::result::Result;
23 |
24 | #[serde_as]
25 | #[derive(Debug, From, Serialize, RpcHandlerError)]
26 | pub enum Error {
27 | // -- App Libs
28 | #[from]
29 | Model(lib_core::model::Error),
30 |
31 | // -- External Modules
32 | #[from]
33 | SerdeJson(#[serde_as(as = "DisplayFromStr")] serde_json::Error),
34 | }
35 |
36 | // region: --- Error Boilerplate
37 | impl core::fmt::Display for Error {
38 | fn fmt(
39 | &self,
40 | fmt: &mut core::fmt::Formatter,
41 | ) -> core::result::Result<(), core::fmt::Error> {
42 | write!(fmt, "{self:?}")
43 | }
44 | }
45 |
46 | impl std::error::Error for Error {}
47 | // endregion: --- Error Boilerplate
48 |
--------------------------------------------------------------------------------
/crates/libs/lib-core/src/model/mod.rs:
--------------------------------------------------------------------------------
1 | //! Model Layer
2 | //!
3 | //! Design:
4 | //!
5 | //! - The Model layer normalizes the application's data type
6 | //! structures and access.
7 | //! - All application code data access must go through the Model layer.
8 | //! - The `ModelManager` holds the internal states/resources
9 | //! needed by ModelControllers to access data.
10 | //! (e.g., db_pool, S3 client, redis client).
11 | //! - Model Controllers (e.g., `ConvBmc`, `AgentBmc`) implement
12 | //! CRUD and other data access methods on a given "entity"
13 | //! (e.g., `Conv`, `Agent`).
14 | //! (`Bmc` is short for Backend Model Controller).
15 | //! - In frameworks like Axum, Tauri, `ModelManager` are typically used as App State.
16 | //! - ModelManager are designed to be passed as an argument
17 | //! to all Model Controllers functions.
18 | //!
19 |
20 | // region: --- Modules
21 |
22 | mod acs;
23 | mod base;
24 | mod error;
25 | mod store;
26 |
27 | pub mod agent;
28 | pub mod conv;
29 | pub mod conv_msg;
30 | pub mod conv_user;
31 | pub mod modql_utils;
32 | pub mod user;
33 |
34 | pub use self::error::{Error, Result};
35 |
36 | use crate::model::store::dbx::Dbx;
37 | use crate::model::store::new_db_pool;
38 |
39 | // endregion: --- Modules
40 |
41 | // region: --- ModelManager
42 |
43 | #[cfg_attr(feature = "with-rpc", derive(rpc_router::RpcResource))]
44 | #[derive(Clone)]
45 | pub struct ModelManager {
46 | dbx: Dbx,
47 | }
48 |
49 | impl ModelManager {
50 | /// Constructor
51 | pub async fn new() -> Result {
52 | let db_pool = new_db_pool()
53 | .await
54 | .map_err(|ex| Error::CantCreateModelManagerProvider(ex.to_string()))?;
55 | let dbx = Dbx::new(db_pool, false)?;
56 | Ok(ModelManager { dbx })
57 | }
58 |
59 | pub fn new_with_txn(&self) -> Result {
60 | let dbx = Dbx::new(self.dbx.db().clone(), true)?;
61 | Ok(ModelManager { dbx })
62 | }
63 |
64 | pub fn dbx(&self) -> &Dbx {
65 | &self.dbx
66 | }
67 | }
68 |
69 | // endregion: --- ModelManager
70 |
--------------------------------------------------------------------------------
/crates/services/web-server/src/main.rs:
--------------------------------------------------------------------------------
1 | // region: --- Modules
2 |
3 | mod config;
4 | mod error;
5 | mod web;
6 |
7 | pub use self::error::{Error, Result};
8 | use config::web_config;
9 |
10 | use lib_web::middleware::mw_auth::{mw_ctx_require, mw_ctx_resolver};
11 | use lib_web::middleware::mw_req_stamp::mw_req_stamp_resolver;
12 | use lib_web::middleware::mw_res_map::mw_reponse_map;
13 | use lib_web::routes::routes_static;
14 |
15 | use crate::web::routes_login;
16 |
17 | use axum::{middleware, Router};
18 | use lib_core::_dev_utils;
19 | use lib_core::model::ModelManager;
20 | use tokio::net::TcpListener;
21 | use tower_cookies::CookieManagerLayer;
22 | use tracing::info;
23 | use tracing_subscriber::EnvFilter;
24 |
25 | // endregion: --- Modules
26 |
27 | #[tokio::main]
28 | async fn main() -> Result<()> {
29 | tracing_subscriber::fmt()
30 | .without_time() // For early local development.
31 | .with_target(false)
32 | .with_env_filter(EnvFilter::from_default_env())
33 | .init();
34 |
35 | // -- FOR DEV ONLY
36 | _dev_utils::init_dev().await;
37 |
38 | let mm = ModelManager::new().await?;
39 |
40 | // -- Define Routes
41 | let routes_rpc = web::routes_rpc::routes(mm.clone())
42 | .route_layer(middleware::from_fn(mw_ctx_require));
43 |
44 | let routes_all = Router::new()
45 | .merge(routes_login::routes(mm.clone()))
46 | .nest("/api", routes_rpc)
47 | .layer(middleware::map_response(mw_reponse_map))
48 | .layer(middleware::from_fn_with_state(mm.clone(), mw_ctx_resolver))
49 | .layer(CookieManagerLayer::new())
50 | .layer(middleware::from_fn(mw_req_stamp_resolver))
51 | .fallback_service(routes_static::serve_dir(&web_config().WEB_FOLDER));
52 |
53 | // region: --- Start Server
54 | // Note: For this block, ok to unwrap.
55 | let listener = TcpListener::bind("127.0.0.1:8080").await.unwrap();
56 | info!("{:<12} - {:?}\n", "LISTENING", listener.local_addr());
57 | axum::serve(listener, routes_all.into_make_service())
58 | .await
59 | .unwrap();
60 | // endregion: --- Start Server
61 |
62 | Ok(())
63 | }
64 |
--------------------------------------------------------------------------------
/crates/libs/lib-auth/src/pwd/scheme/scheme_01.rs:
--------------------------------------------------------------------------------
1 | use super::{Error, Result};
2 | use crate::auth_config;
3 | use crate::pwd::scheme::Scheme;
4 | use crate::pwd::ContentToHash;
5 | use hmac::{Hmac, Mac};
6 | use lib_utils::b64::b64u_encode;
7 | use sha2::Sha512;
8 |
9 | pub struct Scheme01;
10 |
11 | impl Scheme for Scheme01 {
12 | fn hash(&self, to_hash: &ContentToHash) -> Result {
13 | let key = &auth_config().PWD_KEY;
14 | hash(key, to_hash)
15 | }
16 |
17 | fn validate(&self, to_hash: &ContentToHash, raw_pwd_ref: &str) -> Result<()> {
18 | let raw_pwd_new = self.hash(to_hash)?;
19 | if raw_pwd_new == raw_pwd_ref {
20 | Ok(())
21 | } else {
22 | Err(Error::PwdValidate)
23 | }
24 | }
25 | }
26 |
27 | fn hash(key: &[u8], to_hash: &ContentToHash) -> Result {
28 | let ContentToHash { content, salt } = to_hash;
29 |
30 | // -- Create a HMAC-SHA-512 from key.
31 | let mut hmac_sha512 =
32 | Hmac::::new_from_slice(key).map_err(|_| Error::Key)?;
33 |
34 | // -- Add content.
35 | hmac_sha512.update(content.as_bytes());
36 | hmac_sha512.update(salt.as_bytes());
37 |
38 | // -- Finalize and b64u encode.
39 | let hmac_result = hmac_sha512.finalize();
40 | let result_bytes = hmac_result.into_bytes();
41 |
42 | let result = b64u_encode(result_bytes);
43 |
44 | Ok(result)
45 | }
46 |
47 | // region: --- Tests
48 | #[cfg(test)]
49 | mod tests {
50 | pub type Result = core::result::Result;
51 | pub type Error = Box; // For early tests.
52 |
53 | use super::*;
54 | use crate::auth_config;
55 | use uuid::Uuid;
56 |
57 | #[test]
58 | fn test_scheme_01_hash_into_b64u_ok() -> Result<()> {
59 | // -- Setup & Fixtures
60 | let fx_salt = Uuid::parse_str("f05e8961-d6ad-4086-9e78-a6de065e5453")?;
61 | let fx_key = &auth_config().PWD_KEY; // 512 bits = 64 bytes
62 | let fx_to_hash = ContentToHash {
63 | content: "hello world".to_string(),
64 | salt: fx_salt,
65 | };
66 | let fx_res = "qO9A90161DoewhNXFwVcnAaljRIVnajvd5zsVDrySCwxpoLwVCACzaz-8Ev2ZpI8RackUTLBVqFI6H5oMe-OIg";
67 |
68 | // -- Exec
69 | let res = hash(fx_key, &fx_to_hash)?;
70 |
71 | // -- Check
72 | assert_eq!(res, fx_res);
73 |
74 | Ok(())
75 | }
76 | }
77 | // endregion: --- Tests
78 |
--------------------------------------------------------------------------------
/crates/libs/lib-web/src/middleware/mw_res_map.rs:
--------------------------------------------------------------------------------
1 | use crate::error::{Error, Result};
2 | use crate::handlers::handlers_rpc::RpcInfo;
3 | use crate::log::log_request;
4 | use crate::middleware::mw_auth::CtxW;
5 | use crate::middleware::mw_req_stamp::ReqStamp;
6 |
7 | use axum::http::{Method, Uri};
8 | use axum::response::{IntoResponse, Response};
9 | use axum::Json;
10 | use serde_json::{json, to_value};
11 | use std::sync::Arc;
12 | use tracing::debug;
13 | use uuid::Uuid;
14 |
15 | pub async fn mw_reponse_map(
16 | ctx: Result, // Axum 0.8 does not seem to support Option anymore
17 | uri: Uri,
18 | req_method: Method,
19 | req_stamp: ReqStamp,
20 | res: Response,
21 | ) -> Response {
22 | let ctx = ctx.map(|ctx| ctx.0).ok();
23 |
24 | debug!("{:<12} - mw_reponse_map", "RES_MAPPER");
25 | let uuid = Uuid::new_v4();
26 |
27 | let rpc_info = res.extensions().get::>().map(Arc::as_ref);
28 |
29 | // -- Get the eventual response error.
30 | let web_error = res.extensions().get::>().map(Arc::as_ref);
31 | let client_status_error = web_error.map(|se| se.client_status_and_error());
32 |
33 | // -- If client error, build the new reponse.
34 | let error_response =
35 | client_status_error
36 | .as_ref()
37 | .map(|(status_code, client_error)| {
38 | let client_error = to_value(client_error).ok();
39 | let message = client_error.as_ref().and_then(|v| v.get("message"));
40 | let detail = client_error.as_ref().and_then(|v| v.get("detail"));
41 |
42 | let client_error_body = json!({
43 | "id": rpc_info.as_ref().map(|rpc| rpc.id.clone()),
44 | "error": {
45 | "message": message, // Variant name
46 | "data": {
47 | "req_uuid": uuid.to_string(),
48 | "detail": detail
49 | },
50 | }
51 | });
52 |
53 | debug!("CLIENT ERROR BODY:\n{client_error_body}");
54 |
55 | // Build the new response from the client_error_body
56 | (*status_code, Json(client_error_body)).into_response()
57 | });
58 |
59 | // -- Build and log the server log line.
60 | let client_error = client_status_error.unzip().1;
61 |
62 | // TODO: Need to hander if log_request fail (but should not fail request)
63 | let _ = log_request(
64 | req_method,
65 | uri,
66 | req_stamp,
67 | rpc_info,
68 | ctx,
69 | web_error,
70 | client_error,
71 | )
72 | .await;
73 |
74 | debug!("\n");
75 |
76 | error_response.unwrap_or(res)
77 | }
78 |
--------------------------------------------------------------------------------
/crates/libs/lib-auth/src/pwd/scheme/scheme_02.rs:
--------------------------------------------------------------------------------
1 | use super::{Error, Result};
2 | use crate::config::auth_config;
3 | use crate::pwd::scheme::Scheme;
4 | use argon2::password_hash::SaltString;
5 | use argon2::{
6 | Algorithm, Argon2, Params, PasswordHash, PasswordHasher as _,
7 | PasswordVerifier as _, Version,
8 | };
9 | use std::sync::OnceLock;
10 |
11 | pub struct Scheme02;
12 |
13 | impl Scheme for Scheme02 {
14 | fn hash(&self, to_hash: &crate::pwd::ContentToHash) -> Result {
15 | let argon2 = get_argon2();
16 |
17 | let salt_b64 = SaltString::encode_b64(to_hash.salt.as_bytes())
18 | .map_err(|_| Error::Salt)?;
19 |
20 | let pwd = argon2
21 | .hash_password(to_hash.content.as_bytes(), &salt_b64)
22 | .map_err(|_| Error::Hash)?
23 | .to_string();
24 |
25 | Ok(pwd)
26 | }
27 |
28 | fn validate(
29 | &self,
30 | to_hash: &crate::pwd::ContentToHash,
31 | pwd_ref: &str,
32 | ) -> Result<()> {
33 | let argon2 = get_argon2();
34 |
35 | let parsed_hash_ref = PasswordHash::new(pwd_ref).map_err(|_| Error::Hash)?;
36 |
37 | argon2
38 | .verify_password(to_hash.content.as_bytes(), &parsed_hash_ref)
39 | .map_err(|_| Error::PwdValidate)
40 | }
41 | }
42 |
43 | fn get_argon2() -> &'static Argon2<'static> {
44 | static INSTANCE: OnceLock> = OnceLock::new();
45 |
46 | INSTANCE.get_or_init(|| {
47 | let key = &auth_config().PWD_KEY;
48 | Argon2::new_with_secret(
49 | key,
50 | Algorithm::Argon2id, // Same as Argon2::default()
51 | Version::V0x13, // Same as Argon2::default()
52 | Params::default(),
53 | )
54 | .unwrap() // TODO - needs to fail early
55 | })
56 | }
57 |
58 | // region: --- Tests
59 | #[cfg(test)]
60 | mod tests {
61 | pub type Result = core::result::Result;
62 | pub type Error = Box; // For tests.
63 |
64 | use super::*;
65 | use crate::pwd::ContentToHash;
66 | use uuid::Uuid;
67 |
68 | #[test]
69 | fn test_scheme_02_hash_into_b64u_ok() -> Result<()> {
70 | // -- Setup & Fixtures
71 | let fx_to_hash = ContentToHash {
72 | content: "hello world".to_string(),
73 | salt: Uuid::parse_str("f05e8961-d6ad-4086-9e78-a6de065e5453")?,
74 | };
75 | let fx_res = "$argon2id$v=19$m=19456,t=2,p=1$8F6JYdatQIaeeKbeBl5UUw$TaRnmmbDdQ1aTzk2qQ2yQzPQoZfnKqhrfuTH/TRP5V4";
76 |
77 | // -- Exec
78 | let scheme = Scheme02;
79 | let res = scheme.hash(&fx_to_hash)?;
80 |
81 | // -- Check
82 | assert_eq!(res, fx_res);
83 |
84 | Ok(())
85 | }
86 | }
87 | // endregion: --- Tests
88 |
--------------------------------------------------------------------------------
/crates/services/web-server/examples/quick_dev.rs:
--------------------------------------------------------------------------------
1 | #![allow(unused)] // For example code.
2 |
3 | pub type Result = core::result::Result;
4 | pub type Error = Box; // For examples.
5 |
6 | use serde_json::{json, Value};
7 |
8 | #[tokio::main]
9 | async fn main() -> Result<()> {
10 | let hc = httpc_test::new_client("http://localhost:8080")?;
11 |
12 | // hc.do_get("/index.html").await?.print().await?;
13 |
14 | // -- Login
15 | let req_login = hc.do_post(
16 | "/api/login",
17 | json!({
18 | "username": "demo1",
19 | "pwd": "welcome"
20 | }),
21 | );
22 | req_login.await?.print().await?;
23 |
24 | // -- Create Agent
25 | let req_create_agent = hc.do_post(
26 | "/api/rpc",
27 | json!({
28 | "jsonrpc": "2.0",
29 | "id": 1,
30 | "method": "create_agent",
31 | "params": {
32 | "data": {
33 | "name": "agent AAA"
34 | }
35 | }
36 | }),
37 | );
38 | let result = req_create_agent.await?;
39 | result.print().await?;
40 | let agent_id = result.json_value::("/result/data/id")?;
41 |
42 | // -- Get Agent
43 | let req_get_agent = hc.do_post(
44 | "/api/rpc",
45 | json!({
46 | "jsonrpc": "2.0",
47 | "id": 1,
48 | "method": "get_agent",
49 | "params": {
50 | "id": agent_id
51 | }
52 | }),
53 | );
54 | let result = req_get_agent.await?;
55 | result.print().await?;
56 |
57 | // -- Create Conv
58 | let req_create_conv = hc.do_post(
59 | "/api/rpc",
60 | json!({
61 | "jsonrpc": "2.0",
62 | "id": 1,
63 | "method": "create_conv",
64 | "params": {
65 | "data": {
66 | "agent_id": agent_id,
67 | "title": "conv 01"
68 | }
69 | }
70 | }),
71 | );
72 | let result = req_create_conv.await?;
73 | result.print().await?;
74 | let conv_id = result.json_value::("/result/data/id")?;
75 |
76 | // -- Create ConvMsg
77 | let req_create_conv = hc.do_post(
78 | "/api/rpc",
79 | json!({
80 | "jsonrpc": "2.0",
81 | "id": 1,
82 | "method": "add_conv_msg",
83 | "params": {
84 | "data": {
85 | "conv_id": conv_id,
86 | "content": "This is the first comment"
87 | }
88 | }
89 | }),
90 | );
91 | let result = req_create_conv.await?;
92 | result.print().await?;
93 | let conv_msg_id = result.json_value::("/result/data/id")?;
94 |
95 | // -- Logoff
96 | let req_logoff = hc.do_post(
97 | "/api/logoff",
98 | json!({
99 | "logoff": true
100 | }),
101 | );
102 | req_logoff.await?.print().await?;
103 |
104 | Ok(())
105 | }
106 |
--------------------------------------------------------------------------------
/crates/libs/lib-core/src/model/base/macro_utils.rs:
--------------------------------------------------------------------------------
1 | /// Convenience macro rules to generate default CRUD functions for a Bmc/Entity.
2 | /// Note: If custom functionality is required, use the code below as foundational
3 | /// code for the custom implementations.
4 | #[macro_export]
5 | macro_rules! generate_common_bmc_fns {
6 | (
7 | Bmc: $struct_name:ident,
8 | Entity: $entity:ty,
9 | $(ForCreate: $for_create:ty,)?
10 | $(ForUpdate: $for_update:ty,)?
11 | $(Filter: $filter:ty,)?
12 | ) => {
13 | impl $struct_name {
14 | $(
15 | pub async fn create(
16 | ctx: &Ctx,
17 | mm: &ModelManager,
18 | entity_c: $for_create,
19 | ) -> Result {
20 | base::create::(ctx, mm, entity_c).await
21 | }
22 |
23 | pub async fn create_many(
24 | ctx: &Ctx,
25 | mm: &ModelManager,
26 | entity_c: Vec<$for_create>,
27 | ) -> Result> {
28 | base::create_many::(ctx, mm, entity_c).await
29 | }
30 | )?
31 |
32 | pub async fn get(
33 | ctx: &Ctx,
34 | mm: &ModelManager,
35 | id: i64,
36 | ) -> Result<$entity> {
37 | base::get::(ctx, mm, id).await
38 | }
39 |
40 | $(
41 | pub async fn first(
42 | ctx: &Ctx,
43 | mm: &ModelManager,
44 | filter: Option>,
45 | list_options: Option,
46 | ) -> Result