diff --git a/src/actors/mod.rs b/src/actors/mod.rs index 3e7f791..33c1250 100644 --- a/src/actors/mod.rs +++ b/src/actors/mod.rs @@ -1 +1 @@ -pub mod users_actor; \ No newline at end of file +pub mod users_actor; diff --git a/src/actors/users_actor.rs b/src/actors/users_actor.rs index eeb6c29..b50268f 100644 --- a/src/actors/users_actor.rs +++ b/src/actors/users_actor.rs @@ -1,7 +1,7 @@ use actix::{Actor, Context, Handler, Message, MessageResult}; use crate::data::entity_manager::EntityManager; -use crate::data::user::{User, UserID, verify_password}; +use crate::data::user::{verify_password, User, UserID}; #[derive(Debug)] pub enum LoginResult { @@ -29,7 +29,6 @@ pub struct ChangePasswordRequest { pub temporary: bool, } - pub struct UsersActor { manager: EntityManager, } @@ -69,7 +68,10 @@ impl Handler for UsersActor { type Result = MessageResult; fn handle(&mut self, msg: ChangePasswordRequest, _ctx: &mut Self::Context) -> Self::Result { - MessageResult(ChangePasswordResult( - self.manager.change_user_password(&msg.user_id, &msg.new_password, msg.temporary))) + MessageResult(ChangePasswordResult(self.manager.change_user_password( + &msg.user_id, + &msg.new_password, + msg.temporary, + ))) } -} \ No newline at end of file +} diff --git a/src/constants.rs b/src/constants.rs index 93c7d20..303bdbb 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -27,4 +27,4 @@ pub const AUTHENTICATED_ROUTES: &str = "/settings"; pub const ADMIN_ROUTES: &str = "/admin"; /// Auth route -pub const LOGIN_ROUTE: &str = "/login"; \ No newline at end of file +pub const LOGIN_ROUTE: &str = "/login"; diff --git a/src/controllers/assets_controller.rs b/src/controllers/assets_controller.rs index 5d764a7..2ca760d 100644 --- a/src/controllers/assets_controller.rs +++ b/src/controllers/assets_controller.rs @@ -1,7 +1,7 @@ use std::path::Path; -use actix_web::{HttpResponse, web}; -use include_dir::{Dir, include_dir}; +use actix_web::{web, HttpResponse}; +use include_dir::{include_dir, Dir}; /// Assets directory static ASSETS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/assets"); @@ -17,4 +17,4 @@ pub async fn assets_route(path: web::Path) -> HttpResponse { .body(file.contents()) } } -} \ No newline at end of file +} diff --git a/src/controllers/base_controller.rs b/src/controllers/base_controller.rs index fea46e5..be4d476 100644 --- a/src/controllers/base_controller.rs +++ b/src/controllers/base_controller.rs @@ -16,7 +16,11 @@ pub fn redirect_user_for_login(redirect_uri: P) -> HttpResponse { HttpResponse::Found() .append_header(( "Location", - format!("{}?redirect={}", LOGIN_ROUTE, urlencoding::encode(&redirect_uri.to_string())) + format!( + "{}?redirect={}", + LOGIN_ROUTE, + urlencoding::encode(&redirect_uri.to_string()) + ), )) .finish() } diff --git a/src/controllers/login_controller.rs b/src/controllers/login_controller.rs index 75f83b5..6b15ed0 100644 --- a/src/controllers/login_controller.rs +++ b/src/controllers/login_controller.rs @@ -1,10 +1,10 @@ use actix::Addr; use actix_identity::Identity; -use actix_web::{HttpResponse, Responder, web}; +use actix_web::{web, HttpResponse, Responder}; use askama::Template; -use crate::actors::users_actor::{ChangePasswordResult, LoginResult, UsersActor}; use crate::actors::users_actor; +use crate::actors::users_actor::{ChangePasswordResult, LoginResult, UsersActor}; use crate::constants::{APP_NAME, MIN_PASS_LEN}; use crate::controllers::base_controller::redirect_user; use crate::data::session_identity::{SessionIdentity, SessionStatus}; @@ -46,10 +46,12 @@ pub struct LoginRequestQuery { } /// Authenticate user -pub async fn login_route(users: web::Data>, - query: web::Query, - req: Option>, - id: Identity) -> impl Responder { +pub async fn login_route( + users: web::Data>, + query: web::Query, + req: Option>, + id: Identity, +) -> impl Responder { let mut danger = String::new(); let mut success = String::new(); let mut login = String::new(); @@ -59,7 +61,7 @@ pub async fn login_route(users: web::Data>, Some(s) => match s.starts_with('/') && !s.starts_with("//") { true => s, false => "/", - } + }, }; // Check if user session must be closed @@ -78,11 +80,14 @@ pub async fn login_route(users: web::Data>, if req.password.len() < MIN_PASS_LEN { danger = "Password is too short!".to_string(); } else { - let res: ChangePasswordResult = users.send(users_actor::ChangePasswordRequest { - user_id: SessionIdentity(&id).user_id(), - new_password: req.password.clone(), - temporary: false, - }).await.unwrap(); + let res: ChangePasswordResult = users + .send(users_actor::ChangePasswordRequest { + user_id: SessionIdentity(&id).user_id(), + new_password: req.password.clone(), + temporary: false, + }) + .await + .unwrap(); if !res.0 { danger = "Failed to change password!".to_string(); @@ -92,16 +97,16 @@ pub async fn login_route(users: web::Data>, } } } - // Try to authenticate user else if let Some(req) = &req { - // TODO : check request origin (check for valid Referer) - login = req.login.clone(); - let response: LoginResult = users.send(users_actor::LoginRequest { - login: login.clone(), - password: req.password.clone(), - }).await.unwrap(); + let response: LoginResult = users + .send(users_actor::LoginRequest { + login: login.clone(), + password: req.password.clone(), + }) + .await + .unwrap(); match response { LoginResult::Success(user) => { @@ -120,7 +125,6 @@ pub async fn login_route(users: web::Data>, } c => { - // TODO : add bruteforce detection log::warn!("Failed login for username {} : {:?}", login, c); danger = "Login failed.".to_string(); } @@ -129,9 +133,8 @@ pub async fn login_route(users: web::Data>, // Display password reset form if it is appropriate if SessionIdentity(&id).need_new_password() { - return HttpResponse::Ok() - .content_type("text/html") - .body(PasswordResetTemplate { + return HttpResponse::Ok().content_type("text/html").body( + PasswordResetTemplate { _parent: BaseLoginPage { page_title: "Password reset", danger, @@ -140,13 +143,14 @@ pub async fn login_route(users: web::Data>, redirect_uri: urlencoding::encode(redirect_uri).to_string(), }, min_pass_len: MIN_PASS_LEN, - }.render().unwrap()); + } + .render() + .unwrap(), + ); } - - HttpResponse::Ok() - .content_type("text/html") - .body(LoginTemplate { + HttpResponse::Ok().content_type("text/html").body( + LoginTemplate { _parent: BaseLoginPage { page_title: "Login", danger, @@ -155,10 +159,13 @@ pub async fn login_route(users: web::Data>, redirect_uri: urlencoding::encode(redirect_uri).to_string(), }, login, - }.render().unwrap()) + } + .render() + .unwrap(), + ) } /// Sign out user pub async fn logout_route() -> impl Responder { redirect_user("/login?logout=true") -} \ No newline at end of file +} diff --git a/src/controllers/mod.rs b/src/controllers/mod.rs index dd17ed2..692e8ac 100644 --- a/src/controllers/mod.rs +++ b/src/controllers/mod.rs @@ -1,3 +1,3 @@ pub mod assets_controller; +pub mod base_controller; pub mod login_controller; -pub mod base_controller; \ No newline at end of file diff --git a/src/data/app_config.rs b/src/data/app_config.rs index ef8464a..9b0d07a 100644 --- a/src/data/app_config.rs +++ b/src/data/app_config.rs @@ -37,4 +37,4 @@ impl AppConfig { pub fn users_file(&self) -> PathBuf { self.storage_path().join(USERS_LIST_FILE) } -} \ No newline at end of file +} diff --git a/src/data/entity_manager.rs b/src/data/entity_manager.rs index 0bc46f9..c67ab37 100644 --- a/src/data/entity_manager.rs +++ b/src/data/entity_manager.rs @@ -8,7 +8,10 @@ pub struct EntityManager { list: Vec, } -impl EntityManager where E: serde::Serialize + serde::de::DeserializeOwned + Eq + Clone { +impl EntityManager +where + E: serde::Serialize + serde::de::DeserializeOwned + Eq + Clone, +{ /// Open entity pub fn open_or_create>(path: A) -> Res { if !path.as_ref().is_file() { @@ -37,7 +40,10 @@ impl EntityManager where E: serde::Serialize + serde::de::DeserializeOwned /// Save the list fn save(&self) -> Res { - Ok(std::fs::write(&self.file_path, serde_json::to_string(&self.list)?)?) + Ok(std::fs::write( + &self.file_path, + serde_json::to_string(&self.list)?, + )?) } /// Insert a new element in the list @@ -47,7 +53,10 @@ impl EntityManager where E: serde::Serialize + serde::de::DeserializeOwned } /// Replace entries in the list that matches a criteria - pub fn replace_entries(&mut self, filter: F, el: &E) -> Res where F: Fn(&E) -> bool { + pub fn replace_entries(&mut self, filter: F, el: &E) -> Res + where + F: Fn(&E) -> bool, + { for i in 0..self.list.len() { if filter(&self.list[i]) { self.list[i] = el.clone(); @@ -61,4 +70,4 @@ impl EntityManager where E: serde::Serialize + serde::de::DeserializeOwned pub fn iter(&self) -> Iter<'_, E> { self.list.iter() } -} \ No newline at end of file +} diff --git a/src/data/mod.rs b/src/data/mod.rs index 91797cb..bf6248c 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,5 +1,5 @@ pub mod app_config; -pub mod user; -pub mod service; pub mod entity_manager; -pub mod session_identity; \ No newline at end of file +pub mod service; +pub mod session_identity; +pub mod user; diff --git a/src/data/service.rs b/src/data/service.rs index d2ea188..87b6dc2 100644 --- a/src/data/service.rs +++ b/src/data/service.rs @@ -1,2 +1,2 @@ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] -pub struct ServiceID(String); \ No newline at end of file +pub struct ServiceID(String); diff --git a/src/data/session_identity.rs b/src/data/session_identity.rs index 10877e9..c912cf9 100644 --- a/src/data/session_identity.rs +++ b/src/data/session_identity.rs @@ -17,7 +17,6 @@ impl Default for SessionStatus { } } - #[derive(Debug, Serialize, Deserialize, Default)] pub struct SessionIdentityData { pub id: UserID, @@ -33,7 +32,8 @@ impl<'a> SessionIdentity<'a> { } pub fn deserialize_session_data(data: Option) -> Option { - let res: Option = data.as_deref() + let res: Option = data + .as_deref() .map(serde_json::from_str) .map(|f| match f { Ok(d) => Some(d), @@ -55,8 +55,7 @@ impl<'a> SessionIdentity<'a> { } fn set_session_data(&self, data: &SessionIdentityData) { - let s = serde_json::to_string(data) - .expect("Failed to serialize session data!"); + let s = serde_json::to_string(data).expect("Failed to serialize session data!"); self.0.remember(s); } @@ -88,8 +87,6 @@ impl<'a> SessionIdentity<'a> { } pub fn user_id(&self) -> UserID { - self.get_session_data() - .unwrap_or_default() - .id + self.get_session_data().unwrap_or_default().id } -} \ No newline at end of file +} diff --git a/src/data/user.rs b/src/data/user.rs index 5cdef97..9bf53b8 100644 --- a/src/data/user.rs +++ b/src/data/user.rs @@ -80,10 +80,13 @@ impl EntityManager { } /// Update user information - fn update_user(&mut self, id: &UserID, update: F) -> bool where F: FnOnce(User) -> User { + fn update_user(&mut self, id: &UserID, update: F) -> bool + where + F: FnOnce(User) -> User, + { let user = match self.find_by_user_id(id) { None => return false, - Some(user) => user + Some(user) => user, }; if let Err(e) = self.replace_entries(|u| u.uid.eq(id), &update(user)) { @@ -96,7 +99,7 @@ impl EntityManager { pub fn change_user_password(&mut self, id: &UserID, password: &str, temporary: bool) -> bool { let new_hash = match hash_password(password) { - Ok(h) => { h } + Ok(h) => h, Err(e) => { log::error!("Failed to hash user password! {}", e); return false; @@ -109,4 +112,4 @@ impl EntityManager { user }) } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 955e194..6a7e3e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ -pub mod data; -pub mod utils; +pub mod actors; pub mod constants; pub mod controllers; -pub mod actors; -pub mod middlewares; \ No newline at end of file +pub mod data; +pub mod middlewares; +pub mod utils; diff --git a/src/main.rs b/src/main.rs index 2bf333a..074a5f2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,16 @@ use actix::Actor; use actix_identity::{CookieIdentityPolicy, IdentityService}; -use actix_web::{App, get, HttpServer, web}; -use actix_web::cookie::SameSite; use actix_web::cookie::time::Duration; +use actix_web::cookie::SameSite; use actix_web::middleware::Logger; +use actix_web::{get, web, App, HttpServer}; use clap::Parser; use basic_oidc::actors::users_actor::UsersActor; -use basic_oidc::constants::{DEFAULT_ADMIN_PASSWORD, DEFAULT_ADMIN_USERNAME, MAX_INACTIVITY_DURATION, MAX_SESSION_DURATION, SESSION_COOKIE_NAME}; +use basic_oidc::constants::{ + DEFAULT_ADMIN_PASSWORD, DEFAULT_ADMIN_USERNAME, MAX_INACTIVITY_DURATION, MAX_SESSION_DURATION, + SESSION_COOKIE_NAME, +}; use basic_oidc::controllers::assets_controller::assets_route; use basic_oidc::controllers::login_controller::{login_route, logout_route}; use basic_oidc::data::app_config::AppConfig; @@ -72,29 +75,23 @@ async fn main() -> std::io::Result<()> { .login_deadline(Duration::seconds(MAX_SESSION_DURATION)) .same_site(SameSite::Strict); - App::new() .app_data(web::Data::new(users_actor.clone())) .app_data(web::Data::new(config.clone())) - .wrap(Logger::default()) .wrap(AuthMiddleware {}) .wrap(IdentityService::new(policy)) - // /health route .service(health) - // Assets serving .route("/assets/{path:.*}", web::get().to(assets_route)) - // Login page .route("/login", web::get().to(login_route)) .route("/login", web::post().to(login_route)) - // Logout page .route("/logout", web::get().to(logout_route)) }) - .bind(listen_address)? - .run() - .await + .bind(listen_address)? + .run() + .await } diff --git a/src/middlewares/auth_middleware.rs b/src/middlewares/auth_middleware.rs index b281bc4..deed4ef 100644 --- a/src/middlewares/auth_middleware.rs +++ b/src/middlewares/auth_middleware.rs @@ -1,13 +1,16 @@ //! # Authentication middleware -use std::future::{Future, ready, Ready}; +use std::future::{ready, Future, Ready}; use std::pin::Pin; use std::rc::Rc; use actix_identity::RequestIdentity; -use actix_web::{dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform}, Error, HttpResponse, web}; use actix_web::body::EitherBody; use actix_web::http::{header, Method}; +use actix_web::{ + dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform}, + web, Error, HttpResponse, +}; use askama::Template; use crate::constants::{ADMIN_ROUTES, AUTHENTICATED_ROUTES}; @@ -25,10 +28,10 @@ pub struct AuthMiddleware; // `S` - type of the next service // `B` - type of response's body impl Transform for AuthMiddleware - where - S: Service, Error=Error> + 'static, - S::Future: 'static, - B: 'static, +where + S: Service, Error = Error> + 'static, + S::Future: 'static, + B: 'static, { type Response = ServiceResponse>; type Error = Error; @@ -37,7 +40,9 @@ impl Transform for AuthMiddleware type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { - ready(Ok(AuthInnerMiddleware { service: Rc::new(service) })) + ready(Ok(AuthInnerMiddleware { + service: Rc::new(service), + })) } } @@ -67,16 +72,16 @@ pub struct AuthInnerMiddleware { } impl Service for AuthInnerMiddleware - where - S: Service, Error=Error> + 'static, - S::Future: 'static, - B: 'static, +where + S: Service, Error = Error> + 'static, + S::Future: 'static, + B: 'static, { type Response = ServiceResponse>; type Error = Error; #[allow(clippy::type_complexity)] - type Future = Pin>>>; + type Future = Pin>>>; forward_ready!(service); @@ -92,11 +97,14 @@ impl Service for AuthInnerMiddleware if req.method() == Method::POST { if let Some(o) = origin { if !o.to_str().unwrap_or("bad").eq(&config.website_origin) { - log::warn!("Blocked POST request from invalid origin! Origin given {:?}", o); + log::warn!( + "Blocked POST request from invalid origin! Origin given {:?}", + o + ); return Ok(req.into_response( HttpResponse::Unauthorized() .body("POST request from invalid origin!") - .map_into_right_body() + .map_into_right_body(), )); } } @@ -106,28 +114,41 @@ impl Service for AuthInnerMiddleware return Ok(req.into_response( HttpResponse::Unauthorized() .body("Hey don't touch this!") - .map_into_right_body() + .map_into_right_body(), )); } let session = match SessionIdentity::deserialize_session_data(req.get_identity()) { - Some(SessionIdentityData { status: SessionStatus::SignedIn, is_admin: true, .. }) => ConnStatus::Admin, - Some(SessionIdentityData { status: SessionStatus::SignedIn, .. }) => ConnStatus::RegularUser, + Some(SessionIdentityData { + status: SessionStatus::SignedIn, + is_admin: true, + .. + }) => ConnStatus::Admin, + Some(SessionIdentityData { + status: SessionStatus::SignedIn, + .. + }) => ConnStatus::RegularUser, _ => ConnStatus::SignedOut, }; // Redirect user to login page - if !session.is_auth() && (req.path().starts_with(ADMIN_ROUTES) || - req.path().starts_with(AUTHENTICATED_ROUTES)) { + if !session.is_auth() + && (req.path().starts_with(ADMIN_ROUTES) + || req.path().starts_with(AUTHENTICATED_ROUTES)) + { let path = req.path().to_string(); - return Ok(req.into_response(redirect_user_for_login(path)) + return Ok(req + .into_response(redirect_user_for_login(path)) .map_into_right_body()); } // Restrict access to admin pages if !session.is_admin() && req.path().starts_with(ADMIN_ROUTES) { - return Ok(req.into_response(HttpResponse::Unauthorized() - .body(AccessDeniedTemplate {}.render().unwrap())) + return Ok(req + .into_response( + HttpResponse::Unauthorized() + .body(AccessDeniedTemplate {}.render().unwrap()), + ) .map_into_right_body()); } diff --git a/src/middlewares/mod.rs b/src/middlewares/mod.rs index e12d527..2a709c0 100644 --- a/src/middlewares/mod.rs +++ b/src/middlewares/mod.rs @@ -1 +1 @@ -pub mod auth_middleware; \ No newline at end of file +pub mod auth_middleware; diff --git a/src/utils/err.rs b/src/utils/err.rs index d1ed67f..427140e 100644 --- a/src/utils/err.rs +++ b/src/utils/err.rs @@ -1,2 +1,2 @@ use std::error::Error; -pub type Res = Result>; \ No newline at end of file +pub type Res = Result>; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index ab95e2b..dfd8879 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,2 +1,2 @@ pub mod err; -pub mod time; \ No newline at end of file +pub mod time; diff --git a/src/utils/time.rs b/src/utils/time.rs index 37badb5..14e1652 100644 --- a/src/utils/time.rs +++ b/src/utils/time.rs @@ -2,5 +2,8 @@ use std::time::{SystemTime, UNIX_EPOCH}; /// Get the current time since epoch pub fn time() -> u64 { - SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() -} \ No newline at end of file + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() +}