diff --git a/src/api_data/admin/admin_auth_success.rs b/src/api_data/admin/admin_auth_success.rs new file mode 100644 index 0000000..6dcf678 --- /dev/null +++ b/src/api_data/admin/admin_auth_success.rs @@ -0,0 +1,18 @@ +//! # Admin authentication success +//! +//! Structure returned when an administrator successfully authenticate +//! +//! @author Pierre Hubert + +use serde::Serialize; + +#[derive(Serialize)] +pub struct AdminAuthSuccess { + token: String +} + +impl AdminAuthSuccess { + pub fn new(t: String) -> Self { + Self { token: t } + } +} \ No newline at end of file diff --git a/src/api_data/admin/mod.rs b/src/api_data/admin/mod.rs index cdd0fae..ecfd175 100644 --- a/src/api_data/admin/mod.rs +++ b/src/api_data/admin/mod.rs @@ -2,4 +2,5 @@ //! //! @author Pierre Hubert -pub mod admin_auth_options; \ No newline at end of file +pub mod admin_auth_options; +pub mod admin_auth_success; \ No newline at end of file diff --git a/src/constants.rs b/src/constants.rs index d944efa..7aba463 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -160,9 +160,15 @@ pub const PASSWORD_RESET_TOKEN_LIFETIME: u64 = 60 * 60 * 6; /// Length of admin reset tokens pub const ADMIN_RESET_TOKEN_LENGTH: usize = 255; -/// Duration of the validity of a password reset token (1 hour) +/// Duration of the validity of an admin password reset token (1 hour) pub const ADMIN_RESET_TOKEN_LIFETIME: u64 = 60 * 60; +/// Length of an admin access token +pub const ADMIN_ACCESS_TOKEN_LENGTH: usize = 300; + +/// Duration of the validation of an admin access token without refresh +pub const ADMIN_ACCESS_TOKEN_LIFETIME: u64 = 60 * 10; + /// Minimum password length pub const PASSWORD_MIN_LENGTH: usize = 3; diff --git a/src/controllers/admin/admin_account_controller.rs b/src/controllers/admin/admin_account_controller.rs index e305b59..d86d119 100644 --- a/src/controllers/admin/admin_account_controller.rs +++ b/src/controllers/admin/admin_account_controller.rs @@ -3,10 +3,12 @@ //! @author Pierre Hubert use crate::api_data::admin::admin_auth_options::AdminAuthOptions; +use crate::api_data::admin::admin_auth_success::AdminAuthSuccess; use crate::data::base_request_handler::BaseRequestHandler; use crate::data::http_request_handler::HttpRequestHandler; -use crate::helpers::admin_account_helper; +use crate::helpers::{admin_access_token_helper, admin_account_helper}; use crate::routes::RequestResult; +use crate::utils::date_utils::time; /// Get admin auth options pub fn get_auth_options(r: &mut HttpRequestHandler) -> RequestResult { @@ -14,4 +16,23 @@ pub fn get_auth_options(r: &mut HttpRequestHandler) -> RequestResult { let admin = admin_account_helper::find_admin_by_email(&mail)?; r.set_response(AdminAuthOptions::new(&admin)) +} + +/// Login admin using a reset token +pub fn auth_with_reset_token(r: &mut HttpRequestHandler) -> RequestResult { + let reset_token = r.post_string("token")?; + let admin = admin_account_helper::find_admin_by_email(&r.post_email("mail")?)?; + + let token = r.some_or_internal_error( + admin.reset_token, + "Specified user has not valid reset token for now!", + )?; + + if !token.token.eq(&reset_token) || time() > token.expire { + return r.forbidden("Specified reset token is invalid!".to_string()); + } + + let token = admin_access_token_helper::create(admin.id)?; + + r.set_response(AdminAuthSuccess::new(token)) } \ No newline at end of file diff --git a/src/data/admin.rs b/src/data/admin.rs index c177d2f..cd2e8b7 100644 --- a/src/data/admin.rs +++ b/src/data/admin.rs @@ -2,7 +2,7 @@ //! //! @author Pierre Hubert -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] pub struct AdminID(u64); impl AdminID { @@ -38,4 +38,14 @@ pub struct AdminKey { pub admin_id: AdminID, pub name: String, pub key: String, +} + +/// Admin access token +/// +/// Used to store authentication of an admin +#[derive(Clone, Debug)] +pub struct AdminAccessToken { + pub token: String, + pub id: AdminID, + pub last_refresh: u64 } \ No newline at end of file diff --git a/src/helpers/admin_access_token_helper.rs b/src/helpers/admin_access_token_helper.rs new file mode 100644 index 0000000..7507e1a --- /dev/null +++ b/src/helpers/admin_access_token_helper.rs @@ -0,0 +1,80 @@ +//! # Admin access token helper +//! +//! @author Pierre Hubert + +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +use crate::constants::{ADMIN_ACCESS_TOKEN_LENGTH, ADMIN_ACCESS_TOKEN_LIFETIME}; +use crate::data::admin::{AdminAccessToken, AdminID}; +use crate::data::error::{ExecError, Res}; +use crate::utils::crypt_utils::rand_str; +use crate::utils::date_utils::time; + +static mut CACHE: Option>>> = None; + +/// Initialize this helper +pub fn init() { + unsafe { + let map = HashMap::new(); + CACHE = Some(Arc::new(Mutex::new(map))); + } +} + +/// Create and return a new access token for an admin +pub fn create(id: AdminID) -> Res { + let map = unsafe { + CACHE.as_ref().unwrap().lock() + }; + + let token = AdminAccessToken { + token: rand_str(ADMIN_ACCESS_TOKEN_LENGTH), + id, + last_refresh: time(), + }; + + map?.insert(id, token.clone()); + + Ok(token.token) +} + +/// Remove an access token from the list +pub fn destroy(id: AdminID) -> Res { + let map = unsafe { + CACHE.as_ref().unwrap().lock() + }; + + map?.remove(&id); + + Ok(()) +} + +/// Find an admin by its access token +pub fn find_by_token(t: &str) -> Res { + let map = unsafe { + CACHE.as_ref().unwrap().lock() + }; + let mut map = map?; + + let token = map.iter() + .filter(|p| p.1.token.eq(t)) + .next(); + + let mut token = match token { + None => { + return Err(ExecError::boxed_new("The token was not recognized as an admin token!")); + } + Some(t) => t.1 + }.clone(); + + + if token.last_refresh + ADMIN_ACCESS_TOKEN_LIFETIME < time() { + return Err(ExecError::boxed_new("The token has expired!")); + } + + token.last_refresh = time(); + + map.insert(token.id, token.clone()); + + Ok(token) +} \ No newline at end of file diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index 49f2b42..b5a1f2f 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -22,4 +22,5 @@ pub mod push_notifications_helper; pub mod independent_push_notifications_service_helper; pub mod firebase_notifications_helper; pub mod forez_presence_helper; -pub mod admin_account_helper; \ No newline at end of file +pub mod admin_account_helper; +pub mod admin_access_token_helper; \ No newline at end of file diff --git a/src/routes.rs b/src/routes.rs index cd57c92..9f41fab 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -337,5 +337,6 @@ pub fn get_routes() -> Vec { // Admin accounts controller Route::limited_admin_post_without_login("/admin/accounts/auth_options", Box::new(admin_account_controller::get_auth_options), LimitPolicy::FAILURE(5)), + Route::limited_admin_post_without_login("/admin/accounts/auth_with_reset_token", Box::new(admin_account_controller::auth_with_reset_token), LimitPolicy::FAILURE(5)), ] } \ No newline at end of file diff --git a/src/server.rs b/src/server.rs index 33553ef..5c035a8 100644 --- a/src/server.rs +++ b/src/server.rs @@ -17,7 +17,7 @@ use crate::controllers::{rtc_relay_controller, user_ws_controller}; use crate::data::base_request_handler::{BaseRequestHandler, PostFile, RequestValue}; use crate::data::config::Config; use crate::data::http_request_handler::HttpRequestHandler; -use crate::helpers::{api_helper, requests_limit_helper}; +use crate::helpers::{api_helper, requests_limit_helper, admin_access_token_helper}; use crate::routes::{get_routes, RequestResult, Route, RouteScope}; use crate::routes::Method::{GET, POST}; use crate::utils::user_data_utils::user_data_path; @@ -352,6 +352,8 @@ pub async fn start_server(conf: &Config) -> std::io::Result<()> { // Initialize limit helper requests_limit_helper::init(); + admin_access_token_helper::init(); + let addr = conf.server_listen_address(); println!("Start to listen on http://{}/", addr);