diff --git a/Cargo.lock b/Cargo.lock index 8a2a94b..2037862 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1483,6 +1483,9 @@ name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +dependencies = [ + "serde", +] [[package]] name = "is_terminal_polyfill" @@ -1608,6 +1611,7 @@ dependencies = [ "askama", "clap", "env_logger", + "ipnet", "lazy_static", "light-openid", "log", @@ -1619,6 +1623,7 @@ dependencies = [ "serde_json", "thiserror 2.0.11", "urlencoding", + "uuid", ] [[package]] @@ -2887,6 +2892,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" +dependencies = [ + "getrandom 0.2.15", + "serde", +] + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index c201a4f..0e3494f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,4 +20,6 @@ rand = "0.9.0-beta.3" rust-embed = "8.5.0" mime_guess = "2.0.5" askama = "0.12.1" -urlencoding = "2.1.3" \ No newline at end of file +urlencoding = "2.1.3" +uuid = { version = "1.12.1", features = ["v4", "serde"] } +ipnet = { version = "2.11.0", features = ["serde"] } \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 2840342..c4940a9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,3 @@ -# This compose file is compatible with Compose itself, it might need some -# adjustments to run properly with stack. - -version: "3" - services: synapse: image: docker.io/matrixdotorg/synapse:latest diff --git a/src/server/web_ui.rs b/src/server/web_ui.rs index d9875a8..c781112 100644 --- a/src/server/web_ui.rs +++ b/src/server/web_ui.rs @@ -1,12 +1,14 @@ use crate::app_config::AppConfig; use crate::constants::{STATE_KEY, USER_SESSION_KEY}; use crate::server::{HttpFailure, HttpResult}; -use crate::user::{User, UserConfig, UserID}; +use crate::user::{APIClient, User, UserConfig, UserID}; use crate::utils; use actix_session::Session; use actix_web::{web, HttpResponse}; use askama::Template; +use ipnet::IpNet; use light_openid::primitives::OpenIDConfig; +use std::str::FromStr; /// Static assets #[derive(rust_embed::Embed)] @@ -36,17 +38,21 @@ struct HomeTemplate { error_message: Option, } -/// Update matrix token request +/// HTTP form request #[derive(serde::Deserialize)] -pub struct UpdateMatrixToken { +pub struct FormRequest { + /// Update matrix token new_matrix_token: Option, + + /// Create a new client + new_client_desc: Option, + + /// Restrict new client to a given network + ip_network: Option, } /// Main route -pub async fn home( - session: Session, - update_matrix_token: Option>, -) -> HttpResult { +pub async fn home(session: Session, form_req: Option>) -> HttpResult { // Get user information, requesting authentication if information is missing let Some(user): Option = session.get(USER_SESSION_KEY)? else { // Generate auth state @@ -73,9 +79,9 @@ pub async fn home( .await .map_err(HttpFailure::FetchUserConfig)?; - // Update matrix token, if requested - if let Some(update_matrix_token) = update_matrix_token { - if let Some(t) = update_matrix_token.0.new_matrix_token { + if let Some(form_req) = form_req { + // Update matrix token, if requested + if let Some(t) = form_req.0.new_matrix_token { if t.len() < 3 { error_message = Some("Specified Matrix token is too short!".to_string()); } else { @@ -85,6 +91,28 @@ pub async fn home( success_message = Some("Matrix token was successfully updated!".to_string()); } } + + // Create a new client, if requested + if let Some(new_token_desc) = form_req.0.new_client_desc { + let ip_net = match form_req.0.ip_network.as_deref() { + None | Some("") => None, + Some(e) => match IpNet::from_str(e) { + Ok(n) => Some(n), + Err(e) => { + log::error!("Failed to parse IP network provided by user: {e}"); + error_message = Some(format!("Failed to parse restricted IP network: {e}")); + None + } + }, + }; + + if error_message.is_none() { + let token = APIClient::generate(new_token_desc, ip_net); + success_message = Some(format!("The secret of your new token is '{}'. Be sure to write it somewhere as you will not be able to recover it later!", token.secret)); + config.clients.push(token); + config.save().await?; + } + } } // Render page diff --git a/src/user.rs b/src/user.rs index a7e7ba8..1e13529 100644 --- a/src/user.rs +++ b/src/user.rs @@ -1,10 +1,11 @@ -use crate::app_config::AppConfig; -use crate::utils::curr_time; use s3::error::S3Error; use s3::request::ResponseData; use s3::{Bucket, BucketConfiguration}; use thiserror::Error; +use crate::app_config::AppConfig; +use crate::utils::{curr_time, rand_str}; + #[derive(Error, Debug)] pub enum UserError { #[error("failed to fetch user configuration: {0}")] @@ -27,6 +28,34 @@ pub struct User { pub email: String, } +/// Single API client information +#[derive(serde::Serialize, serde::Deserialize)] +pub struct APIClient { + /// Client unique ID + pub id: uuid::Uuid, + + /// Client description + pub description: String, + + /// Restricted API network for token + pub network: Option, + + /// Client secret + pub secret: String, +} + +impl APIClient { + /// Generate a new API client + pub fn generate(description: String, network: Option) -> Self { + Self { + id: Default::default(), + description, + network, + secret: rand_str(20), + } + } +} + #[derive(serde::Serialize, serde::Deserialize)] pub struct UserConfig { /// Target user ID @@ -40,6 +69,9 @@ pub struct UserConfig { /// Current user matrix token pub matrix_token: String, + + /// API clients + pub clients: Vec, } impl UserConfig { @@ -97,6 +129,7 @@ impl UserConfig { created: curr_time()?, updated: curr_time()?, matrix_token: "".to_string(), + clients: vec![], }) } Err(e) => Err(UserError::FetchUserConfig(e).into()), diff --git a/templates/index.html b/templates/index.html index 757e92b..3772812 100644 --- a/templates/index.html +++ b/templates/index.html @@ -46,6 +46,29 @@ {% endif %} + +
+
New client
+
+
+
+ + + Client description helps with identification. +
+
+ + + Restrict the networks this IP address can be used from. +
+ + +
+
+
+
Matrix authentication token