mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
feat: Improve the initial setup experience
- Issue a single-use token for initial account creation - Disable registration through other methods until the first account is made - Print helpful instructions to the console on the first run - Improve the welcome message sent in the admin room on first run
This commit is contained in:
@@ -1,14 +1,17 @@
|
||||
mod data;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::{future::ready, pin::Pin, sync::Arc};
|
||||
|
||||
use conduwuit::{Err, Result, utils};
|
||||
use data::Data;
|
||||
pub use data::{DatabaseTokenInfo, TokenExpires};
|
||||
use futures::{Stream, StreamExt};
|
||||
use futures::{
|
||||
Stream, StreamExt,
|
||||
stream::{iter, once},
|
||||
};
|
||||
use ruma::OwnedUserId;
|
||||
|
||||
use crate::{Dep, config};
|
||||
use crate::{Dep, config, firstrun};
|
||||
|
||||
const RANDOM_TOKEN_LENGTH: usize = 16;
|
||||
|
||||
@@ -19,6 +22,7 @@ pub struct Service {
|
||||
|
||||
struct Services {
|
||||
config: Dep<config::Service>,
|
||||
firstrun: Dep<firstrun::Service>,
|
||||
}
|
||||
|
||||
/// A validated registration token which may be used to create an account.
|
||||
@@ -46,6 +50,9 @@ pub enum ValidTokenSource {
|
||||
ConfigFile,
|
||||
/// 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,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ValidTokenSource {
|
||||
@@ -53,6 +60,7 @@ impl std::fmt::Display for ValidTokenSource {
|
||||
match self {
|
||||
| Self::ConfigFile => write!(f, "Token defined in config."),
|
||||
| Self::Database(info) => info.fmt(f),
|
||||
| Self::FirstAccount => write!(f, "Initial setup token."),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,6 +71,7 @@ impl crate::Service for Service {
|
||||
db: Data::new(args.db),
|
||||
services: Services {
|
||||
config: args.depend::<config::Service>("config"),
|
||||
firstrun: args.depend::<firstrun::Service>("firstrun"),
|
||||
},
|
||||
}))
|
||||
}
|
||||
@@ -71,13 +80,17 @@ impl crate::Service for Service {
|
||||
}
|
||||
|
||||
impl Service {
|
||||
/// Generate a random string suitable to be used as a registration token.
|
||||
#[must_use]
|
||||
pub fn generate_token_string() -> String { utils::random_string(RANDOM_TOKEN_LENGTH) }
|
||||
|
||||
/// Issue a new registration token and save it in the database.
|
||||
pub fn issue_token(
|
||||
&self,
|
||||
creator: OwnedUserId,
|
||||
expires: Option<TokenExpires>,
|
||||
) -> (String, DatabaseTokenInfo) {
|
||||
let token = utils::random_string(RANDOM_TOKEN_LENGTH);
|
||||
let token = Self::generate_token_string();
|
||||
let info = DatabaseTokenInfo::new(creator, expires);
|
||||
|
||||
self.db.save_token(&token, &info);
|
||||
@@ -87,20 +100,31 @@ impl Service {
|
||||
/// Get all the "special" registration tokens that aren't defined in the
|
||||
/// database.
|
||||
fn iterate_static_tokens(&self) -> impl Iterator<Item = ValidToken> {
|
||||
// right now this is just the config file token
|
||||
// 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()
|
||||
}
|
||||
|
||||
/// Validate a registration token.
|
||||
pub async fn validate_token(&self, token: String) -> Option<ValidToken> {
|
||||
// Check static registration tokens first
|
||||
// Check for the first-account token first
|
||||
if let Some(first_account_token) = self.services.firstrun.get_first_account_token() {
|
||||
if first_account_token == *token {
|
||||
return Some(first_account_token);
|
||||
}
|
||||
|
||||
// If the first-account token is set, no other tokens are valid
|
||||
return None;
|
||||
}
|
||||
|
||||
// Then static registration tokens
|
||||
for static_token in self.iterate_static_tokens() {
|
||||
if static_token == *token {
|
||||
return Some(static_token);
|
||||
}
|
||||
}
|
||||
|
||||
// Now check the database
|
||||
// Then check the database
|
||||
if let Some(token_info) = self.db.lookup_token_info(&token).await
|
||||
&& token_info.is_valid()
|
||||
{
|
||||
@@ -117,14 +141,14 @@ impl Service {
|
||||
/// Mark a valid token as having been used to create a new account.
|
||||
pub fn mark_token_as_used(&self, ValidToken { token, source }: ValidToken) {
|
||||
match source {
|
||||
| ValidTokenSource::ConfigFile => {
|
||||
// we don't track uses of the config file token, do nothing
|
||||
},
|
||||
| ValidTokenSource::Database(mut info) => {
|
||||
info.uses = info.uses.saturating_add(1);
|
||||
|
||||
self.db.save_token(&token, &info);
|
||||
},
|
||||
| _ => {
|
||||
// Do nothing for other token sources.
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +159,6 @@ impl Service {
|
||||
pub fn revoke_token(&self, ValidToken { token, source }: ValidToken) -> Result {
|
||||
match source {
|
||||
| ValidTokenSource::ConfigFile => {
|
||||
// the config file token cannot be revoked
|
||||
Err!(
|
||||
"The token set in the config file cannot be revoked. Edit the config file \
|
||||
to change it."
|
||||
@@ -145,11 +168,19 @@ impl Service {
|
||||
self.db.revoke_token(&token);
|
||||
Ok(())
|
||||
},
|
||||
| ValidTokenSource::FirstAccount => {
|
||||
Err!("The initial setup token cannot be revoked.")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over all valid registration tokens.
|
||||
pub fn iterate_tokens(&self) -> impl Stream<Item = ValidToken> + Send + '_ {
|
||||
pub fn iterate_tokens(&self) -> Pin<Box<dyn Stream<Item = ValidToken> + Send + '_>> {
|
||||
// If the first-account token is set, no other tokens are valid
|
||||
if let Some(first_account_token) = self.services.firstrun.get_first_account_token() {
|
||||
return once(ready(first_account_token)).boxed();
|
||||
}
|
||||
|
||||
let db_tokens = self
|
||||
.db
|
||||
.iterate_and_clean_tokens()
|
||||
@@ -158,6 +189,6 @@ impl Service {
|
||||
source: ValidTokenSource::Database(info),
|
||||
});
|
||||
|
||||
futures::stream::iter(self.iterate_static_tokens()).chain(db_tokens)
|
||||
iter(self.iterate_static_tokens()).chain(db_tokens).boxed()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user