feat: Readded support for reading registration tokens from a file

Co-authored-by: Ginger <ginger@gingershaped.computer>
This commit is contained in:
Ben Botwin 2026-02-16 16:27:59 -05:00 committed by Ellis Git
parent da561ab792
commit 5eb74bc1dd
5 changed files with 79 additions and 28 deletions

View file

@ -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

View file

@ -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 \

View file

@ -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<String>,
/// 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<PathBuf>,
/// 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

View file

@ -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<ValidToken> {
self.registration_token.clone().map(|token| ValidToken {
token,
source: ValidTokenSource::ConfigFile,
})
self.registration_token
.clone()
.map(|token| ValidToken { token, source: ValidTokenSource::Config })
}
}

View file

@ -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<String>,
}
struct Services {
@ -45,34 +48,54 @@ impl PartialEq<str> 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<Arc<Self>> {
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::Service>("config"),
firstrun: args.depend::<firstrun::Service>("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<Item = ValidToken> {
// 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."
)
},
}
}