Can register new clients
This commit is contained in:
		
							
								
								
									
										15
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										15
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -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" | ||||
|   | ||||
| @@ -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" | ||||
| urlencoding = "2.1.3" | ||||
| uuid = { version = "1.12.1", features = ["v4", "serde"] } | ||||
| ipnet = { version = "2.11.0", features = ["serde"] } | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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<String>, | ||||
| } | ||||
|  | ||||
| /// Update matrix token request | ||||
| /// HTTP form request | ||||
| #[derive(serde::Deserialize)] | ||||
| pub struct UpdateMatrixToken { | ||||
| pub struct FormRequest { | ||||
|     /// Update matrix token | ||||
|     new_matrix_token: Option<String>, | ||||
|  | ||||
|     /// Create a new client | ||||
|     new_client_desc: Option<String>, | ||||
|  | ||||
|     /// Restrict new client to a given network | ||||
|     ip_network: Option<String>, | ||||
| } | ||||
|  | ||||
| /// Main route | ||||
| pub async fn home( | ||||
|     session: Session, | ||||
|     update_matrix_token: Option<web::Form<UpdateMatrixToken>>, | ||||
| ) -> HttpResult { | ||||
| pub async fn home(session: Session, form_req: Option<web::Form<FormRequest>>) -> HttpResult { | ||||
|     // Get user information, requesting authentication if information is missing | ||||
|     let Some(user): Option<User> = 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 | ||||
|   | ||||
							
								
								
									
										37
									
								
								src/user.rs
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								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<ipnet::IpNet>, | ||||
|  | ||||
|     /// Client secret | ||||
|     pub secret: String, | ||||
| } | ||||
|  | ||||
| impl APIClient { | ||||
|     /// Generate a new API client | ||||
|     pub fn generate(description: String, network: Option<ipnet::IpNet>) -> 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<APIClient>, | ||||
| } | ||||
|  | ||||
| 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()), | ||||
|   | ||||
| @@ -46,6 +46,29 @@ | ||||
|     </div> | ||||
|     {% endif %} | ||||
|  | ||||
|     <!-- New client --> | ||||
|     <div class="card border-light mb-3"> | ||||
|         <div class="card-header">New client</div> | ||||
|         <div class="card-body"> | ||||
|             <form action="/" method="post"> | ||||
|                 <div> | ||||
|                     <label for="new_client_desc" class="form-label">Description</label> | ||||
|                     <input type="text" class="form-control" id="new_client_desc" required minlength="3" | ||||
|                            aria-describedby="new_client_desc" placeholder="New client description..." name="new_client_desc" /> | ||||
|                     <small class="form-text text-muted">Client description helps with identification.</small> | ||||
|                 </div> | ||||
|                 <div> | ||||
|                     <label for="ip_network" class="form-label">Allowed IP network</label> | ||||
|                     <input type="text" class="form-control" id="ip_network" aria-describedby="ip_network" | ||||
|                            placeholder="Client network (x.x.x.x/x or x:x:x:x:x:x/x" name="ip_network" /> | ||||
|                     <small class="form-text text-muted">Restrict the networks this IP address can be used from.</small> | ||||
|                 </div> | ||||
|  | ||||
|                 <input type="submit" class="btn btn-primary" value="Create client"/> | ||||
|             </form> | ||||
|         </div> | ||||
|     </div> | ||||
|  | ||||
|     <!-- Matrix authentication token --> | ||||
|     <div class="card border-light mb-3"> | ||||
|         <div class="card-header">Matrix authentication token</div> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user