diff --git a/conduwuit-example.toml b/conduwuit-example.toml index 7070ef7c..967efd37 100644 --- a/conduwuit-example.toml +++ b/conduwuit-example.toml @@ -476,18 +476,23 @@ #yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = false # A static registration token that new users will have to provide when -# creating an account. If unset and `allow_registration` is true, -# you must set -# `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse` -# to true to allow open registration without any conditions. -# -# If you do not want to set a static token, the `!admin token` commands -# may also be used to manage registration tokens. +# creating an account. This token does not supersede tokens from other sources, such as the `!admin token` +# command or the `registration_token_file` configuration option. # # example: "o&^uCtes4HPf0Vu@F20jQeeWE7" # #registration_token = +# A path to a file containing static registration tokens, one per line. +# Tokens in this file do not supersede tokens from other sources, such as the `!admin token` +# command or the `registration_token` configuration option. +# +# The file will be read once, when Continuwuity starts. It is not currently reread +# when the server configuration is reloaded. If the file cannot be read, Continuwuity +# will fail to start. +# +#registration_token_file = + # The public site key for reCaptcha. If this is provided, reCaptcha # becomes required during registration. If both captcha *and* # registration token are enabled, both will be required during diff --git a/src/core/config/check.rs b/src/core/config/check.rs index 1ae6e1df..b98c4696 100644 --- a/src/core/config/check.rs +++ b/src/core/config/check.rs @@ -174,6 +174,7 @@ pub fn check(config: &Config) -> Result { if config.allow_registration && config.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse && config.registration_token.is_none() + && config.registration_token_file.is_none() { warn!( "Open registration is enabled via setting \ diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index b75be63c..fb2c9375 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -609,19 +609,25 @@ pub struct Config { pub yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse: bool, /// A static registration token that new users will have to provide when - /// creating an account. If unset and `allow_registration` is true, - /// you must set - /// `yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse` - /// to true to allow open registration without any conditions. - /// - /// If you do not want to set a static token, the `!admin token` commands - /// may also be used to manage registration tokens. + /// creating an account. This token does not supersede tokens from other + /// sources, such as the `!admin token` command or the + /// `registration_token_file` configuration option. /// /// example: "o&^uCtes4HPf0Vu@F20jQeeWE7" /// /// display: sensitive pub registration_token: Option, + /// A path to a file containing static registration tokens, one per line. + /// Tokens in this file do not supersede tokens from other sources, such as + /// the `!admin token` command or the `registration_token` configuration + /// option. + /// + /// The file will be read once, when Continuwuity starts. It is not + /// currently reread when the server configuration is reloaded. If the file + /// cannot be read, Continuwuity will fail to start. + pub registration_token_file: Option, + /// The public site key for reCaptcha. If this is provided, reCaptcha /// becomes required during registration. If both captcha *and* /// registration token are enabled, both will be required during diff --git a/src/service/config/mod.rs b/src/service/config/mod.rs index 20cb431d..a8ae8c11 100644 --- a/src/service/config/mod.rs +++ b/src/service/config/mod.rs @@ -19,10 +19,9 @@ impl Service { /// Get the registration token set in the config file, if it exists. #[must_use] pub fn get_config_file_token(&self) -> Option { - self.registration_token.clone().map(|token| ValidToken { - token, - source: ValidTokenSource::ConfigFile, - }) + self.registration_token + .clone() + .map(|token| ValidToken { token, source: ValidTokenSource::Config }) } } diff --git a/src/service/registration_tokens/mod.rs b/src/service/registration_tokens/mod.rs index e401762a..0d72b2d5 100644 --- a/src/service/registration_tokens/mod.rs +++ b/src/service/registration_tokens/mod.rs @@ -2,7 +2,7 @@ mod data; use std::{future::ready, pin::Pin, sync::Arc}; -use conduwuit::{Err, Result, utils}; +use conduwuit::{Err, Result, err, utils}; use data::Data; pub use data::{DatabaseTokenInfo, TokenExpires}; use futures::{ @@ -18,6 +18,9 @@ const RANDOM_TOKEN_LENGTH: usize = 16; pub struct Service { db: Data, services: Services, + /// The registration tokens which were read from the registration token + /// file, if one is configured. + registration_tokens_from_file: Vec, } struct Services { @@ -45,34 +48,54 @@ impl PartialEq for ValidToken { /// The source of a valid database token. #[derive(Debug)] pub enum ValidTokenSource { - /// The static token set in the homeserver's config file, which is - /// always valid. - ConfigFile, + /// The static token set in the homeserver's config file. + Config, /// A database token which has been checked to be valid. Database(DatabaseTokenInfo), /// The single-use token which may be used to create the homeserver's first /// account. FirstAccount, + /// A registration token read from the registration token file set in the + /// config. + TokenFile, } impl std::fmt::Display for ValidTokenSource { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - | Self::ConfigFile => write!(f, "Token defined in config."), + | Self::Config => write!(f, "Static token set in the server configuration."), | Self::Database(info) => info.fmt(f), | Self::FirstAccount => write!(f, "Initial setup token."), + | Self::TokenFile => write!(f, "Static token set in the registration token file."), } } } impl crate::Service for Service { fn build(args: crate::Args<'_>) -> Result> { + let registration_tokens_from_file = args.server.config.registration_token_file + .clone() + // If the token file option was set, read the path it points to + .map(std::fs::read_to_string) + .transpose() + .map_err(|err| err!("Failed to read registration token file: {err}")) + .map(|tokens| { + if let Some(tokens) = tokens { + // If the token file option was set, return the file's lines as tokens + tokens.lines().map(ToOwned::to_owned).collect() + } else { + // Otherwise, if the option wasn't set, return no tokens + vec![] + } + })?; + Ok(Arc::new(Self { db: Data::new(args.db), services: Services { config: args.depend::("config"), firstrun: args.depend::("firstrun"), }, + registration_tokens_from_file, })) } @@ -97,12 +120,23 @@ impl Service { (token, info) } - /// Get all the "special" registration tokens that aren't defined in the + /// Get all the static registration tokens that aren't defined in the /// database. fn iterate_static_tokens(&self) -> impl Iterator { - // This does not include the first-account token, because it's special: - // no other registration tokens are valid when it is set. - self.services.config.get_config_file_token().into_iter() + // This does not include the first-account token, because it has special + // behavior: no other registration tokens are valid when it is set. + self.services + .config + .get_config_file_token() + .into_iter() + .chain( + self.registration_tokens_from_file + .iter() + .map(|token_string| ValidToken { + token: token_string.clone(), + source: ValidTokenSource::TokenFile, + }), + ) } /// Validate a registration token. @@ -158,7 +192,7 @@ impl Service { /// revoked. pub fn revoke_token(&self, ValidToken { token, source }: ValidToken) -> Result { match source { - | ValidTokenSource::ConfigFile => { + | ValidTokenSource::Config => { Err!( "The token set in the config file cannot be revoked. Edit the config file \ to change it." @@ -171,6 +205,12 @@ impl Service { | ValidTokenSource::FirstAccount => { Err!("The initial setup token cannot be revoked.") }, + | ValidTokenSource::TokenFile => { + Err!( + "Tokens set in the registration token file cannot be revoked. Edit the \ + registration token file and restart Continuwuity to change them." + ) + }, } }