Can set Matrix access token
This commit is contained in:
		
							
								
								
									
										2
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -1616,7 +1616,9 @@ dependencies = [
 | 
				
			|||||||
 "rust-embed",
 | 
					 "rust-embed",
 | 
				
			||||||
 "rust-s3",
 | 
					 "rust-s3",
 | 
				
			||||||
 "serde",
 | 
					 "serde",
 | 
				
			||||||
 | 
					 "serde_json",
 | 
				
			||||||
 "thiserror 2.0.11",
 | 
					 "thiserror 2.0.11",
 | 
				
			||||||
 | 
					 "urlencoding",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,8 @@ clap = { version = "4.5.26", features = ["derive", "env"] }
 | 
				
			|||||||
lazy_static = "1.5.0"
 | 
					lazy_static = "1.5.0"
 | 
				
			||||||
anyhow = "1.0.95"
 | 
					anyhow = "1.0.95"
 | 
				
			||||||
serde = { version = "1.0.217", features = ["derive"] }
 | 
					serde = { version = "1.0.217", features = ["derive"] }
 | 
				
			||||||
rust-s3 = "0.36.0-beta.2"
 | 
					serde_json = "1.0.137"
 | 
				
			||||||
 | 
					rust-s3 = { version = "0.36.0-beta.2", features = ["tokio"] }
 | 
				
			||||||
actix-web = "4"
 | 
					actix-web = "4"
 | 
				
			||||||
actix-session = { version = "0.10.1", features = ["redis-session"] }
 | 
					actix-session = { version = "0.10.1", features = ["redis-session"] }
 | 
				
			||||||
light-openid = "1.0.2"
 | 
					light-openid = "1.0.2"
 | 
				
			||||||
@@ -19,3 +20,4 @@ rand = "0.9.0-beta.3"
 | 
				
			|||||||
rust-embed = "8.5.0"
 | 
					rust-embed = "8.5.0"
 | 
				
			||||||
mime_guess = "2.0.5"
 | 
					mime_guess = "2.0.5"
 | 
				
			||||||
askama = "0.12.1"
 | 
					askama = "0.12.1"
 | 
				
			||||||
 | 
					urlencoding = "2.1.3"
 | 
				
			||||||
@@ -75,11 +75,11 @@ pub struct AppConfig {
 | 
				
			|||||||
    s3_endpoint: String,
 | 
					    s3_endpoint: String,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// S3 access key
 | 
					    /// S3 access key
 | 
				
			||||||
    #[arg(long, env, default_value = "topsecret")]
 | 
					    #[arg(long, env, default_value = "minioadmin")]
 | 
				
			||||||
    s3_access_key: String,
 | 
					    s3_access_key: String,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// S3 secret key
 | 
					    /// S3 secret key
 | 
				
			||||||
    #[arg(long, env, default_value = "topsecret")]
 | 
					    #[arg(long, env, default_value = "minioadmin")]
 | 
				
			||||||
    s3_secret_key: String,
 | 
					    s3_secret_key: String,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// S3 skip auto create bucket if not existing
 | 
					    /// S3 skip auto create bucket if not existing
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,11 +4,16 @@ use actix_web::cookie::Key;
 | 
				
			|||||||
use actix_web::{web, App, HttpServer};
 | 
					use actix_web::{web, App, HttpServer};
 | 
				
			||||||
use matrix_gateway::app_config::AppConfig;
 | 
					use matrix_gateway::app_config::AppConfig;
 | 
				
			||||||
use matrix_gateway::server::web_ui;
 | 
					use matrix_gateway::server::web_ui;
 | 
				
			||||||
 | 
					use matrix_gateway::user::UserConfig;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[actix_web::main]
 | 
					#[actix_web::main]
 | 
				
			||||||
async fn main() -> std::io::Result<()> {
 | 
					async fn main() -> std::io::Result<()> {
 | 
				
			||||||
    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
 | 
					    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    UserConfig::create_bucket_if_required()
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					        .expect("Failed to create bucket!");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // FIXME : not scalable
 | 
					    // FIXME : not scalable
 | 
				
			||||||
    let secret_key = Key::generate();
 | 
					    let secret_key = Key::generate();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -33,6 +38,7 @@ async fn main() -> std::io::Result<()> {
 | 
				
			|||||||
            // Web configuration routes
 | 
					            // Web configuration routes
 | 
				
			||||||
            .route("/assets/{tail:.*}", web::get().to(web_ui::static_file))
 | 
					            .route("/assets/{tail:.*}", web::get().to(web_ui::static_file))
 | 
				
			||||||
            .route("/", web::get().to(web_ui::home))
 | 
					            .route("/", web::get().to(web_ui::home))
 | 
				
			||||||
 | 
					            .route("/", web::post().to(web_ui::home))
 | 
				
			||||||
            .route("/oidc_cb", web::get().to(web_ui::oidc_cb))
 | 
					            .route("/oidc_cb", web::get().to(web_ui::oidc_cb))
 | 
				
			||||||
            .route("/sign_out", web::get().to(web_ui::sign_out))
 | 
					            .route("/sign_out", web::get().to(web_ui::sign_out))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,8 @@ pub enum HttpFailure {
 | 
				
			|||||||
    SessionError(#[from] actix_session::SessionGetError),
 | 
					    SessionError(#[from] actix_session::SessionGetError),
 | 
				
			||||||
    #[error("an unspecified open id error occurred: {0}")]
 | 
					    #[error("an unspecified open id error occurred: {0}")]
 | 
				
			||||||
    OpenID(Box<dyn Error>),
 | 
					    OpenID(Box<dyn Error>),
 | 
				
			||||||
 | 
					    #[error("an error occurred while fetching user configuration: {0}")]
 | 
				
			||||||
 | 
					    FetchUserConfig(anyhow::Error),
 | 
				
			||||||
    #[error("an unspecified internal error occurred: {0}")]
 | 
					    #[error("an unspecified internal error occurred: {0}")]
 | 
				
			||||||
    InternalError(#[from] anyhow::Error),
 | 
					    InternalError(#[from] anyhow::Error),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
use crate::app_config::AppConfig;
 | 
					use crate::app_config::AppConfig;
 | 
				
			||||||
use crate::constants::{STATE_KEY, USER_SESSION_KEY};
 | 
					use crate::constants::{STATE_KEY, USER_SESSION_KEY};
 | 
				
			||||||
use crate::server::{HttpFailure, HttpResult};
 | 
					use crate::server::{HttpFailure, HttpResult};
 | 
				
			||||||
use crate::user::{User, UserID};
 | 
					use crate::user::{User, UserConfig, UserID};
 | 
				
			||||||
use crate::utils;
 | 
					use crate::utils;
 | 
				
			||||||
use actix_session::Session;
 | 
					use actix_session::Session;
 | 
				
			||||||
use actix_web::{web, HttpResponse};
 | 
					use actix_web::{web, HttpResponse};
 | 
				
			||||||
@@ -32,10 +32,21 @@ pub async fn static_file(path: web::Path<String>) -> HttpResult {
 | 
				
			|||||||
struct HomeTemplate {
 | 
					struct HomeTemplate {
 | 
				
			||||||
    name: String,
 | 
					    name: String,
 | 
				
			||||||
    matrix_token: String,
 | 
					    matrix_token: String,
 | 
				
			||||||
 | 
					    success_message: Option<String>,
 | 
				
			||||||
 | 
					    error_message: Option<String>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Update matrix token request
 | 
				
			||||||
 | 
					#[derive(serde::Deserialize)]
 | 
				
			||||||
 | 
					pub struct UpdateMatrixToken {
 | 
				
			||||||
 | 
					    new_matrix_token: Option<String>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Main route
 | 
					/// Main route
 | 
				
			||||||
pub async fn home(session: Session) -> HttpResult {
 | 
					pub async fn home(
 | 
				
			||||||
 | 
					    session: Session,
 | 
				
			||||||
 | 
					    update_matrix_token: Option<web::Form<UpdateMatrixToken>>,
 | 
				
			||||||
 | 
					) -> HttpResult {
 | 
				
			||||||
    // Get user information, requesting authentication if information is missing
 | 
					    // Get user information, requesting authentication if information is missing
 | 
				
			||||||
    let Some(user): Option<User> = session.get(USER_SESSION_KEY)? else {
 | 
					    let Some(user): Option<User> = session.get(USER_SESSION_KEY)? else {
 | 
				
			||||||
        // Generate auth state
 | 
					        // Generate auth state
 | 
				
			||||||
@@ -54,12 +65,37 @@ pub async fn home(session: Session) -> HttpResult {
 | 
				
			|||||||
            .finish());
 | 
					            .finish());
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut success_message = None;
 | 
				
			||||||
 | 
					    let mut error_message = None;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Retrieve user configuration
 | 
				
			||||||
 | 
					    let mut config = UserConfig::load(&user.id)
 | 
				
			||||||
 | 
					        .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 t.len() < 3 {
 | 
				
			||||||
 | 
					                error_message = Some("Specified Matrix token is too short!".to_string());
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // TODO : invalidate all existing connections
 | 
				
			||||||
 | 
					                config.matrix_token = t;
 | 
				
			||||||
 | 
					                config.save().await?;
 | 
				
			||||||
 | 
					                success_message = Some("Matrix token was successfully updated!".to_string());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Render page
 | 
				
			||||||
    Ok(HttpResponse::Ok()
 | 
					    Ok(HttpResponse::Ok()
 | 
				
			||||||
        .insert_header(("content-type", "text/html"))
 | 
					        .insert_header(("content-type", "text/html"))
 | 
				
			||||||
        .body(
 | 
					        .body(
 | 
				
			||||||
            HomeTemplate {
 | 
					            HomeTemplate {
 | 
				
			||||||
                name: user.name,
 | 
					                name: user.name,
 | 
				
			||||||
                matrix_token: "TODO".to_string(),
 | 
					                matrix_token: config.obfuscated_matrix_token(),
 | 
				
			||||||
 | 
					                success_message,
 | 
				
			||||||
 | 
					                error_message,
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            .render()
 | 
					            .render()
 | 
				
			||||||
            .unwrap(),
 | 
					            .unwrap(),
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										126
									
								
								src/user.rs
									
									
									
									
									
								
							
							
						
						
									
										126
									
								
								src/user.rs
									
									
									
									
									
								
							@@ -1,9 +1,135 @@
 | 
				
			|||||||
 | 
					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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Error, Debug)]
 | 
				
			||||||
 | 
					pub enum UserError {
 | 
				
			||||||
 | 
					    #[error("failed to fetch user configuration: {0}")]
 | 
				
			||||||
 | 
					    FetchUserConfig(S3Error),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
					#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
				
			||||||
pub struct UserID(pub String);
 | 
					pub struct UserID(pub String);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl UserID {
 | 
				
			||||||
 | 
					    fn conf_path_in_bucket(&self) -> String {
 | 
				
			||||||
 | 
					        format!("confs/{}.json", urlencoding::encode(&self.0))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
					#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
				
			||||||
pub struct User {
 | 
					pub struct User {
 | 
				
			||||||
    pub id: UserID,
 | 
					    pub id: UserID,
 | 
				
			||||||
    pub name: String,
 | 
					    pub name: String,
 | 
				
			||||||
    pub email: String,
 | 
					    pub email: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
 | 
					pub struct UserConfig {
 | 
				
			||||||
 | 
					    /// Target user ID
 | 
				
			||||||
 | 
					    pub user_id: UserID,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Configuration creation time
 | 
				
			||||||
 | 
					    pub created: u64,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Configuration last update time
 | 
				
			||||||
 | 
					    pub updated: u64,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Current user matrix token
 | 
				
			||||||
 | 
					    pub matrix_token: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl UserConfig {
 | 
				
			||||||
 | 
					    /// Create S3 bucket if required
 | 
				
			||||||
 | 
					    pub async fn create_bucket_if_required() -> anyhow::Result<()> {
 | 
				
			||||||
 | 
					        if AppConfig::get().s3_skip_auto_create_bucket {
 | 
				
			||||||
 | 
					            log::debug!("Skipping bucket existence check");
 | 
				
			||||||
 | 
					            return Ok(());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let bucket = AppConfig::get().s3_bucket()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match bucket.location().await {
 | 
				
			||||||
 | 
					            Ok(_) => {
 | 
				
			||||||
 | 
					                log::debug!("The bucket already exists.");
 | 
				
			||||||
 | 
					                return Ok(());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Err(S3Error::HttpFailWithBody(404, s)) if s.contains("<Code>NoSuchKey</Code>") => {
 | 
				
			||||||
 | 
					                log::warn!("Failed to fetch bucket location, but it seems that bucket exists.");
 | 
				
			||||||
 | 
					                return Ok(());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Err(S3Error::HttpFailWithBody(404, s)) if s.contains("<Code>NoSuchBucket</Code>") => {
 | 
				
			||||||
 | 
					                log::warn!("The bucket does not seem to exists, trying to create it!")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Err(e) => {
 | 
				
			||||||
 | 
					                log::error!("Got unexpected error when querying bucket info: {}", e);
 | 
				
			||||||
 | 
					                return Err(e.into());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Bucket::create_with_path_style(
 | 
				
			||||||
 | 
					            &bucket.name,
 | 
				
			||||||
 | 
					            bucket.region,
 | 
				
			||||||
 | 
					            AppConfig::get().s3_credentials()?,
 | 
				
			||||||
 | 
					            BucketConfiguration::private(),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get current user configuration
 | 
				
			||||||
 | 
					    pub async fn load(user_id: &UserID) -> anyhow::Result<Self> {
 | 
				
			||||||
 | 
					        let res: Result<ResponseData, S3Error> = AppConfig::get()
 | 
				
			||||||
 | 
					            .s3_bucket()?
 | 
				
			||||||
 | 
					            .get_object(user_id.conf_path_in_bucket())
 | 
				
			||||||
 | 
					            .await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match res {
 | 
				
			||||||
 | 
					            Ok(res) => Ok(serde_json::from_slice(res.as_slice())?),
 | 
				
			||||||
 | 
					            Err(S3Error::HttpFailWithBody(404, _)) => {
 | 
				
			||||||
 | 
					                log::warn!("User configuration does not exists, generating a new one...");
 | 
				
			||||||
 | 
					                Ok(Self {
 | 
				
			||||||
 | 
					                    user_id: user_id.clone(),
 | 
				
			||||||
 | 
					                    created: curr_time()?,
 | 
				
			||||||
 | 
					                    updated: curr_time()?,
 | 
				
			||||||
 | 
					                    matrix_token: "".to_string(),
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Err(e) => Err(UserError::FetchUserConfig(e).into()),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Set user configuration
 | 
				
			||||||
 | 
					    pub async fn save(&mut self) -> anyhow::Result<()> {
 | 
				
			||||||
 | 
					        log::info!("Saving new configuration for user {:?}", self.user_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.updated = curr_time()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Save updated configuration
 | 
				
			||||||
 | 
					        AppConfig::get()
 | 
				
			||||||
 | 
					            .s3_bucket()?
 | 
				
			||||||
 | 
					            .put_object(
 | 
				
			||||||
 | 
					                self.user_id.conf_path_in_bucket(),
 | 
				
			||||||
 | 
					                &serde_json::to_vec(self)?,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get current user matrix token, in an obfuscated form
 | 
				
			||||||
 | 
					    pub fn obfuscated_matrix_token(&self) -> String {
 | 
				
			||||||
 | 
					        self.matrix_token
 | 
				
			||||||
 | 
					            .chars()
 | 
				
			||||||
 | 
					            .enumerate()
 | 
				
			||||||
 | 
					            .map(|(num, c)| match num {
 | 
				
			||||||
 | 
					                0 | 1 => c,
 | 
				
			||||||
 | 
					                _ => 'X',
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .collect()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								src/utils.rs
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/utils.rs
									
									
									
									
									
								
							@@ -1,6 +1,14 @@
 | 
				
			|||||||
use rand::distr::{Alphanumeric, SampleString};
 | 
					use rand::distr::{Alphanumeric, SampleString};
 | 
				
			||||||
 | 
					use std::time::{SystemTime, UNIX_EPOCH};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Generate a random string of a given size
 | 
					/// Generate a random string of a given size
 | 
				
			||||||
pub fn rand_str(len: usize) -> String {
 | 
					pub fn rand_str(len: usize) -> String {
 | 
				
			||||||
    Alphanumeric.sample_string(&mut rand::rng(), len)
 | 
					    Alphanumeric.sample_string(&mut rand::rng(), len)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Get current time
 | 
				
			||||||
 | 
					pub fn curr_time() -> anyhow::Result<u64> {
 | 
				
			||||||
 | 
					    Ok(SystemTime::now()
 | 
				
			||||||
 | 
					        .duration_since(UNIX_EPOCH)
 | 
				
			||||||
 | 
					        .map(|t| t.as_secs())?)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,6 +32,20 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div class="body-content">
 | 
					<div class="body-content">
 | 
				
			||||||
 | 
					    <!-- Success message -->
 | 
				
			||||||
 | 
					    {% if let Some(msg) = success_message %}
 | 
				
			||||||
 | 
					    <div class="alert alert-success">
 | 
				
			||||||
 | 
					        {{ msg }}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <!-- Error message -->
 | 
				
			||||||
 | 
					    {% if let Some(msg) = error_message %}
 | 
				
			||||||
 | 
					    <div class="alert alert-danger">
 | 
				
			||||||
 | 
					        {{ msg }}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <!-- Matrix authentication token -->
 | 
					    <!-- Matrix authentication token -->
 | 
				
			||||||
    <div class="card border-light mb-3">
 | 
					    <div class="card border-light mb-3">
 | 
				
			||||||
        <div class="card-header">Matrix authentication token</div>
 | 
					        <div class="card-header">Matrix authentication token</div>
 | 
				
			||||||
@@ -50,11 +64,11 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            <p>Tip: you can rename the session to easily identify it among all your other sessions!</p>
 | 
					            <p>Tip: you can rename the session to easily identify it among all your other sessions!</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <form action="/set_matrix_access_token">
 | 
					            <form action="/" method="post">
 | 
				
			||||||
                <div>
 | 
					                <div>
 | 
				
			||||||
                    <label for="accessTokenInput" class="form-label mt-4">New Matrix access token</label>
 | 
					                    <label for="accessTokenInput" class="form-label mt-4">New Matrix access token</label>
 | 
				
			||||||
                    <input type="text" class="form-control" id="accessTokenInput" aria-describedby="tokenHelp"
 | 
					                    <input type="text" class="form-control" id="accessTokenInput" aria-describedby="tokenHelp"
 | 
				
			||||||
                           placeholder="{{ matrix_token }}" required minlength="2"/>
 | 
					                           placeholder="{{ matrix_token }}" required minlength="2" name="new_matrix_token"/>
 | 
				
			||||||
                    <small id="tokenHelp" class="form-text text-muted">Changing this value will reset all active
 | 
					                    <small id="tokenHelp" class="form-text text-muted">Changing this value will reset all active
 | 
				
			||||||
                        connections
 | 
					                        connections
 | 
				
			||||||
                        to Matrix GW.</small>
 | 
					                        to Matrix GW.</small>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user