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"
 | 
			
		||||
 
 | 
			
		||||
@@ -21,3 +21,5 @@ rust-embed = "8.5.0"
 | 
			
		||||
mime_guess = "2.0.5"
 | 
			
		||||
askama = "0.12.1"
 | 
			
		||||
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)?;
 | 
			
		||||
 | 
			
		||||
    if let Some(form_req) = form_req {
 | 
			
		||||
        // 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(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