Add create-user command
This commit is contained in:
parent
533ef48393
commit
8daf566eb2
19 changed files with 153 additions and 141 deletions
|
@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Support Monero Wallet RPC authentication.
|
||||||
|
- Added `create-user` command.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Added emoji count check to profile data validator.
|
- Added emoji count check to profile data validator.
|
||||||
|
|
|
@ -32,6 +32,12 @@ List generated invites:
|
||||||
mitractl list-invite-codes
|
mitractl list-invite-codes
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Create user:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
mitractl create-user <username> <password> <role-name>
|
||||||
|
```
|
||||||
|
|
||||||
Set or change password:
|
Set or change password:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|
|
@ -1,19 +1,14 @@
|
||||||
use anyhow::{anyhow, Error};
|
use anyhow::Error;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use mitra::activitypub::{
|
use mitra::activitypub::{
|
||||||
actors::helpers::update_remote_profile,
|
actors::helpers::update_remote_profile, builders::delete_note::prepare_delete_note,
|
||||||
builders::delete_note::prepare_delete_note,
|
builders::delete_person::prepare_delete_person, fetcher::fetchers::fetch_actor,
|
||||||
builders::delete_person::prepare_delete_person,
|
|
||||||
fetcher::fetchers::fetch_actor,
|
|
||||||
};
|
};
|
||||||
use mitra::media::{
|
use mitra::admin::roles::{role_from_str, ALLOWED_ROLES};
|
||||||
remove_files,
|
use mitra::media::{remove_files, remove_media, MediaStorage};
|
||||||
remove_media,
|
use mitra::validators::{emojis::EMOJI_LOCAL_MAX_SIZE, users::validate_local_username};
|
||||||
MediaStorage,
|
|
||||||
};
|
|
||||||
use mitra::validators::emojis::EMOJI_LOCAL_MAX_SIZE;
|
|
||||||
use mitra_config::Config;
|
use mitra_config::Config;
|
||||||
use mitra_models::{
|
use mitra_models::{
|
||||||
attachments::queries::delete_unused_attachments,
|
attachments::queries::delete_unused_attachments,
|
||||||
|
@ -21,35 +16,23 @@ use mitra_models::{
|
||||||
database::DatabaseClient,
|
database::DatabaseClient,
|
||||||
emojis::helpers::get_emoji_by_name,
|
emojis::helpers::get_emoji_by_name,
|
||||||
emojis::queries::{
|
emojis::queries::{
|
||||||
create_emoji,
|
create_emoji, delete_emoji, find_unused_remote_emojis, get_emoji_by_name_and_hostname,
|
||||||
delete_emoji,
|
|
||||||
find_unused_remote_emojis,
|
|
||||||
get_emoji_by_name_and_hostname,
|
|
||||||
},
|
},
|
||||||
oauth::queries::delete_oauth_tokens,
|
oauth::queries::delete_oauth_tokens,
|
||||||
posts::queries::{delete_post, find_extraneous_posts, get_post_by_id},
|
posts::queries::{delete_post, find_extraneous_posts, get_post_by_id},
|
||||||
profiles::queries::{
|
profiles::queries::{
|
||||||
delete_profile,
|
delete_profile, find_empty_profiles, find_unreachable, get_profile_by_id,
|
||||||
find_empty_profiles,
|
|
||||||
find_unreachable,
|
|
||||||
get_profile_by_id,
|
|
||||||
get_profile_by_remote_actor_id,
|
get_profile_by_remote_actor_id,
|
||||||
},
|
},
|
||||||
subscriptions::queries::reset_subscriptions,
|
subscriptions::queries::reset_subscriptions,
|
||||||
users::queries::{
|
users::queries::{
|
||||||
create_invite_code,
|
create_invite_code, create_user, get_invite_codes, get_user_by_id, set_user_password,
|
||||||
get_invite_codes,
|
|
||||||
get_user_by_id,
|
|
||||||
set_user_password,
|
|
||||||
set_user_role,
|
set_user_role,
|
||||||
},
|
},
|
||||||
users::types::Role,
|
users::types::UserCreateData,
|
||||||
};
|
};
|
||||||
use mitra_utils::{
|
use mitra_utils::{
|
||||||
crypto_rsa::{
|
crypto_rsa::{generate_rsa_key, serialize_private_key},
|
||||||
generate_rsa_key,
|
|
||||||
serialize_private_key,
|
|
||||||
},
|
|
||||||
datetime::{days_before_now, get_min_datetime},
|
datetime::{days_before_now, get_min_datetime},
|
||||||
passwords::hash_password,
|
passwords::hash_password,
|
||||||
};
|
};
|
||||||
|
@ -68,6 +51,7 @@ pub enum SubCommand {
|
||||||
|
|
||||||
GenerateInviteCode(GenerateInviteCode),
|
GenerateInviteCode(GenerateInviteCode),
|
||||||
ListInviteCodes(ListInviteCodes),
|
ListInviteCodes(ListInviteCodes),
|
||||||
|
CreateUser(CreateUser),
|
||||||
SetPassword(SetPassword),
|
SetPassword(SetPassword),
|
||||||
SetRole(SetRole),
|
SetRole(SetRole),
|
||||||
RefetchActor(RefetchActor),
|
RefetchActor(RefetchActor),
|
||||||
|
@ -116,14 +100,8 @@ pub struct GenerateInviteCode {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GenerateInviteCode {
|
impl GenerateInviteCode {
|
||||||
pub async fn execute(
|
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
|
||||||
&self,
|
let invite_code = create_invite_code(db_client, self.note.as_deref()).await?;
|
||||||
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);
|
println!("generated invite code: {}", invite_code);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -134,10 +112,7 @@ impl GenerateInviteCode {
|
||||||
pub struct ListInviteCodes;
|
pub struct ListInviteCodes;
|
||||||
|
|
||||||
impl ListInviteCodes {
|
impl ListInviteCodes {
|
||||||
pub async fn execute(
|
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
|
||||||
&self,
|
|
||||||
db_client: &impl DatabaseClient,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let invite_codes = get_invite_codes(db_client).await?;
|
let invite_codes = get_invite_codes(db_client).await?;
|
||||||
if invite_codes.is_empty() {
|
if invite_codes.is_empty() {
|
||||||
println!("no invite codes found");
|
println!("no invite codes found");
|
||||||
|
@ -149,7 +124,37 @@ impl ListInviteCodes {
|
||||||
} else {
|
} else {
|
||||||
println!("{}", invite_code.code);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,10 +167,7 @@ pub struct SetPassword {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SetPassword {
|
impl SetPassword {
|
||||||
pub async fn execute(
|
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
|
||||||
&self,
|
|
||||||
db_client: &impl DatabaseClient,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let password_hash = hash_password(&self.password)?;
|
let password_hash = hash_password(&self.password)?;
|
||||||
set_user_password(db_client, &self.id, password_hash).await?;
|
set_user_password(db_client, &self.id, password_hash).await?;
|
||||||
// Revoke all sessions
|
// Revoke all sessions
|
||||||
|
@ -179,21 +181,13 @@ impl SetPassword {
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
pub struct SetRole {
|
pub struct SetRole {
|
||||||
id: Uuid,
|
id: Uuid,
|
||||||
#[clap(value_parser = ["admin", "user", "read_only_user"])]
|
#[clap(value_parser = ALLOWED_ROLES)]
|
||||||
role: String,
|
role: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SetRole {
|
impl SetRole {
|
||||||
pub async fn execute(
|
pub async fn execute(&self, db_client: &impl DatabaseClient) -> Result<(), Error> {
|
||||||
&self,
|
let role = role_from_str(&self.role)?;
|
||||||
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")),
|
|
||||||
};
|
|
||||||
set_user_role(db_client, &self.id, role).await?;
|
set_user_role(db_client, &self.id, role).await?;
|
||||||
println!("role changed");
|
println!("role changed");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -212,10 +206,7 @@ impl RefetchActor {
|
||||||
config: &Config,
|
config: &Config,
|
||||||
db_client: &mut impl DatabaseClient,
|
db_client: &mut impl DatabaseClient,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let profile = get_profile_by_remote_actor_id(
|
let profile = get_profile_by_remote_actor_id(db_client, &self.id).await?;
|
||||||
db_client,
|
|
||||||
&self.id,
|
|
||||||
).await?;
|
|
||||||
let actor = fetch_actor(&config.instance(), &self.id).await?;
|
let actor = fetch_actor(&config.instance(), &self.id).await?;
|
||||||
update_remote_profile(
|
update_remote_profile(
|
||||||
db_client,
|
db_client,
|
||||||
|
@ -223,7 +214,8 @@ impl RefetchActor {
|
||||||
&MediaStorage::from(config),
|
&MediaStorage::from(config),
|
||||||
profile,
|
profile,
|
||||||
actor,
|
actor,
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
println!("profile updated");
|
println!("profile updated");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -245,8 +237,7 @@ impl DeleteProfile {
|
||||||
let mut maybe_delete_person = None;
|
let mut maybe_delete_person = None;
|
||||||
if profile.is_local() {
|
if profile.is_local() {
|
||||||
let user = get_user_by_id(db_client, &profile.id).await?;
|
let user = get_user_by_id(db_client, &profile.id).await?;
|
||||||
let activity =
|
let activity = prepare_delete_person(db_client, &config.instance(), &user).await?;
|
||||||
prepare_delete_person(db_client, &config.instance(), &user).await?;
|
|
||||||
maybe_delete_person = Some(activity);
|
maybe_delete_person = Some(activity);
|
||||||
};
|
};
|
||||||
let deletion_queue = delete_profile(db_client, &profile.id).await?;
|
let deletion_queue = delete_profile(db_client, &profile.id).await?;
|
||||||
|
@ -276,12 +267,8 @@ impl DeletePost {
|
||||||
let mut maybe_delete_note = None;
|
let mut maybe_delete_note = None;
|
||||||
if post.author.is_local() {
|
if post.author.is_local() {
|
||||||
let author = get_user_by_id(db_client, &post.author.id).await?;
|
let author = get_user_by_id(db_client, &post.author.id).await?;
|
||||||
let activity = prepare_delete_note(
|
let activity =
|
||||||
db_client,
|
prepare_delete_note(db_client, &config.instance(), &author, &post).await?;
|
||||||
&config.instance(),
|
|
||||||
&author,
|
|
||||||
&post,
|
|
||||||
).await?;
|
|
||||||
maybe_delete_note = Some(activity);
|
maybe_delete_note = Some(activity);
|
||||||
};
|
};
|
||||||
let deletion_queue = delete_post(db_client, &post.id).await?;
|
let deletion_queue = delete_post(db_client, &post.id).await?;
|
||||||
|
@ -308,11 +295,8 @@ impl DeleteEmoji {
|
||||||
config: &Config,
|
config: &Config,
|
||||||
db_client: &impl DatabaseClient,
|
db_client: &impl DatabaseClient,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let emoji = get_emoji_by_name(
|
let emoji =
|
||||||
db_client,
|
get_emoji_by_name(db_client, &self.emoji_name, self.hostname.as_deref()).await?;
|
||||||
&self.emoji_name,
|
|
||||||
self.hostname.as_deref(),
|
|
||||||
).await?;
|
|
||||||
let deletion_queue = delete_emoji(db_client, &emoji.id).await?;
|
let deletion_queue = delete_emoji(db_client, &emoji.id).await?;
|
||||||
remove_media(config, deletion_queue).await;
|
remove_media(config, deletion_queue).await;
|
||||||
println!("emoji deleted");
|
println!("emoji deleted");
|
||||||
|
@ -338,7 +322,7 @@ impl DeleteExtraneousPosts {
|
||||||
let deletion_queue = delete_post(db_client, &post_id).await?;
|
let deletion_queue = delete_post(db_client, &post_id).await?;
|
||||||
remove_media(config, deletion_queue).await;
|
remove_media(config, deletion_queue).await;
|
||||||
println!("post {} deleted", post_id);
|
println!("post {} deleted", post_id);
|
||||||
};
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -356,10 +340,7 @@ impl DeleteUnusedAttachments {
|
||||||
db_client: &impl DatabaseClient,
|
db_client: &impl DatabaseClient,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let created_before = days_before_now(self.days);
|
let created_before = days_before_now(self.days);
|
||||||
let deletion_queue = delete_unused_attachments(
|
let deletion_queue = delete_unused_attachments(db_client, &created_before).await?;
|
||||||
db_client,
|
|
||||||
&created_before,
|
|
||||||
).await?;
|
|
||||||
remove_media(config, deletion_queue).await;
|
remove_media(config, deletion_queue).await;
|
||||||
println!("unused attachments deleted");
|
println!("unused attachments deleted");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -379,10 +360,9 @@ impl DeleteOrphanedFiles {
|
||||||
let media_dir = config.media_dir();
|
let media_dir = config.media_dir();
|
||||||
let mut files = vec![];
|
let mut files = vec![];
|
||||||
for maybe_path in std::fs::read_dir(&media_dir)? {
|
for maybe_path in std::fs::read_dir(&media_dir)? {
|
||||||
let file_name = maybe_path?.file_name()
|
let file_name = maybe_path?.file_name().to_string_lossy().to_string();
|
||||||
.to_string_lossy().to_string();
|
|
||||||
files.push(file_name);
|
files.push(file_name);
|
||||||
};
|
}
|
||||||
println!("found {} files", files.len());
|
println!("found {} files", files.len());
|
||||||
let orphaned = find_orphaned_files(db_client, files).await?;
|
let orphaned = find_orphaned_files(db_client, files).await?;
|
||||||
if !orphaned.is_empty() {
|
if !orphaned.is_empty() {
|
||||||
|
@ -412,7 +392,7 @@ impl DeleteEmptyProfiles {
|
||||||
let deletion_queue = delete_profile(db_client, &profile.id).await?;
|
let deletion_queue = delete_profile(db_client, &profile.id).await?;
|
||||||
remove_media(config, deletion_queue).await;
|
remove_media(config, deletion_queue).await;
|
||||||
println!("profile {} deleted", profile.acct);
|
println!("profile {} deleted", profile.acct);
|
||||||
};
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -432,7 +412,7 @@ impl PruneRemoteEmojis {
|
||||||
let deletion_queue = delete_emoji(db_client, &emoji_id).await?;
|
let deletion_queue = delete_emoji(db_client, &emoji_id).await?;
|
||||||
remove_media(config, deletion_queue).await;
|
remove_media(config, deletion_queue).await;
|
||||||
println!("emoji {} deleted", emoji_id);
|
println!("emoji {} deleted", emoji_id);
|
||||||
};
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -462,7 +442,7 @@ impl ListUnreachableActors {
|
||||||
profile.unreachable_since.unwrap().to_string(),
|
profile.unreachable_since.unwrap().to_string(),
|
||||||
profile.updated_at.to_string(),
|
profile.updated_at.to_string(),
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -480,11 +460,8 @@ impl ImportEmoji {
|
||||||
_config: &Config,
|
_config: &Config,
|
||||||
db_client: &impl DatabaseClient,
|
db_client: &impl DatabaseClient,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let emoji = get_emoji_by_name_and_hostname(
|
let emoji =
|
||||||
db_client,
|
get_emoji_by_name_and_hostname(db_client, &self.emoji_name, &self.hostname).await?;
|
||||||
&self.emoji_name,
|
|
||||||
&self.hostname,
|
|
||||||
).await?;
|
|
||||||
if emoji.image.file_size > EMOJI_LOCAL_MAX_SIZE {
|
if emoji.image.file_size > EMOJI_LOCAL_MAX_SIZE {
|
||||||
println!("emoji is too big");
|
println!("emoji is too big");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
@ -496,7 +473,8 @@ impl ImportEmoji {
|
||||||
emoji.image,
|
emoji.image,
|
||||||
None,
|
None,
|
||||||
&get_min_datetime(),
|
&get_min_datetime(),
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
println!("added emoji to local collection");
|
println!("added emoji to local collection");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -548,10 +526,7 @@ pub struct CreateMoneroWallet {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateMoneroWallet {
|
impl CreateMoneroWallet {
|
||||||
pub async fn execute(
|
pub async fn execute(&self, _config: &Config) -> Result<(), Error> {
|
||||||
&self,
|
|
||||||
_config: &Config,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
println!("wallet created");
|
println!("wallet created");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ async fn main() {
|
||||||
match subcmd {
|
match subcmd {
|
||||||
SubCommand::GenerateInviteCode(cmd) => cmd.execute(db_client).await.unwrap(),
|
SubCommand::GenerateInviteCode(cmd) => cmd.execute(db_client).await.unwrap(),
|
||||||
SubCommand::ListInviteCodes(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::SetPassword(cmd) => cmd.execute(db_client).await.unwrap(),
|
||||||
SubCommand::SetRole(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(),
|
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::ResetSubscriptions(cmd) => cmd.execute(&config, db_client).await.unwrap(),
|
||||||
SubCommand::CreateMoneroWallet(cmd) => cmd.execute(&config).await.unwrap(),
|
SubCommand::CreateMoneroWallet(cmd) => cmd.execute(&config).await.unwrap(),
|
||||||
SubCommand::CheckExpiredInvoice(cmd) => cmd.execute(&config, db_client).await.unwrap(),
|
SubCommand::CheckExpiredInvoice(cmd) => cmd.execute(&config, db_client).await.unwrap(),
|
||||||
_ => panic!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -142,6 +142,7 @@ mod tests {
|
||||||
let sender = create_profile(db_client, sender_data).await.unwrap();
|
let sender = create_profile(db_client, sender_data).await.unwrap();
|
||||||
let recipient_data = UserCreateData {
|
let recipient_data = UserCreateData {
|
||||||
username: "recipient".to_string(),
|
username: "recipient".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let recipient = create_user(db_client, recipient_data).await.unwrap();
|
let recipient = create_user(db_client, recipient_data).await.unwrap();
|
||||||
|
|
|
@ -223,6 +223,7 @@ mod tests {
|
||||||
let db_client = &mut create_test_database().await;
|
let db_client = &mut create_test_database().await;
|
||||||
let user_data = UserCreateData {
|
let user_data = UserCreateData {
|
||||||
username: "test".to_string(),
|
username: "test".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let user = create_user(db_client, user_data).await.unwrap();
|
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 db_client = &mut create_test_database().await;
|
||||||
let user_data = UserCreateData {
|
let user_data = UserCreateData {
|
||||||
username: "test".to_string(),
|
username: "test".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let user = create_user(db_client, user_data).await.unwrap();
|
let user = create_user(db_client, user_data).await.unwrap();
|
||||||
|
|
|
@ -157,15 +157,20 @@ mod tests {
|
||||||
};
|
};
|
||||||
use super::*;
|
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]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn test_add_related_posts() {
|
async fn test_add_related_posts() {
|
||||||
let db_client = &mut create_test_database().await;
|
let db_client = &mut create_test_database().await;
|
||||||
let author_data = UserCreateData {
|
let author = create_test_user(db_client, "test").await;
|
||||||
username: "test".to_string(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let author = create_user(db_client, author_data).await.unwrap();
|
|
||||||
let post_data = PostCreateData {
|
let post_data = PostCreateData {
|
||||||
content: "post".to_string(),
|
content: "post".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -222,14 +227,6 @@ mod tests {
|
||||||
assert_eq!(result, true);
|
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]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn test_can_view_post_followers_only_anonymous() {
|
async fn test_can_view_post_followers_only_anonymous() {
|
||||||
|
|
|
@ -1375,6 +1375,7 @@ mod tests {
|
||||||
let db_client = &mut create_test_database().await;
|
let db_client = &mut create_test_database().await;
|
||||||
let user_data = UserCreateData {
|
let user_data = UserCreateData {
|
||||||
username: "test".to_string(),
|
username: "test".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let user = create_user(db_client, user_data).await.unwrap();
|
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 db_client = &mut create_test_database().await;
|
||||||
let user_data = UserCreateData {
|
let user_data = UserCreateData {
|
||||||
username: "test".to_string(),
|
username: "test".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let user = create_user(db_client, user_data).await.unwrap();
|
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 db_client = &mut create_test_database().await;
|
||||||
let current_user_data = UserCreateData {
|
let current_user_data = UserCreateData {
|
||||||
username: "test".to_string(),
|
username: "test".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let current_user = create_user(db_client, current_user_data).await.unwrap();
|
let current_user = create_user(db_client, current_user_data).await.unwrap();
|
||||||
|
@ -1438,6 +1441,7 @@ mod tests {
|
||||||
// Another user's public post
|
// Another user's public post
|
||||||
let user_data_1 = UserCreateData {
|
let user_data_1 = UserCreateData {
|
||||||
username: "another-user".to_string(),
|
username: "another-user".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let user_1 = create_user(db_client, user_data_1).await.unwrap();
|
let user_1 = create_user(db_client, user_data_1).await.unwrap();
|
||||||
|
@ -1464,6 +1468,7 @@ mod tests {
|
||||||
// Followed user's public post
|
// Followed user's public post
|
||||||
let user_data_2 = UserCreateData {
|
let user_data_2 = UserCreateData {
|
||||||
username: "followed".to_string(),
|
username: "followed".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let user_2 = create_user(db_client, user_data_2).await.unwrap();
|
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)
|
// Subscribers-only post by subscription (without mention)
|
||||||
let user_data_3 = UserCreateData {
|
let user_data_3 = UserCreateData {
|
||||||
username: "subscription".to_string(),
|
username: "subscription".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let user_3 = create_user(db_client, user_data_3).await.unwrap();
|
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
|
// Repost from followed user if hiding reposts
|
||||||
let user_data_4 = UserCreateData {
|
let user_data_4 = UserCreateData {
|
||||||
username: "hide reposts".to_string(),
|
username: "hide reposts".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let user_4 = create_user(db_client, user_data_4).await.unwrap();
|
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 db_client = &mut create_test_database().await;
|
||||||
let user_data = UserCreateData {
|
let user_data = UserCreateData {
|
||||||
username: "test".to_string(),
|
username: "test".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let user = create_user(db_client, user_data).await.unwrap();
|
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 db_client = &mut create_test_database().await;
|
||||||
let user_data = UserCreateData {
|
let user_data = UserCreateData {
|
||||||
username: "test".to_string(),
|
username: "test".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let user = create_user(db_client, user_data).await.unwrap();
|
let user = create_user(db_client, user_data).await.unwrap();
|
||||||
|
|
|
@ -596,6 +596,7 @@ mod tests {
|
||||||
let db_client = &mut create_test_database().await;
|
let db_client = &mut create_test_database().await;
|
||||||
let source_data = UserCreateData {
|
let source_data = UserCreateData {
|
||||||
username: "test".to_string(),
|
username: "test".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let source = create_user(db_client, source_data).await.unwrap();
|
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 source = create_profile(db_client, source_data).await.unwrap();
|
||||||
let target_data = UserCreateData {
|
let target_data = UserCreateData {
|
||||||
username: "test".to_string(),
|
username: "test".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let target = create_user(db_client, target_data).await.unwrap();
|
let target = create_user(db_client, target_data).await.unwrap();
|
||||||
|
|
|
@ -222,6 +222,7 @@ mod tests {
|
||||||
let sender_address = "0xb9c5714089478a327f09197987f16f9e5d936e8a";
|
let sender_address = "0xb9c5714089478a327f09197987f16f9e5d936e8a";
|
||||||
let recipient_data = UserCreateData {
|
let recipient_data = UserCreateData {
|
||||||
username: "recipient".to_string(),
|
username: "recipient".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let recipient = create_user(db_client, recipient_data).await.unwrap();
|
let recipient = create_user(db_client, recipient_data).await.unwrap();
|
||||||
|
|
|
@ -78,6 +78,8 @@ pub async fn create_user(
|
||||||
db_client: &mut impl DatabaseClient,
|
db_client: &mut impl DatabaseClient,
|
||||||
user_data: UserCreateData,
|
user_data: UserCreateData,
|
||||||
) -> Result<User, DatabaseError> {
|
) -> Result<User, DatabaseError> {
|
||||||
|
assert!(user_data.password_hash.is_some() ||
|
||||||
|
user_data.wallet_address.is_some());
|
||||||
let mut transaction = db_client.transaction().await?;
|
let mut transaction = db_client.transaction().await?;
|
||||||
// Prevent changes to actor_profile table
|
// Prevent changes to actor_profile table
|
||||||
transaction.execute(
|
transaction.execute(
|
||||||
|
@ -351,6 +353,7 @@ mod tests {
|
||||||
let db_client = &mut create_test_database().await;
|
let db_client = &mut create_test_database().await;
|
||||||
let user_data = UserCreateData {
|
let user_data = UserCreateData {
|
||||||
username: "myname".to_string(),
|
username: "myname".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let user = create_user(db_client, user_data).await.unwrap();
|
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 db_client = &mut create_test_database().await;
|
||||||
let user_data = UserCreateData {
|
let user_data = UserCreateData {
|
||||||
username: "myname".to_string(),
|
username: "myname".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
create_user(db_client, user_data).await.unwrap();
|
create_user(db_client, user_data).await.unwrap();
|
||||||
let another_user_data = UserCreateData {
|
let another_user_data = UserCreateData {
|
||||||
username: "myName".to_string(),
|
username: "myName".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let result = create_user(db_client, another_user_data).await;
|
let result = create_user(db_client, another_user_data).await;
|
||||||
|
@ -379,7 +384,11 @@ mod tests {
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn test_set_user_role() {
|
async fn test_set_user_role() {
|
||||||
let db_client = &mut create_test_database().await;
|
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();
|
let user = create_user(db_client, user_data).await.unwrap();
|
||||||
assert_eq!(user.role, Role::NormalUser);
|
assert_eq!(user.role, Role::NormalUser);
|
||||||
set_user_role(db_client, &user.id, Role::ReadOnlyUser).await.unwrap();
|
set_user_role(db_client, &user.id, Role::ReadOnlyUser).await.unwrap();
|
||||||
|
@ -391,7 +400,11 @@ mod tests {
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn test_update_client_config() {
|
async fn test_update_client_config() {
|
||||||
let db_client = &mut create_test_database().await;
|
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();
|
let user = create_user(db_client, user_data).await.unwrap();
|
||||||
assert_eq!(user.client_config.is_empty(), true);
|
assert_eq!(user.client_config.is_empty(), true);
|
||||||
let client_name = "test";
|
let client_name = "test";
|
||||||
|
|
1
src/admin/mod.rs
Normal file
1
src/admin/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod roles;
|
15
src/admin/roles.rs
Normal file
15
src/admin/roles.rs
Normal file
|
@ -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<Role, ValidationError> {
|
||||||
|
let role = match role_str {
|
||||||
|
"user" => Role::NormalUser,
|
||||||
|
"admin" => Role::Admin,
|
||||||
|
"read_only_user" => Role::ReadOnlyUser,
|
||||||
|
_ => return Err(ValidationError("unknown role")),
|
||||||
|
};
|
||||||
|
Ok(role)
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod activitypub;
|
pub mod activitypub;
|
||||||
|
pub mod admin;
|
||||||
pub mod atom;
|
pub mod atom;
|
||||||
mod errors;
|
mod errors;
|
||||||
pub mod http;
|
pub mod http;
|
||||||
|
|
|
@ -110,11 +110,13 @@ mod tests {
|
||||||
{
|
{
|
||||||
let user_data_1 = UserCreateData {
|
let user_data_1 = UserCreateData {
|
||||||
username: "user".to_string(),
|
username: "user".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let user_1 = create_user(db_client, user_data_1).await.unwrap();
|
let user_1 = create_user(db_client, user_data_1).await.unwrap();
|
||||||
let user_data_2 = UserCreateData {
|
let user_data_2 = UserCreateData {
|
||||||
username: "another-user".to_string(),
|
username: "another-user".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let user_2 = create_user(db_client, user_data_2).await.unwrap();
|
let user_2 = create_user(db_client, user_data_2).await.unwrap();
|
||||||
|
|
|
@ -37,10 +37,6 @@ use crate::mastodon_api::{
|
||||||
uploads::{save_b64_file, UploadError},
|
uploads::{save_b64_file, UploadError},
|
||||||
};
|
};
|
||||||
use crate::media::get_file_url;
|
use crate::media::get_file_url;
|
||||||
use crate::validators::{
|
|
||||||
profiles::validate_username,
|
|
||||||
users::validate_local_username,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// https://docs.joinmastodon.org/entities/field/
|
/// https://docs.joinmastodon.org/entities/field/
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
@ -268,18 +264,6 @@ pub struct AccountCreateData {
|
||||||
pub invite_code: Option<String>,
|
pub invite_code: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
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)]
|
#[derive(Deserialize)]
|
||||||
struct AccountFieldSource {
|
struct AccountFieldSource {
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -563,19 +547,6 @@ mod tests {
|
||||||
|
|
||||||
const INSTANCE_URL: &str = "https://example.com";
|
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]
|
#[test]
|
||||||
fn test_create_account_from_profile() {
|
fn test_create_account_from_profile() {
|
||||||
let profile = DbActorProfile {
|
let profile = DbActorProfile {
|
||||||
|
|
|
@ -94,7 +94,10 @@ use crate::mastodon_api::{
|
||||||
statuses::helpers::build_status_list,
|
statuses::helpers::build_status_list,
|
||||||
statuses::types::Status,
|
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::{
|
use super::helpers::{
|
||||||
get_aliases,
|
get_aliases,
|
||||||
get_relationship,
|
get_relationship,
|
||||||
|
@ -128,7 +131,6 @@ pub async fn create_account(
|
||||||
) -> Result<HttpResponse, MastodonError> {
|
) -> Result<HttpResponse, MastodonError> {
|
||||||
let db_client = &mut **get_database_client(&db_pool).await?;
|
let db_client = &mut **get_database_client(&db_pool).await?;
|
||||||
// Validate
|
// Validate
|
||||||
account_data.clean()?;
|
|
||||||
if config.registration.registration_type == RegistrationType::Invite {
|
if config.registration.registration_type == RegistrationType::Invite {
|
||||||
let invite_code = account_data.invite_code.as_ref()
|
let invite_code = account_data.invite_code.as_ref()
|
||||||
.ok_or(ValidationError("invite code is required"))?;
|
.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 maybe_password_hash = if let Some(password) = account_data.password.as_ref() {
|
||||||
let password_hash = hash_password(password)
|
let password_hash = hash_password(password)
|
||||||
.map_err(|_| MastodonError::InternalError)?;
|
.map_err(|_| MastodonError::InternalError)?;
|
||||||
|
|
|
@ -2,7 +2,10 @@ use regex::Regex;
|
||||||
|
|
||||||
use crate::errors::ValidationError;
|
use crate::errors::ValidationError;
|
||||||
|
|
||||||
|
use super::profiles::validate_username;
|
||||||
|
|
||||||
pub fn validate_local_username(username: &str) -> Result<(), ValidationError> {
|
pub fn validate_local_username(username: &str) -> Result<(), ValidationError> {
|
||||||
|
validate_username(username)?;
|
||||||
// The username regexp should not allow domain names and IP addresses
|
// The username regexp should not allow domain names and IP addresses
|
||||||
let username_regexp = Regex::new(r"^[a-z0-9_]+$").unwrap();
|
let username_regexp = Regex::new(r"^[a-z0-9_]+$").unwrap();
|
||||||
if !username_regexp.is_match(username) {
|
if !username_regexp.is_match(username) {
|
||||||
|
|
|
@ -125,6 +125,7 @@ mod tests {
|
||||||
let instance = Instance::for_test("https://example.com");
|
let instance = Instance::for_test("https://example.com");
|
||||||
let user_data = UserCreateData {
|
let user_data = UserCreateData {
|
||||||
username: "test".to_string(),
|
username: "test".to_string(),
|
||||||
|
password_hash: Some("test".to_string()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
create_user(db_client, user_data).await.unwrap();
|
create_user(db_client, user_data).await.unwrap();
|
||||||
|
|
Loading…
Reference in a new issue