From 8daf566eb2f1152d4ae5ce7f1863acf84475dcc6 Mon Sep 17 00:00:00 2001 From: silverpill Date: Mon, 10 Apr 2023 21:20:47 +0000 Subject: [PATCH] Add create-user command --- CHANGELOG.md | 5 + docs/mitractl.md | 6 + mitra-cli/src/cli.rs | 163 +++++++++------------- mitra-cli/src/main.rs | 3 +- mitra-models/src/invoices/queries.rs | 1 + mitra-models/src/oauth/queries.rs | 2 + mitra-models/src/posts/helpers.rs | 23 ++- mitra-models/src/posts/queries.rs | 9 ++ mitra-models/src/relationships/queries.rs | 2 + mitra-models/src/subscriptions/queries.rs | 1 + mitra-models/src/users/queries.rs | 17 ++- src/admin/mod.rs | 1 + src/admin/roles.rs | 15 ++ src/lib.rs | 1 + src/mastodon_api/accounts/helpers.rs | 2 + src/mastodon_api/accounts/types.rs | 29 ---- src/mastodon_api/accounts/views.rs | 10 +- src/validators/users.rs | 3 + src/webfinger/views.rs | 1 + 19 files changed, 153 insertions(+), 141 deletions(-) create mode 100644 src/admin/mod.rs create mode 100644 src/admin/roles.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index c24f2ae..719190c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Added + +- Support Monero Wallet RPC authentication. +- Added `create-user` command. + ### Changed - Added emoji count check to profile data validator. diff --git a/docs/mitractl.md b/docs/mitractl.md index 94fb116..a5a2a28 100644 --- a/docs/mitractl.md +++ b/docs/mitractl.md @@ -32,6 +32,12 @@ List generated invites: mitractl list-invite-codes ``` +Create user: + +```shell +mitractl create-user +``` + Set or change password: ```shell diff --git a/mitra-cli/src/cli.rs b/mitra-cli/src/cli.rs index 6f156ad..504c6ea 100644 --- a/mitra-cli/src/cli.rs +++ b/mitra-cli/src/cli.rs @@ -1,19 +1,14 @@ -use anyhow::{anyhow, Error}; +use anyhow::Error; use clap::Parser; use uuid::Uuid; use mitra::activitypub::{ - actors::helpers::update_remote_profile, - builders::delete_note::prepare_delete_note, - builders::delete_person::prepare_delete_person, - fetcher::fetchers::fetch_actor, + actors::helpers::update_remote_profile, builders::delete_note::prepare_delete_note, + builders::delete_person::prepare_delete_person, fetcher::fetchers::fetch_actor, }; -use mitra::media::{ - remove_files, - remove_media, - MediaStorage, -}; -use mitra::validators::emojis::EMOJI_LOCAL_MAX_SIZE; +use mitra::admin::roles::{role_from_str, ALLOWED_ROLES}; +use mitra::media::{remove_files, remove_media, MediaStorage}; +use mitra::validators::{emojis::EMOJI_LOCAL_MAX_SIZE, users::validate_local_username}; use mitra_config::Config; use mitra_models::{ attachments::queries::delete_unused_attachments, @@ -21,35 +16,23 @@ use mitra_models::{ database::DatabaseClient, emojis::helpers::get_emoji_by_name, emojis::queries::{ - create_emoji, - delete_emoji, - find_unused_remote_emojis, - get_emoji_by_name_and_hostname, + create_emoji, delete_emoji, find_unused_remote_emojis, get_emoji_by_name_and_hostname, }, oauth::queries::delete_oauth_tokens, posts::queries::{delete_post, find_extraneous_posts, get_post_by_id}, profiles::queries::{ - delete_profile, - find_empty_profiles, - find_unreachable, - get_profile_by_id, + delete_profile, find_empty_profiles, find_unreachable, get_profile_by_id, get_profile_by_remote_actor_id, }, subscriptions::queries::reset_subscriptions, users::queries::{ - create_invite_code, - get_invite_codes, - get_user_by_id, - set_user_password, + create_invite_code, create_user, get_invite_codes, get_user_by_id, set_user_password, set_user_role, }, - users::types::Role, + users::types::UserCreateData, }; use mitra_utils::{ - crypto_rsa::{ - generate_rsa_key, - serialize_private_key, - }, + crypto_rsa::{generate_rsa_key, serialize_private_key}, datetime::{days_before_now, get_min_datetime}, passwords::hash_password, }; @@ -68,6 +51,7 @@ pub enum SubCommand { GenerateInviteCode(GenerateInviteCode), ListInviteCodes(ListInviteCodes), + CreateUser(CreateUser), SetPassword(SetPassword), SetRole(SetRole), RefetchActor(RefetchActor), @@ -116,14 +100,8 @@ pub struct GenerateInviteCode { } impl GenerateInviteCode { - pub async fn execute( - &self, - db_client: &impl DatabaseClient, - ) -> Result<(), Error> { - let invite_code = create_invite_code( - db_client, - self.note.as_deref(), - ).await?; + pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> { + let invite_code = create_invite_code(db_client, self.note.as_deref()).await?; println!("generated invite code: {}", invite_code); Ok(()) } @@ -134,10 +112,7 @@ impl GenerateInviteCode { pub struct ListInviteCodes; impl ListInviteCodes { - pub async fn execute( - &self, - db_client: &impl DatabaseClient, - ) -> Result<(), Error> { + pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> { let invite_codes = get_invite_codes(db_client).await?; if invite_codes.is_empty() { println!("no invite codes found"); @@ -149,7 +124,37 @@ impl ListInviteCodes { } else { println!("{}", invite_code.code); }; + } + Ok(()) + } +} + +/// Create new user +#[derive(Parser)] +pub struct CreateUser { + username: String, + password: String, + #[clap(value_parser = ALLOWED_ROLES)] + role: String, +} + +impl CreateUser { + pub async fn execute(&self, db_client: &mut impl DatabaseClient) -> Result<(), Error> { + validate_local_username(&self.username)?; + let password_hash = hash_password(&self.password)?; + let private_key = generate_rsa_key()?; + let private_key_pem = serialize_private_key(&private_key)?; + let role = role_from_str(&self.role)?; + let user_data = UserCreateData { + username: self.username.clone(), + password_hash: Some(password_hash), + private_key_pem, + wallet_address: None, + invite_code: None, + role, }; + create_user(db_client, user_data).await?; + println!("user created"); Ok(()) } } @@ -162,10 +167,7 @@ pub struct SetPassword { } impl SetPassword { - pub async fn execute( - &self, - db_client: &impl DatabaseClient, - ) -> Result<(), Error> { + pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> { let password_hash = hash_password(&self.password)?; set_user_password(db_client, &self.id, password_hash).await?; // Revoke all sessions @@ -179,21 +181,13 @@ impl SetPassword { #[derive(Parser)] pub struct SetRole { id: Uuid, - #[clap(value_parser = ["admin", "user", "read_only_user"])] + #[clap(value_parser = ALLOWED_ROLES)] role: String, } impl SetRole { - pub async fn execute( - &self, - db_client: &impl DatabaseClient, - ) -> Result<(), Error> { - let role = match self.role.as_str() { - "user" => Role::NormalUser, - "admin" => Role::Admin, - "read_only_user" => Role::ReadOnlyUser, - _ => return Err(anyhow!("unknown role")), - }; + pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> { + let role = role_from_str(&self.role)?; set_user_role(db_client, &self.id, role).await?; println!("role changed"); Ok(()) @@ -212,10 +206,7 @@ impl RefetchActor { config: &Config, db_client: &mut impl DatabaseClient, ) -> Result<(), Error> { - let profile = get_profile_by_remote_actor_id( - db_client, - &self.id, - ).await?; + let profile = get_profile_by_remote_actor_id(db_client, &self.id).await?; let actor = fetch_actor(&config.instance(), &self.id).await?; update_remote_profile( db_client, @@ -223,7 +214,8 @@ impl RefetchActor { &MediaStorage::from(config), profile, actor, - ).await?; + ) + .await?; println!("profile updated"); Ok(()) } @@ -245,8 +237,7 @@ impl DeleteProfile { let mut maybe_delete_person = None; if profile.is_local() { let user = get_user_by_id(db_client, &profile.id).await?; - let activity = - prepare_delete_person(db_client, &config.instance(), &user).await?; + let activity = prepare_delete_person(db_client, &config.instance(), &user).await?; maybe_delete_person = Some(activity); }; let deletion_queue = delete_profile(db_client, &profile.id).await?; @@ -276,12 +267,8 @@ impl DeletePost { let mut maybe_delete_note = None; if post.author.is_local() { let author = get_user_by_id(db_client, &post.author.id).await?; - let activity = prepare_delete_note( - db_client, - &config.instance(), - &author, - &post, - ).await?; + let activity = + prepare_delete_note(db_client, &config.instance(), &author, &post).await?; maybe_delete_note = Some(activity); }; let deletion_queue = delete_post(db_client, &post.id).await?; @@ -308,11 +295,8 @@ impl DeleteEmoji { config: &Config, db_client: &impl DatabaseClient, ) -> Result<(), Error> { - let emoji = get_emoji_by_name( - db_client, - &self.emoji_name, - self.hostname.as_deref(), - ).await?; + let emoji = + get_emoji_by_name(db_client, &self.emoji_name, self.hostname.as_deref()).await?; let deletion_queue = delete_emoji(db_client, &emoji.id).await?; remove_media(config, deletion_queue).await; println!("emoji deleted"); @@ -338,7 +322,7 @@ impl DeleteExtraneousPosts { let deletion_queue = delete_post(db_client, &post_id).await?; remove_media(config, deletion_queue).await; println!("post {} deleted", post_id); - }; + } Ok(()) } } @@ -356,10 +340,7 @@ impl DeleteUnusedAttachments { db_client: &impl DatabaseClient, ) -> Result<(), Error> { let created_before = days_before_now(self.days); - let deletion_queue = delete_unused_attachments( - db_client, - &created_before, - ).await?; + let deletion_queue = delete_unused_attachments(db_client, &created_before).await?; remove_media(config, deletion_queue).await; println!("unused attachments deleted"); Ok(()) @@ -379,10 +360,9 @@ impl DeleteOrphanedFiles { let media_dir = config.media_dir(); let mut files = vec![]; for maybe_path in std::fs::read_dir(&media_dir)? { - let file_name = maybe_path?.file_name() - .to_string_lossy().to_string(); + let file_name = maybe_path?.file_name().to_string_lossy().to_string(); files.push(file_name); - }; + } println!("found {} files", files.len()); let orphaned = find_orphaned_files(db_client, files).await?; if !orphaned.is_empty() { @@ -412,7 +392,7 @@ impl DeleteEmptyProfiles { let deletion_queue = delete_profile(db_client, &profile.id).await?; remove_media(config, deletion_queue).await; println!("profile {} deleted", profile.acct); - }; + } Ok(()) } } @@ -432,7 +412,7 @@ impl PruneRemoteEmojis { let deletion_queue = delete_emoji(db_client, &emoji_id).await?; remove_media(config, deletion_queue).await; println!("emoji {} deleted", emoji_id); - }; + } Ok(()) } } @@ -462,7 +442,7 @@ impl ListUnreachableActors { profile.unreachable_since.unwrap().to_string(), profile.updated_at.to_string(), ); - }; + } Ok(()) } } @@ -480,11 +460,8 @@ impl ImportEmoji { _config: &Config, db_client: &impl DatabaseClient, ) -> Result<(), Error> { - let emoji = get_emoji_by_name_and_hostname( - db_client, - &self.emoji_name, - &self.hostname, - ).await?; + let emoji = + get_emoji_by_name_and_hostname(db_client, &self.emoji_name, &self.hostname).await?; if emoji.image.file_size > EMOJI_LOCAL_MAX_SIZE { println!("emoji is too big"); return Ok(()); @@ -496,7 +473,8 @@ impl ImportEmoji { emoji.image, None, &get_min_datetime(), - ).await?; + ) + .await?; println!("added emoji to local collection"); Ok(()) } @@ -548,10 +526,7 @@ pub struct CreateMoneroWallet { } impl CreateMoneroWallet { - pub async fn execute( - &self, - _config: &Config, - ) -> Result<(), Error> { + pub async fn execute(&self, _config: &Config) -> Result<(), Error> { println!("wallet created"); Ok(()) } diff --git a/mitra-cli/src/main.rs b/mitra-cli/src/main.rs index 30c2221..4be67b0 100644 --- a/mitra-cli/src/main.rs +++ b/mitra-cli/src/main.rs @@ -31,6 +31,7 @@ async fn main() { match subcmd { SubCommand::GenerateInviteCode(cmd) => cmd.execute(db_client).await.unwrap(), SubCommand::ListInviteCodes(cmd) => cmd.execute(db_client).await.unwrap(), + SubCommand::CreateUser(cmd) => cmd.execute(db_client).await.unwrap(), SubCommand::SetPassword(cmd) => cmd.execute(db_client).await.unwrap(), SubCommand::SetRole(cmd) => cmd.execute(db_client).await.unwrap(), SubCommand::RefetchActor(cmd) => cmd.execute(&config, db_client).await.unwrap(), @@ -48,7 +49,7 @@ async fn main() { SubCommand::ResetSubscriptions(cmd) => cmd.execute(&config, db_client).await.unwrap(), SubCommand::CreateMoneroWallet(cmd) => cmd.execute(&config).await.unwrap(), SubCommand::CheckExpiredInvoice(cmd) => cmd.execute(&config, db_client).await.unwrap(), - _ => panic!(), + _ => unreachable!(), }; }, }; diff --git a/mitra-models/src/invoices/queries.rs b/mitra-models/src/invoices/queries.rs index 25a1885..acdc1b7 100644 --- a/mitra-models/src/invoices/queries.rs +++ b/mitra-models/src/invoices/queries.rs @@ -142,6 +142,7 @@ mod tests { let sender = create_profile(db_client, sender_data).await.unwrap(); let recipient_data = UserCreateData { username: "recipient".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; let recipient = create_user(db_client, recipient_data).await.unwrap(); diff --git a/mitra-models/src/oauth/queries.rs b/mitra-models/src/oauth/queries.rs index de39801..f035709 100644 --- a/mitra-models/src/oauth/queries.rs +++ b/mitra-models/src/oauth/queries.rs @@ -223,6 +223,7 @@ mod tests { let db_client = &mut create_test_database().await; let user_data = UserCreateData { username: "test".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; let user = create_user(db_client, user_data).await.unwrap(); @@ -248,6 +249,7 @@ mod tests { let db_client = &mut create_test_database().await; let user_data = UserCreateData { username: "test".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; let user = create_user(db_client, user_data).await.unwrap(); diff --git a/mitra-models/src/posts/helpers.rs b/mitra-models/src/posts/helpers.rs index 02716e8..d592b47 100644 --- a/mitra-models/src/posts/helpers.rs +++ b/mitra-models/src/posts/helpers.rs @@ -157,15 +157,20 @@ mod tests { }; use super::*; + async fn create_test_user(db_client: &mut Client, username: &str) -> User { + let user_data = UserCreateData { + username: username.to_string(), + password_hash: Some("test".to_string()), + ..Default::default() + }; + create_user(db_client, user_data).await.unwrap() + } + #[tokio::test] #[serial] async fn test_add_related_posts() { let db_client = &mut create_test_database().await; - let author_data = UserCreateData { - username: "test".to_string(), - ..Default::default() - }; - let author = create_user(db_client, author_data).await.unwrap(); + let author = create_test_user(db_client, "test").await; let post_data = PostCreateData { content: "post".to_string(), ..Default::default() @@ -222,14 +227,6 @@ mod tests { assert_eq!(result, true); } - async fn create_test_user(db_client: &mut Client, username: &str) -> User { - let user_data = UserCreateData { - username: username.to_string(), - ..Default::default() - }; - create_user(db_client, user_data).await.unwrap() - } - #[tokio::test] #[serial] async fn test_can_view_post_followers_only_anonymous() { diff --git a/mitra-models/src/posts/queries.rs b/mitra-models/src/posts/queries.rs index 6191839..82ebb2a 100644 --- a/mitra-models/src/posts/queries.rs +++ b/mitra-models/src/posts/queries.rs @@ -1375,6 +1375,7 @@ mod tests { let db_client = &mut create_test_database().await; let user_data = UserCreateData { username: "test".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; let user = create_user(db_client, user_data).await.unwrap(); @@ -1400,6 +1401,7 @@ mod tests { let db_client = &mut create_test_database().await; let user_data = UserCreateData { username: "test".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; let user = create_user(db_client, user_data).await.unwrap(); @@ -1419,6 +1421,7 @@ mod tests { let db_client = &mut create_test_database().await; let current_user_data = UserCreateData { username: "test".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; let current_user = create_user(db_client, current_user_data).await.unwrap(); @@ -1438,6 +1441,7 @@ mod tests { // Another user's public post let user_data_1 = UserCreateData { username: "another-user".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; let user_1 = create_user(db_client, user_data_1).await.unwrap(); @@ -1464,6 +1468,7 @@ mod tests { // Followed user's public post let user_data_2 = UserCreateData { username: "followed".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; let user_2 = create_user(db_client, user_data_2).await.unwrap(); @@ -1504,6 +1509,7 @@ mod tests { // Subscribers-only post by subscription (without mention) let user_data_3 = UserCreateData { username: "subscription".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; let user_3 = create_user(db_client, user_data_3).await.unwrap(); @@ -1525,6 +1531,7 @@ mod tests { // Repost from followed user if hiding reposts let user_data_4 = UserCreateData { username: "hide reposts".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; let user_4 = create_user(db_client, user_data_4).await.unwrap(); @@ -1559,6 +1566,7 @@ mod tests { let db_client = &mut create_test_database().await; let user_data = UserCreateData { username: "test".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; let user = create_user(db_client, user_data).await.unwrap(); @@ -1622,6 +1630,7 @@ mod tests { let db_client = &mut create_test_database().await; let user_data = UserCreateData { username: "test".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; let user = create_user(db_client, user_data).await.unwrap(); diff --git a/mitra-models/src/relationships/queries.rs b/mitra-models/src/relationships/queries.rs index d07285c..b82d935 100644 --- a/mitra-models/src/relationships/queries.rs +++ b/mitra-models/src/relationships/queries.rs @@ -596,6 +596,7 @@ mod tests { let db_client = &mut create_test_database().await; let source_data = UserCreateData { username: "test".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; let source = create_user(db_client, source_data).await.unwrap(); @@ -657,6 +658,7 @@ mod tests { let source = create_profile(db_client, source_data).await.unwrap(); let target_data = UserCreateData { username: "test".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; let target = create_user(db_client, target_data).await.unwrap(); diff --git a/mitra-models/src/subscriptions/queries.rs b/mitra-models/src/subscriptions/queries.rs index 7c3a626..3b82f95 100644 --- a/mitra-models/src/subscriptions/queries.rs +++ b/mitra-models/src/subscriptions/queries.rs @@ -222,6 +222,7 @@ mod tests { let sender_address = "0xb9c5714089478a327f09197987f16f9e5d936e8a"; let recipient_data = UserCreateData { username: "recipient".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; let recipient = create_user(db_client, recipient_data).await.unwrap(); diff --git a/mitra-models/src/users/queries.rs b/mitra-models/src/users/queries.rs index 7099136..6227a39 100644 --- a/mitra-models/src/users/queries.rs +++ b/mitra-models/src/users/queries.rs @@ -78,6 +78,8 @@ pub async fn create_user( db_client: &mut impl DatabaseClient, user_data: UserCreateData, ) -> Result { + assert!(user_data.password_hash.is_some() || + user_data.wallet_address.is_some()); let mut transaction = db_client.transaction().await?; // Prevent changes to actor_profile table transaction.execute( @@ -351,6 +353,7 @@ mod tests { let db_client = &mut create_test_database().await; let user_data = UserCreateData { username: "myname".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; let user = create_user(db_client, user_data).await.unwrap(); @@ -364,11 +367,13 @@ mod tests { let db_client = &mut create_test_database().await; let user_data = UserCreateData { username: "myname".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; create_user(db_client, user_data).await.unwrap(); let another_user_data = UserCreateData { username: "myName".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; let result = create_user(db_client, another_user_data).await; @@ -379,7 +384,11 @@ mod tests { #[serial] async fn test_set_user_role() { let db_client = &mut create_test_database().await; - let user_data = UserCreateData::default(); + let user_data = UserCreateData { + username: "test".to_string(), + password_hash: Some("test".to_string()), + ..Default::default() + }; let user = create_user(db_client, user_data).await.unwrap(); assert_eq!(user.role, Role::NormalUser); set_user_role(db_client, &user.id, Role::ReadOnlyUser).await.unwrap(); @@ -391,7 +400,11 @@ mod tests { #[serial] async fn test_update_client_config() { let db_client = &mut create_test_database().await; - let user_data = UserCreateData::default(); + let user_data = UserCreateData { + username: "test".to_string(), + password_hash: Some("test".to_string()), + ..Default::default() + }; let user = create_user(db_client, user_data).await.unwrap(); assert_eq!(user.client_config.is_empty(), true); let client_name = "test"; diff --git a/src/admin/mod.rs b/src/admin/mod.rs new file mode 100644 index 0000000..ad3e620 --- /dev/null +++ b/src/admin/mod.rs @@ -0,0 +1 @@ +pub mod roles; diff --git a/src/admin/roles.rs b/src/admin/roles.rs new file mode 100644 index 0000000..1414c79 --- /dev/null +++ b/src/admin/roles.rs @@ -0,0 +1,15 @@ +use mitra_models::users::types::Role; + +use crate::errors::ValidationError; + +pub const ALLOWED_ROLES: [&str; 3] = ["admin", "user", "read_only_user"]; + +pub fn role_from_str(role_str: &str) -> Result { + let role = match role_str { + "user" => Role::NormalUser, + "admin" => Role::Admin, + "read_only_user" => Role::ReadOnlyUser, + _ => return Err(ValidationError("unknown role")), + }; + Ok(role) +} diff --git a/src/lib.rs b/src/lib.rs index 271265b..e25cbb4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod activitypub; +pub mod admin; pub mod atom; mod errors; pub mod http; diff --git a/src/mastodon_api/accounts/helpers.rs b/src/mastodon_api/accounts/helpers.rs index 9461056..9cca9ff 100644 --- a/src/mastodon_api/accounts/helpers.rs +++ b/src/mastodon_api/accounts/helpers.rs @@ -110,11 +110,13 @@ mod tests { { let user_data_1 = UserCreateData { username: "user".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; let user_1 = create_user(db_client, user_data_1).await.unwrap(); let user_data_2 = UserCreateData { username: "another-user".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; let user_2 = create_user(db_client, user_data_2).await.unwrap(); diff --git a/src/mastodon_api/accounts/types.rs b/src/mastodon_api/accounts/types.rs index 200bc41..1a97abd 100644 --- a/src/mastodon_api/accounts/types.rs +++ b/src/mastodon_api/accounts/types.rs @@ -37,10 +37,6 @@ use crate::mastodon_api::{ uploads::{save_b64_file, UploadError}, }; use crate::media::get_file_url; -use crate::validators::{ - profiles::validate_username, - users::validate_local_username, -}; /// https://docs.joinmastodon.org/entities/field/ #[derive(Serialize)] @@ -268,18 +264,6 @@ pub struct AccountCreateData { pub invite_code: Option, } -impl AccountCreateData { - - pub fn clean(&self) -> Result<(), ValidationError> { - validate_username(&self.username)?; - validate_local_username(&self.username)?; - if self.password.is_none() && self.message.is_none() { - return Err(ValidationError("password or EIP-4361 message is required")); - }; - Ok(()) - } -} - #[derive(Deserialize)] struct AccountFieldSource { name: String, @@ -563,19 +547,6 @@ mod tests { const INSTANCE_URL: &str = "https://example.com"; - #[test] - fn test_validate_account_create_data() { - let account_data = AccountCreateData { - username: "test".to_string(), - password: None, - message: None, - signature: Some("test".to_string()), - invite_code: None, - }; - let error = account_data.clean().unwrap_err(); - assert_eq!(error.to_string(), "password or EIP-4361 message is required"); - } - #[test] fn test_create_account_from_profile() { let profile = DbActorProfile { diff --git a/src/mastodon_api/accounts/views.rs b/src/mastodon_api/accounts/views.rs index a50a957..c7a6267 100644 --- a/src/mastodon_api/accounts/views.rs +++ b/src/mastodon_api/accounts/views.rs @@ -94,7 +94,10 @@ use crate::mastodon_api::{ statuses::helpers::build_status_list, statuses::types::Status, }; -use crate::validators::profiles::clean_profile_update_data; +use crate::validators::{ + profiles::clean_profile_update_data, + users::validate_local_username, +}; use super::helpers::{ get_aliases, get_relationship, @@ -128,7 +131,6 @@ pub async fn create_account( ) -> Result { let db_client = &mut **get_database_client(&db_pool).await?; // Validate - account_data.clean()?; if config.registration.registration_type == RegistrationType::Invite { let invite_code = account_data.invite_code.as_ref() .ok_or(ValidationError("invite code is required"))?; @@ -137,6 +139,10 @@ pub async fn create_account( }; }; + validate_local_username(&account_data.username)?; + if account_data.password.is_none() && account_data.message.is_none() { + return Err(ValidationError("password or EIP-4361 message is required").into()); + }; let maybe_password_hash = if let Some(password) = account_data.password.as_ref() { let password_hash = hash_password(password) .map_err(|_| MastodonError::InternalError)?; diff --git a/src/validators/users.rs b/src/validators/users.rs index 8e29e72..74bef54 100644 --- a/src/validators/users.rs +++ b/src/validators/users.rs @@ -2,7 +2,10 @@ use regex::Regex; use crate::errors::ValidationError; +use super::profiles::validate_username; + pub fn validate_local_username(username: &str) -> Result<(), ValidationError> { + validate_username(username)?; // The username regexp should not allow domain names and IP addresses let username_regexp = Regex::new(r"^[a-z0-9_]+$").unwrap(); if !username_regexp.is_match(username) { diff --git a/src/webfinger/views.rs b/src/webfinger/views.rs index aeb4a79..33dbb92 100644 --- a/src/webfinger/views.rs +++ b/src/webfinger/views.rs @@ -125,6 +125,7 @@ mod tests { let instance = Instance::for_test("https://example.com"); let user_data = UserCreateData { username: "test".to_string(), + password_hash: Some("test".to_string()), ..Default::default() }; create_user(db_client, user_data).await.unwrap();