diff --git a/Cargo.lock b/Cargo.lock index 25821a0..53d06e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1874,6 +1874,26 @@ dependencies = [ [[package]] name = "imphnen-cms-service" version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "axum-test", + "chrono", + "imphnen-entities", + "imphnen-libs", + "imphnen-utils", + "lazy_static", + "rand 0.9.0", + "regex", + "serde", + "serde_json", + "surrealdb", + "tokio", + "tower-http", + "utoipa", + "utoipa-swagger-ui", + "validator", +] [[package]] name = "imphnen-dimentorin-service" @@ -1945,6 +1965,7 @@ dependencies = [ "imphnen-entities", "imphnen-iam-service", "imphnen-libs", + "imphnen-middleware-service", "imphnen-utils", "lazy_static", "rand 0.9.0", @@ -1998,6 +2019,33 @@ dependencies = [ "tokio", ] +[[package]] +name = "imphnen-middleware-service" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "axum-test", + "chrono", + "futures", + "imphnen-entities", + "imphnen-iam-service", + "imphnen-libs", + "imphnen-utils", + "lazy_static", + "rand 0.9.0", + "regex", + "serde", + "serde_json", + "surrealdb", + "tokio", + "tower", + "tower-http", + "utoipa", + "utoipa-swagger-ui", + "validator", +] + [[package]] name = "imphnen-utils" version = "0.1.0" diff --git a/imphnen-cms-service/src/lib.rs b/imphnen-cms-service/src/lib.rs new file mode 100644 index 0000000..1303f3d --- /dev/null +++ b/imphnen-cms-service/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/imphnen-cms-service/src/main.rs b/imphnen-cms-service/src/main.rs deleted file mode 100644 index a30eb95..0000000 --- a/imphnen-cms-service/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/imphnen-gateway-service/Cargo.toml b/imphnen-gateway-service/Cargo.toml index 7c54ffb..512f618 100644 --- a/imphnen-gateway-service/Cargo.toml +++ b/imphnen-gateway-service/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] imphnen-iam-service = { version = "0.1.0", path = "../imphnen-iam-service" } +imphnen-middleware-service = { version = "0.1.0", path = "../imphnen-middleware-service" } imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" } imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" } imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" } diff --git a/imphnen-gateway-service/src/lib.rs b/imphnen-gateway-service/src/lib.rs index 62820d5..f08ddbb 100644 --- a/imphnen-gateway-service/src/lib.rs +++ b/imphnen-gateway-service/src/lib.rs @@ -5,10 +5,8 @@ use imphnen_entities::{AppState, SurrealMemClient, SurrealWsClient}; use imphnen_iam_service::{iam_protected_routes, iam_public_routes}; pub mod docs; -pub mod middleware; - pub use docs::*; -pub use middleware::*; +use imphnen_middleware_service::{auth_middleware, cors_middleware}; use utoipa_swagger_ui::SwaggerUi; pub async fn gateway_service( diff --git a/imphnen-middleware-service/Cargo.toml b/imphnen-middleware-service/Cargo.toml new file mode 100644 index 0000000..4463224 --- /dev/null +++ b/imphnen-middleware-service/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "imphnen-middleware-service" +version = "0.1.0" +edition = "2024" + +[dependencies] +imphnen-iam-service = { version = "0.1.0", path = "../imphnen-iam-service" } +imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" } +imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" } +imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" } +axum.workspace = true +serde.workspace = true +serde_json.workspace = true +utoipa.workspace = true +lazy_static.workspace = true +regex.workspace = true +validator.workspace = true +axum-test.workspace = true +surrealdb.workspace = true +rand.workspace = true +tokio.workspace = true +chrono.workspace = true +anyhow.workspace = true +tower-http.workspace = true +utoipa-swagger-ui.workspace = true +futures = "0.3.31" +tower = "0.5.2" diff --git a/imphnen-gateway-service/src/middleware.rs b/imphnen-middleware-service/src/auth_middleware/mod.rs similarity index 51% rename from imphnen-gateway-service/src/middleware.rs rename to imphnen-middleware-service/src/auth_middleware/mod.rs index 6da33a4..6c3f625 100644 --- a/imphnen-gateway-service/src/middleware.rs +++ b/imphnen-middleware-service/src/auth_middleware/mod.rs @@ -1,15 +1,11 @@ use axum::{ - Extension, - extract::Request, - http::{HeaderValue, Method, StatusCode, header}, - middleware::Next, + Extension, extract::Request, http::StatusCode, middleware::Next, response::Response, }; use imphnen_iam_service::{UsersItemDtoRaw, UsersRepository}; -use imphnen_libs::{AppState, Env}; +use imphnen_libs::AppState; use imphnen_utils::{common_response, extract_email}; use std::convert::Infallible; -use tower_http::cors::CorsLayer; pub async fn auth_middleware( Extension(state): Extension, @@ -46,33 +42,3 @@ pub async fn auth_middleware( req.extensions_mut().insert(user.unwrap()); Ok(next.run(req).await) } - -pub fn cors_middleware() -> CorsLayer { - let env = Env::new(); - let cors_origins = match env.rust_env.as_str() { - "development" => vec!["http://localhost:3000"], - "production" => { - vec![ - "https://gacha.imphnen.dev", - "https://imphnen.dev", - "https://dimentorin.imphnen.dev", - ] - } - _ => vec![ - "http://localhost:3000", - "https://gacha.imphnen.dev", - "https://imphnen.dev", - "https://dimentorin.imphnen.dev", - ], - }; - let allowed_origins: Vec = cors_origins - .into_iter() - .filter_map(|origin| origin.parse::().ok()) - .collect(); - - CorsLayer::new() - .allow_origin(allowed_origins) - .allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE]) - .allow_headers([header::AUTHORIZATION, header::CONTENT_TYPE]) - .allow_credentials(true) -} diff --git a/imphnen-middleware-service/src/cors_middleware/mod.rs b/imphnen-middleware-service/src/cors_middleware/mod.rs new file mode 100644 index 0000000..e79553c --- /dev/null +++ b/imphnen-middleware-service/src/cors_middleware/mod.rs @@ -0,0 +1,33 @@ +use axum::http::{HeaderValue, Method, header}; +use imphnen_libs::Env; +use tower_http::cors::CorsLayer; + +pub fn cors_middleware() -> CorsLayer { + let env = Env::new(); + let cors_origins = match env.rust_env.as_str() { + "development" => vec!["http://localhost:3000"], + "production" => { + vec![ + "https://gacha.imphnen.dev", + "https://imphnen.dev", + "https://dimentorin.imphnen.dev", + ] + } + _ => vec![ + "http://localhost:3000", + "https://gacha.imphnen.dev", + "https://imphnen.dev", + "https://dimentorin.imphnen.dev", + ], + }; + let allowed_origins: Vec = cors_origins + .into_iter() + .filter_map(|origin| origin.parse::().ok()) + .collect(); + + CorsLayer::new() + .allow_origin(allowed_origins) + .allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE]) + .allow_headers([header::AUTHORIZATION, header::CONTENT_TYPE]) + .allow_credentials(true) +} diff --git a/imphnen-middleware-service/src/lib.rs b/imphnen-middleware-service/src/lib.rs new file mode 100644 index 0000000..d8b468a --- /dev/null +++ b/imphnen-middleware-service/src/lib.rs @@ -0,0 +1,7 @@ +pub mod auth_middleware; +pub mod cors_middleware; +pub mod permissions_middleware; + +pub use auth_middleware::*; +pub use cors_middleware::*; +pub use permissions_middleware::*; diff --git a/imphnen-middleware-service/src/payment_middleware/.gitkeep b/imphnen-middleware-service/src/payment_middleware/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/imphnen-middleware-service/src/permissions_middleware/mod.rs b/imphnen-middleware-service/src/permissions_middleware/mod.rs new file mode 100644 index 0000000..27d7641 --- /dev/null +++ b/imphnen-middleware-service/src/permissions_middleware/mod.rs @@ -0,0 +1,102 @@ +use axum::{ + body::Body, + http::{Request, Response, StatusCode}, +}; +use futures::future::BoxFuture; +use imphnen_iam_service::{AuthRepository, PermissionsEnum}; +use imphnen_libs::AppState; +use imphnen_utils::{common_response, extract_email}; +use std::task::{Context, Poll}; +use tower::{Layer, Service}; + +#[derive(Clone)] +pub struct PermissionsMiddlewareLayer { + app_state: AppState, + permissions: Vec, +} + +impl PermissionsMiddlewareLayer { + pub fn new(app_state: AppState, permissions: Vec) -> Self { + Self { + app_state, + permissions, + } + } +} + +impl Layer for PermissionsMiddlewareLayer { + type Service = PermissionsMiddleware; + fn layer(&self, inner: S) -> Self::Service { + PermissionsMiddleware { + inner, + app_state: self.app_state.clone(), + permissions: self.permissions.clone(), + } + } +} + +#[derive(Clone)] +pub struct PermissionsMiddleware { + inner: S, + app_state: AppState, + permissions: Vec, +} + +impl Service> for PermissionsMiddleware +where + S: Service, Response = Response> + Clone + Send + 'static, + S::Future: Send + 'static, +{ + type Response = S::Response; + type Error = S::Error; + type Future = BoxFuture<'static, Result>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, req: Request) -> Self::Future { + let mut inner = self.inner.clone(); + let app_state = self.app_state.clone(); + let permissions = self.permissions.clone(); + + Box::pin(async move { + let headers = req.headers(); + let email = match extract_email(headers) { + Some(email) => email, + None => { + return Ok(common_response( + StatusCode::UNAUTHORIZED, + "Invalid or missing authorization token", + )); + } + }; + + let auth_repo = AuthRepository::new(&app_state); + let user = match auth_repo.query_get_stored_user(email).await { + Ok(user) => user, + Err(_) => { + return Ok(common_response( + StatusCode::UNAUTHORIZED, + "User session expired or not found", + )); + } + }; + + let user_permissions: Vec = + user.role.permissions.into_iter().map(|p| p.name).collect(); + let allowed = permissions + .iter() + .all(|p| user_permissions.contains(&p.to_string())); + + if !allowed { + return Ok(common_response( + StatusCode::FORBIDDEN, + "You don't have the required permissions", + )); + } + + inner.call(req).await + }) + } +}