First pass at invite-only migration. (#1949)
* First pass at invite-only migration. * Implement email verification (fixes #219) * remove unwrap * Adding views and functionality to registration application. #209 * Add private instance site column, and back end checks. * Adding some message fields to LoginResponse * Adding private instance to site setup. * A few additions: - Add a DeleteAccount response. - RegistrationApplicationView now has the safe LocalUserSettings. - Adding VerifyEmail to websocket API, added a proper response type. * Adding and reorganizing some email helpers. * A few fixes for private sites: - Added a check_registration_application function. - Only send a verification email if its been changed. - VerifyEmail now returns LoginResponse. - Deleting the old tokens after a successful email verify. - If port is missing on email config, display a better error message. * Version 0.15.0-rc.3 * Adding published to email_verification table. * Adding fixes from comments. * Version 0.15.0-rc.4 * Adding modlog private site check. * Version 0.15.0-rc.6 Co-authored-by: Felix Ableitner <me@nutomic.com>
This commit is contained in:
parent
1410c5659c
commit
c883a49a40
59 changed files with 1540 additions and 258 deletions
28
Cargo.lock
generated
28
Cargo.lock
generated
|
@ -1686,7 +1686,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_api"
|
name = "lemmy_api"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix",
|
"actix",
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
|
@ -1729,7 +1729,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_api_common"
|
name = "lemmy_api_common"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
@ -1747,7 +1747,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_api_crud"
|
name = "lemmy_api_crud"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix",
|
"actix",
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
|
@ -1790,7 +1790,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_apub"
|
name = "lemmy_apub"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activitystreams-kinds",
|
"activitystreams-kinds",
|
||||||
"actix",
|
"actix",
|
||||||
|
@ -1836,7 +1836,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_apub_lib"
|
name = "lemmy_apub_lib"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activitystreams",
|
"activitystreams",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
@ -1863,7 +1863,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_apub_lib_derive"
|
name = "lemmy_apub_lib_derive"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.33",
|
"proc-macro2 1.0.33",
|
||||||
"quote 1.0.10",
|
"quote 1.0.10",
|
||||||
|
@ -1873,7 +1873,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_db_schema"
|
name = "lemmy_db_schema"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bcrypt",
|
"bcrypt",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
@ -1895,7 +1895,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_db_views"
|
name = "lemmy_db_views"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"diesel",
|
"diesel",
|
||||||
"lemmy_db_schema",
|
"lemmy_db_schema",
|
||||||
|
@ -1907,7 +1907,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_db_views_actor"
|
name = "lemmy_db_views_actor"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"diesel",
|
"diesel",
|
||||||
"lemmy_db_schema",
|
"lemmy_db_schema",
|
||||||
|
@ -1916,7 +1916,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_db_views_moderator"
|
name = "lemmy_db_views_moderator"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"diesel",
|
"diesel",
|
||||||
"lemmy_db_schema",
|
"lemmy_db_schema",
|
||||||
|
@ -1925,7 +1925,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_routes"
|
name = "lemmy_routes"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix",
|
"actix",
|
||||||
"actix-http",
|
"actix-http",
|
||||||
|
@ -1956,7 +1956,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_server"
|
name = "lemmy_server"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activitystreams",
|
"activitystreams",
|
||||||
"actix",
|
"actix",
|
||||||
|
@ -2000,7 +2000,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_utils"
|
name = "lemmy_utils"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
@ -2038,7 +2038,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_websocket"
|
name = "lemmy_websocket"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix",
|
"actix",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
|
26
Cargo.toml
26
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lemmy_server"
|
name = "lemmy_server"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A link aggregator for the fediverse"
|
description = "A link aggregator for the fediverse"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
|
@ -31,18 +31,18 @@ members = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_api = { version = "=0.14.4-rc.4", path = "./crates/api" }
|
lemmy_api = { version = "=0.15.0-rc.6", path = "./crates/api" }
|
||||||
lemmy_api_crud = { version = "=0.14.4-rc.4", path = "./crates/api_crud" }
|
lemmy_api_crud = { version = "=0.15.0-rc.6", path = "./crates/api_crud" }
|
||||||
lemmy_apub = { version = "=0.14.4-rc.4", path = "./crates/apub" }
|
lemmy_apub = { version = "=0.15.0-rc.6", path = "./crates/apub" }
|
||||||
lemmy_apub_lib = { version = "=0.14.4-rc.4", path = "./crates/apub_lib" }
|
lemmy_apub_lib = { version = "=0.15.0-rc.6", path = "./crates/apub_lib" }
|
||||||
lemmy_utils = { version = "=0.14.4-rc.4", path = "./crates/utils" }
|
lemmy_utils = { version = "=0.15.0-rc.6", path = "./crates/utils" }
|
||||||
lemmy_db_schema = { version = "=0.14.4-rc.4", path = "./crates/db_schema" }
|
lemmy_db_schema = { version = "=0.15.0-rc.6", path = "./crates/db_schema" }
|
||||||
lemmy_db_views = { version = "=0.14.4-rc.4", path = "./crates/db_views" }
|
lemmy_db_views = { version = "=0.15.0-rc.6", path = "./crates/db_views" }
|
||||||
lemmy_db_views_moderator = { version = "=0.14.4-rc.4", path = "./crates/db_views_moderator" }
|
lemmy_db_views_moderator = { version = "=0.15.0-rc.6", path = "./crates/db_views_moderator" }
|
||||||
lemmy_db_views_actor = { version = "=0.14.4-rc.4", path = "./crates/db_views_actor" }
|
lemmy_db_views_actor = { version = "=0.15.0-rc.6", path = "./crates/db_views_actor" }
|
||||||
lemmy_api_common = { version = "=0.14.4-rc.4", path = "crates/api_common" }
|
lemmy_api_common = { version = "=0.15.0-rc.6", path = "crates/api_common" }
|
||||||
lemmy_websocket = { version = "=0.14.4-rc.4", path = "./crates/websocket" }
|
lemmy_websocket = { version = "=0.15.0-rc.6", path = "./crates/websocket" }
|
||||||
lemmy_routes = { version = "=0.14.4-rc.4", path = "./crates/routes" }
|
lemmy_routes = { version = "=0.15.0-rc.6", path = "./crates/routes" }
|
||||||
diesel = "1.4.8"
|
diesel = "1.4.8"
|
||||||
diesel_migrations = "1.4.0"
|
diesel_migrations = "1.4.0"
|
||||||
chrono = { version = "0.4.19", features = ["serde"] }
|
chrono = { version = "0.4.19", features = ["serde"] }
|
||||||
|
|
|
@ -97,6 +97,10 @@
|
||||||
open_registration: true
|
open_registration: true
|
||||||
enable_nsfw: true
|
enable_nsfw: true
|
||||||
community_creation_admin_only: true
|
community_creation_admin_only: true
|
||||||
|
require_email_verification: true
|
||||||
|
require_application: true
|
||||||
|
application_question: "string"
|
||||||
|
private_instance: true
|
||||||
}
|
}
|
||||||
# the domain name of your instance (mandatory)
|
# the domain name of your instance (mandatory)
|
||||||
hostname: "unset"
|
hostname: "unset"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lemmy_api"
|
name = "lemmy_api"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A link aggregator for the fediverse"
|
description = "A link aggregator for the fediverse"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
|
@ -13,15 +13,15 @@ path = "src/lib.rs"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_apub = { version = "=0.14.4-rc.4", path = "../apub" }
|
lemmy_apub = { version = "=0.15.0-rc.6", path = "../apub" }
|
||||||
lemmy_apub_lib = { version = "=0.14.4-rc.4", path = "../apub_lib" }
|
lemmy_apub_lib = { version = "=0.15.0-rc.6", path = "../apub_lib" }
|
||||||
lemmy_utils = { version = "=0.14.4-rc.4", path = "../utils" }
|
lemmy_utils = { version = "=0.15.0-rc.6", path = "../utils" }
|
||||||
lemmy_db_schema = { version = "=0.14.4-rc.4", path = "../db_schema" }
|
lemmy_db_schema = { version = "=0.15.0-rc.6", path = "../db_schema" }
|
||||||
lemmy_db_views = { version = "=0.14.4-rc.4", path = "../db_views" }
|
lemmy_db_views = { version = "=0.15.0-rc.6", path = "../db_views" }
|
||||||
lemmy_db_views_moderator = { version = "=0.14.4-rc.4", path = "../db_views_moderator" }
|
lemmy_db_views_moderator = { version = "=0.15.0-rc.6", path = "../db_views_moderator" }
|
||||||
lemmy_db_views_actor = { version = "=0.14.4-rc.4", path = "../db_views_actor" }
|
lemmy_db_views_actor = { version = "=0.15.0-rc.6", path = "../db_views_actor" }
|
||||||
lemmy_api_common = { version = "=0.14.4-rc.4", path = "../api_common" }
|
lemmy_api_common = { version = "=0.15.0-rc.6", path = "../api_common" }
|
||||||
lemmy_websocket = { version = "=0.14.4-rc.4", path = "../websocket" }
|
lemmy_websocket = { version = "=0.15.0-rc.6", path = "../websocket" }
|
||||||
diesel = "1.4.8"
|
diesel = "1.4.8"
|
||||||
bcrypt = "0.10.1"
|
bcrypt = "0.10.1"
|
||||||
chrono = { version = "0.4.19", features = ["serde"] }
|
chrono = { version = "0.4.19", features = ["serde"] }
|
||||||
|
|
|
@ -38,6 +38,15 @@ pub async fn match_websocket_operation(
|
||||||
UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
|
UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
|
||||||
UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
|
UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
|
||||||
UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
|
UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
|
||||||
|
UserOperation::GetUnreadRegistrationApplicationCount => {
|
||||||
|
do_websocket_operation::<GetUnreadRegistrationApplicationCount>(context, id, op, data).await
|
||||||
|
}
|
||||||
|
UserOperation::ListRegistrationApplications => {
|
||||||
|
do_websocket_operation::<ListRegistrationApplications>(context, id, op, data).await
|
||||||
|
}
|
||||||
|
UserOperation::ApproveRegistrationApplication => {
|
||||||
|
do_websocket_operation::<ApproveRegistrationApplication>(context, id, op, data).await
|
||||||
|
}
|
||||||
UserOperation::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await,
|
UserOperation::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await,
|
||||||
UserOperation::BlockPerson => {
|
UserOperation::BlockPerson => {
|
||||||
do_websocket_operation::<BlockPerson>(context, id, op, data).await
|
do_websocket_operation::<BlockPerson>(context, id, op, data).await
|
||||||
|
@ -75,6 +84,9 @@ pub async fn match_websocket_operation(
|
||||||
UserOperation::GetUnreadCount => {
|
UserOperation::GetUnreadCount => {
|
||||||
do_websocket_operation::<GetUnreadCount>(context, id, op, data).await
|
do_websocket_operation::<GetUnreadCount>(context, id, op, data).await
|
||||||
}
|
}
|
||||||
|
UserOperation::VerifyEmail => {
|
||||||
|
do_websocket_operation::<VerifyEmail>(context, id, op, data).await
|
||||||
|
}
|
||||||
|
|
||||||
// Private Message ops
|
// Private Message ops
|
||||||
UserOperation::MarkPrivateMessageAsRead => {
|
UserOperation::MarkPrivateMessageAsRead => {
|
||||||
|
@ -219,8 +231,8 @@ mod tests {
|
||||||
let inserted_person = Person::create(&conn, &new_person).unwrap();
|
let inserted_person = Person::create(&conn, &new_person).unwrap();
|
||||||
|
|
||||||
let local_user_form = LocalUserForm {
|
let local_user_form = LocalUserForm {
|
||||||
person_id: inserted_person.id,
|
person_id: Some(inserted_person.id),
|
||||||
password_encrypted: "123456".to_string(),
|
password_encrypted: Some("123456".to_string()),
|
||||||
..LocalUserForm::default()
|
..LocalUserForm::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,14 @@ use captcha::{gen, Difficulty};
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
blocking,
|
blocking,
|
||||||
|
check_registration_application,
|
||||||
get_local_user_view_from_jwt,
|
get_local_user_view_from_jwt,
|
||||||
is_admin,
|
is_admin,
|
||||||
password_length_check,
|
password_length_check,
|
||||||
person::*,
|
person::*,
|
||||||
|
send_email_verification_success,
|
||||||
|
send_password_reset_email,
|
||||||
|
send_verification_email,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
diesel_option_overwrite,
|
diesel_option_overwrite,
|
||||||
|
@ -19,6 +23,7 @@ use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
comment::Comment,
|
comment::Comment,
|
||||||
community::Community,
|
community::Community,
|
||||||
|
email_verification::EmailVerification,
|
||||||
local_user::{LocalUser, LocalUserForm},
|
local_user::{LocalUser, LocalUserForm},
|
||||||
moderator::*,
|
moderator::*,
|
||||||
password_reset_request::*,
|
password_reset_request::*,
|
||||||
|
@ -46,12 +51,10 @@ use lemmy_db_views_actor::{
|
||||||
};
|
};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
claims::Claims,
|
claims::Claims,
|
||||||
email::send_email,
|
|
||||||
location_info,
|
location_info,
|
||||||
utils::{generate_random_string, is_valid_display_name, is_valid_matrix_id, naive_from_unix},
|
utils::{is_valid_display_name, is_valid_matrix_id, naive_from_unix},
|
||||||
ConnectionId,
|
ConnectionId,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
Sensitive,
|
|
||||||
};
|
};
|
||||||
use lemmy_websocket::{
|
use lemmy_websocket::{
|
||||||
messages::{CaptchaItem, SendAllMessage},
|
messages::{CaptchaItem, SendAllMessage},
|
||||||
|
@ -90,14 +93,25 @@ impl Perform for Login {
|
||||||
return Err(LemmyError::from_message("password_incorrect"));
|
return Err(LemmyError::from_message("password_incorrect"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let site = blocking(context.pool(), Site::read_simple).await??;
|
||||||
|
if site.require_email_verification && !local_user_view.local_user.email_verified {
|
||||||
|
return Err(LemmyError::from_message("email_not_verified"));
|
||||||
|
}
|
||||||
|
|
||||||
|
check_registration_application(&site, &local_user_view, context.pool()).await?;
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(LoginResponse {
|
Ok(LoginResponse {
|
||||||
jwt: Claims::jwt(
|
jwt: Some(
|
||||||
|
Claims::jwt(
|
||||||
local_user_view.local_user.id.0,
|
local_user_view.local_user.id.0,
|
||||||
&context.secret().jwt_secret,
|
&context.secret().jwt_secret,
|
||||||
&context.settings().hostname,
|
&context.settings().hostname,
|
||||||
)?
|
)?
|
||||||
.into(),
|
.into(),
|
||||||
|
),
|
||||||
|
verify_email_sent: false,
|
||||||
|
registration_created: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,11 +178,35 @@ impl Perform for SaveUserSettings {
|
||||||
|
|
||||||
let avatar = diesel_option_overwrite_to_url(&data.avatar)?;
|
let avatar = diesel_option_overwrite_to_url(&data.avatar)?;
|
||||||
let banner = diesel_option_overwrite_to_url(&data.banner)?;
|
let banner = diesel_option_overwrite_to_url(&data.banner)?;
|
||||||
let email = diesel_option_overwrite(&data.email.clone().map(Sensitive::into_inner));
|
|
||||||
let bio = diesel_option_overwrite(&data.bio);
|
let bio = diesel_option_overwrite(&data.bio);
|
||||||
let display_name = diesel_option_overwrite(&data.display_name);
|
let display_name = diesel_option_overwrite(&data.display_name);
|
||||||
let matrix_user_id = diesel_option_overwrite(&data.matrix_user_id);
|
let matrix_user_id = diesel_option_overwrite(&data.matrix_user_id);
|
||||||
let bot_account = data.bot_account;
|
let bot_account = data.bot_account;
|
||||||
|
let email_deref = data.email.as_deref().map(|e| e.to_owned());
|
||||||
|
let email = diesel_option_overwrite(&email_deref);
|
||||||
|
|
||||||
|
if let Some(Some(email)) = &email {
|
||||||
|
let previous_email = local_user_view.local_user.email.unwrap_or_default();
|
||||||
|
// Only send the verification email if there was an email change
|
||||||
|
if previous_email.ne(email) {
|
||||||
|
send_verification_email(
|
||||||
|
local_user_view.local_user.id,
|
||||||
|
email,
|
||||||
|
&local_user_view.person.name,
|
||||||
|
context.pool(),
|
||||||
|
&context.settings(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the site requires email, make sure email is not Some(None). IE, an overwrite to a None value
|
||||||
|
if let Some(email) = &email {
|
||||||
|
let site_fut = blocking(context.pool(), Site::read_simple);
|
||||||
|
if email.is_none() && site_fut.await??.require_email_verification {
|
||||||
|
return Err(LemmyError::from_message("email_required"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(Some(bio)) = &bio {
|
if let Some(Some(bio)) = &bio {
|
||||||
if bio.chars().count() > 300 {
|
if bio.chars().count() > 300 {
|
||||||
|
@ -228,9 +266,9 @@ impl Perform for SaveUserSettings {
|
||||||
.map_err(|e| e.with_message("user_already_exists"))?;
|
.map_err(|e| e.with_message("user_already_exists"))?;
|
||||||
|
|
||||||
let local_user_form = LocalUserForm {
|
let local_user_form = LocalUserForm {
|
||||||
person_id,
|
person_id: Some(person_id),
|
||||||
email,
|
email,
|
||||||
password_encrypted,
|
password_encrypted: Some(password_encrypted),
|
||||||
show_nsfw: data.show_nsfw,
|
show_nsfw: data.show_nsfw,
|
||||||
show_bot_accounts: data.show_bot_accounts,
|
show_bot_accounts: data.show_bot_accounts,
|
||||||
show_scores: data.show_scores,
|
show_scores: data.show_scores,
|
||||||
|
@ -242,6 +280,8 @@ impl Perform for SaveUserSettings {
|
||||||
show_read_posts: data.show_read_posts,
|
show_read_posts: data.show_read_posts,
|
||||||
show_new_post_notifs: data.show_new_post_notifs,
|
show_new_post_notifs: data.show_new_post_notifs,
|
||||||
send_notifications_to_email: data.send_notifications_to_email,
|
send_notifications_to_email: data.send_notifications_to_email,
|
||||||
|
email_verified: None,
|
||||||
|
accepted_application: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let local_user_res = blocking(context.pool(), move |conn| {
|
let local_user_res = blocking(context.pool(), move |conn| {
|
||||||
|
@ -265,12 +305,16 @@ impl Perform for SaveUserSettings {
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(LoginResponse {
|
Ok(LoginResponse {
|
||||||
jwt: Claims::jwt(
|
jwt: Some(
|
||||||
|
Claims::jwt(
|
||||||
updated_local_user.id.0,
|
updated_local_user.id.0,
|
||||||
&context.secret().jwt_secret,
|
&context.secret().jwt_secret,
|
||||||
&context.settings().hostname,
|
&context.settings().hostname,
|
||||||
)?
|
)?
|
||||||
.into(),
|
.into(),
|
||||||
|
),
|
||||||
|
verify_email_sent: false,
|
||||||
|
registration_created: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -315,12 +359,16 @@ impl Perform for ChangePassword {
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(LoginResponse {
|
Ok(LoginResponse {
|
||||||
jwt: Claims::jwt(
|
jwt: Some(
|
||||||
|
Claims::jwt(
|
||||||
updated_local_user.id.0,
|
updated_local_user.id.0,
|
||||||
&context.secret().jwt_secret,
|
&context.secret().jwt_secret,
|
||||||
&context.settings().hostname,
|
&context.settings().hostname,
|
||||||
)?
|
)?
|
||||||
.into(),
|
.into(),
|
||||||
|
),
|
||||||
|
verify_email_sent: false,
|
||||||
|
registration_created: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -736,34 +784,8 @@ impl Perform for PasswordReset {
|
||||||
.map_err(LemmyError::from)
|
.map_err(LemmyError::from)
|
||||||
.map_err(|e| e.with_message("couldnt_find_that_username_or_email"))?;
|
.map_err(|e| e.with_message("couldnt_find_that_username_or_email"))?;
|
||||||
|
|
||||||
// Generate a random token
|
|
||||||
let token = generate_random_string();
|
|
||||||
|
|
||||||
// Insert the row
|
|
||||||
let token2 = token.clone();
|
|
||||||
let local_user_id = local_user_view.local_user.id;
|
|
||||||
blocking(context.pool(), move |conn| {
|
|
||||||
PasswordResetRequest::create_token(conn, local_user_id, &token2)
|
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
// Email the pure token to the user.
|
// Email the pure token to the user.
|
||||||
// TODO no i18n support here.
|
send_password_reset_email(&local_user_view, context.pool(), &context.settings()).await?;
|
||||||
let email = &local_user_view.local_user.email.expect("email");
|
|
||||||
let subject = &format!("Password reset for {}", local_user_view.person.name);
|
|
||||||
let protocol_and_hostname = &context.settings().get_protocol_and_hostname();
|
|
||||||
let html = &format!("<h1>Password Reset Request for {}</h1><br><a href={}/password_change/{}>Click here to reset your password</a>", local_user_view.person.name, protocol_and_hostname, &token);
|
|
||||||
send_email(
|
|
||||||
subject,
|
|
||||||
email,
|
|
||||||
&local_user_view.person.name,
|
|
||||||
html,
|
|
||||||
&context.settings(),
|
|
||||||
)
|
|
||||||
.map_err(|e| anyhow::anyhow!("{}", e))
|
|
||||||
.map_err(LemmyError::from)
|
|
||||||
.map_err(|e| e.with_message("email_send_failed"))?;
|
|
||||||
|
|
||||||
Ok(PasswordResetResponse {})
|
Ok(PasswordResetResponse {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -805,12 +827,16 @@ impl Perform for PasswordChange {
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(LoginResponse {
|
Ok(LoginResponse {
|
||||||
jwt: Claims::jwt(
|
jwt: Some(
|
||||||
|
Claims::jwt(
|
||||||
updated_local_user.id.0,
|
updated_local_user.id.0,
|
||||||
&context.secret().jwt_secret,
|
&context.secret().jwt_secret,
|
||||||
&context.settings().hostname,
|
&context.settings().hostname,
|
||||||
)?
|
)?
|
||||||
.into(),
|
.into(),
|
||||||
|
),
|
||||||
|
verify_email_sent: false,
|
||||||
|
registration_created: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -893,3 +919,49 @@ impl Perform for GetUnreadCount {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Perform for VerifyEmail {
|
||||||
|
type Response = VerifyEmailResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
_websocket_id: Option<usize>,
|
||||||
|
) -> Result<Self::Response, LemmyError> {
|
||||||
|
let token = self.token.clone();
|
||||||
|
let verification = blocking(context.pool(), move |conn| {
|
||||||
|
EmailVerification::read_for_token(conn, &token)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
.map_err(LemmyError::from)
|
||||||
|
.map_err(|e| e.with_message("token_not_found"))?;
|
||||||
|
|
||||||
|
let form = LocalUserForm {
|
||||||
|
// necessary in case this is a new signup
|
||||||
|
email_verified: Some(true),
|
||||||
|
// necessary in case email of an existing user was changed
|
||||||
|
email: Some(Some(verification.email)),
|
||||||
|
..LocalUserForm::default()
|
||||||
|
};
|
||||||
|
let local_user_id = verification.local_user_id;
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
LocalUser::update(conn, local_user_id, &form)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let local_user_view = blocking(context.pool(), move |conn| {
|
||||||
|
LocalUserView::read(conn, local_user_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
send_email_verification_success(&local_user_view, &context.settings())?;
|
||||||
|
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
EmailVerification::delete_old_tokens_for_local_user(conn, local_user_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
Ok(VerifyEmailResponse {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,9 +5,11 @@ use diesel::NotFound;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
blocking,
|
blocking,
|
||||||
build_federated_instances,
|
build_federated_instances,
|
||||||
|
check_private_instance,
|
||||||
get_local_user_view_from_jwt,
|
get_local_user_view_from_jwt,
|
||||||
get_local_user_view_from_jwt_opt,
|
get_local_user_view_from_jwt_opt,
|
||||||
is_admin,
|
is_admin,
|
||||||
|
send_application_approved_email,
|
||||||
site::*,
|
site::*,
|
||||||
};
|
};
|
||||||
use lemmy_apub::{
|
use lemmy_apub::{
|
||||||
|
@ -19,9 +21,15 @@ use lemmy_apub::{
|
||||||
EndpointType,
|
EndpointType,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
|
diesel_option_overwrite,
|
||||||
from_opt_str_to_opt_enum,
|
from_opt_str_to_opt_enum,
|
||||||
newtypes::PersonId,
|
newtypes::PersonId,
|
||||||
source::{moderator::*, site::Site},
|
source::{
|
||||||
|
local_user::{LocalUser, LocalUserForm},
|
||||||
|
moderator::*,
|
||||||
|
registration_application::{RegistrationApplication, RegistrationApplicationForm},
|
||||||
|
site::Site,
|
||||||
|
},
|
||||||
traits::{Crud, DeleteableOrRemoveable},
|
traits::{Crud, DeleteableOrRemoveable},
|
||||||
DbPool,
|
DbPool,
|
||||||
ListingType,
|
ListingType,
|
||||||
|
@ -30,7 +38,12 @@ use lemmy_db_schema::{
|
||||||
};
|
};
|
||||||
use lemmy_db_views::{
|
use lemmy_db_views::{
|
||||||
comment_view::{CommentQueryBuilder, CommentView},
|
comment_view::{CommentQueryBuilder, CommentView},
|
||||||
|
local_user_view::LocalUserView,
|
||||||
post_view::{PostQueryBuilder, PostView},
|
post_view::{PostQueryBuilder, PostView},
|
||||||
|
registration_application_view::{
|
||||||
|
RegistrationApplicationQueryBuilder,
|
||||||
|
RegistrationApplicationView,
|
||||||
|
},
|
||||||
site_view::SiteView,
|
site_view::SiteView,
|
||||||
};
|
};
|
||||||
use lemmy_db_views_actor::{
|
use lemmy_db_views_actor::{
|
||||||
|
@ -64,6 +77,12 @@ impl Perform for GetModlog {
|
||||||
) -> Result<GetModlogResponse, LemmyError> {
|
) -> Result<GetModlogResponse, LemmyError> {
|
||||||
let data: &GetModlog = self;
|
let data: &GetModlog = self;
|
||||||
|
|
||||||
|
let local_user_view =
|
||||||
|
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
check_private_instance(&local_user_view, context.pool()).await?;
|
||||||
|
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
let mod_person_id = data.mod_person_id;
|
let mod_person_id = data.mod_person_id;
|
||||||
let page = data.page;
|
let page = data.page;
|
||||||
|
@ -149,6 +168,8 @@ impl Perform for Search {
|
||||||
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
check_private_instance(&local_user_view, context.pool()).await?;
|
||||||
|
|
||||||
let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
|
let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
|
||||||
let show_bot_accounts = local_user_view
|
let show_bot_accounts = local_user_view
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -388,6 +409,8 @@ impl Perform for ResolveObject {
|
||||||
let local_user_view =
|
let local_user_view =
|
||||||
get_local_user_view_from_jwt_opt(self.auth.as_ref(), context.pool(), context.secret())
|
get_local_user_view_from_jwt_opt(self.auth.as_ref(), context.pool(), context.secret())
|
||||||
.await?;
|
.await?;
|
||||||
|
check_private_instance(&local_user_view, context.pool()).await?;
|
||||||
|
|
||||||
let res = search_by_apub_id(&self.q, context)
|
let res = search_by_apub_id(&self.q, context)
|
||||||
.await
|
.await
|
||||||
.map_err(LemmyError::from)
|
.map_err(LemmyError::from)
|
||||||
|
@ -555,3 +578,142 @@ impl Perform for SaveSiteConfig {
|
||||||
Ok(GetSiteConfigResponse { config_hjson })
|
Ok(GetSiteConfigResponse { config_hjson })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lists registration applications, filterable by undenied only.
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Perform for ListRegistrationApplications {
|
||||||
|
type Response = ListRegistrationApplicationsResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
_websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<Self::Response, LemmyError> {
|
||||||
|
let data = self;
|
||||||
|
let local_user_view =
|
||||||
|
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||||
|
|
||||||
|
// Make sure user is an admin
|
||||||
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
let unread_only = data.unread_only;
|
||||||
|
let verified_email_only = blocking(context.pool(), Site::read_simple)
|
||||||
|
.await??
|
||||||
|
.require_email_verification;
|
||||||
|
|
||||||
|
let page = data.page;
|
||||||
|
let limit = data.limit;
|
||||||
|
let registration_applications = blocking(context.pool(), move |conn| {
|
||||||
|
RegistrationApplicationQueryBuilder::create(conn)
|
||||||
|
.unread_only(unread_only)
|
||||||
|
.verified_email_only(verified_email_only)
|
||||||
|
.page(page)
|
||||||
|
.limit(limit)
|
||||||
|
.list()
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = Self::Response {
|
||||||
|
registration_applications,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Perform for ApproveRegistrationApplication {
|
||||||
|
type Response = RegistrationApplicationResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
_websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<Self::Response, LemmyError> {
|
||||||
|
let data = self;
|
||||||
|
let local_user_view =
|
||||||
|
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||||
|
|
||||||
|
let app_id = data.id;
|
||||||
|
|
||||||
|
// Only let admins do this
|
||||||
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
// Update the registration with reason, admin_id
|
||||||
|
let deny_reason = diesel_option_overwrite(&data.deny_reason);
|
||||||
|
let app_form = RegistrationApplicationForm {
|
||||||
|
admin_id: Some(local_user_view.person.id),
|
||||||
|
deny_reason,
|
||||||
|
..RegistrationApplicationForm::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let registration_application = blocking(context.pool(), move |conn| {
|
||||||
|
RegistrationApplication::update(conn, app_id, &app_form)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
// Update the local_user row
|
||||||
|
let local_user_form = LocalUserForm {
|
||||||
|
accepted_application: Some(data.approve),
|
||||||
|
..LocalUserForm::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let approved_user_id = registration_application.local_user_id;
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
LocalUser::update(conn, approved_user_id, &local_user_form)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
if data.approve {
|
||||||
|
let approved_local_user_view = blocking(context.pool(), move |conn| {
|
||||||
|
LocalUserView::read(conn, approved_user_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
if approved_local_user_view.local_user.email.is_some() {
|
||||||
|
send_application_approved_email(&approved_local_user_view, &context.settings())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the view
|
||||||
|
let registration_application = blocking(context.pool(), move |conn| {
|
||||||
|
RegistrationApplicationView::read(conn, app_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
Ok(Self::Response {
|
||||||
|
registration_application,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Perform for GetUnreadRegistrationApplicationCount {
|
||||||
|
type Response = GetUnreadRegistrationApplicationCountResponse;
|
||||||
|
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
_websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<Self::Response, LemmyError> {
|
||||||
|
let data = self;
|
||||||
|
let local_user_view =
|
||||||
|
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||||
|
|
||||||
|
// Only let admins do this
|
||||||
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
let verified_email_only = blocking(context.pool(), Site::read_simple)
|
||||||
|
.await??
|
||||||
|
.require_email_verification;
|
||||||
|
|
||||||
|
let registration_applications = blocking(context.pool(), move |conn| {
|
||||||
|
RegistrationApplicationView::get_unread_count(conn, verified_email_only)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
Ok(Self::Response {
|
||||||
|
registration_applications,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lemmy_api_common"
|
name = "lemmy_api_common"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A link aggregator for the fediverse"
|
description = "A link aggregator for the fediverse"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
|
@ -13,11 +13,11 @@ path = "src/lib.rs"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_db_views = { version = "=0.14.4-rc.4", path = "../db_views" }
|
lemmy_db_views = { version = "=0.15.0-rc.6", path = "../db_views" }
|
||||||
lemmy_db_views_moderator = { version = "=0.14.4-rc.4", path = "../db_views_moderator" }
|
lemmy_db_views_moderator = { version = "=0.15.0-rc.6", path = "../db_views_moderator" }
|
||||||
lemmy_db_views_actor = { version = "=0.14.4-rc.4", path = "../db_views_actor" }
|
lemmy_db_views_actor = { version = "=0.15.0-rc.6", path = "../db_views_actor" }
|
||||||
lemmy_db_schema = { version = "=0.14.4-rc.4", path = "../db_schema" }
|
lemmy_db_schema = { version = "=0.15.0-rc.6", path = "../db_schema" }
|
||||||
lemmy_utils = { version = "=0.14.4-rc.4", path = "../utils" }
|
lemmy_utils = { version = "=0.15.0-rc.6", path = "../utils" }
|
||||||
serde = { version = "1.0.131", features = ["derive"] }
|
serde = { version = "1.0.131", features = ["derive"] }
|
||||||
diesel = "1.4.8"
|
diesel = "1.4.8"
|
||||||
actix-web = { version = "4.0.0-beta.14", default-features = false, features = ["cookies"] }
|
actix-web = { version = "4.0.0-beta.14", default-features = false, features = ["cookies"] }
|
||||||
|
|
|
@ -10,8 +10,11 @@ use lemmy_db_schema::{
|
||||||
newtypes::{CommunityId, LocalUserId, PersonId, PostId},
|
newtypes::{CommunityId, LocalUserId, PersonId, PostId},
|
||||||
source::{
|
source::{
|
||||||
community::Community,
|
community::Community,
|
||||||
|
email_verification::{EmailVerification, EmailVerificationForm},
|
||||||
|
password_reset_request::PasswordResetRequest,
|
||||||
person_block::PersonBlock,
|
person_block::PersonBlock,
|
||||||
post::{Post, PostRead, PostReadForm},
|
post::{Post, PostRead, PostReadForm},
|
||||||
|
registration_application::RegistrationApplication,
|
||||||
secret::Secret,
|
secret::Secret,
|
||||||
site::Site,
|
site::Site,
|
||||||
},
|
},
|
||||||
|
@ -23,7 +26,14 @@ use lemmy_db_views_actor::{
|
||||||
community_person_ban_view::CommunityPersonBanView,
|
community_person_ban_view::CommunityPersonBanView,
|
||||||
community_view::CommunityView,
|
community_view::CommunityView,
|
||||||
};
|
};
|
||||||
use lemmy_utils::{claims::Claims, settings::structs::FederationConfig, LemmyError, Sensitive};
|
use lemmy_utils::{
|
||||||
|
claims::Claims,
|
||||||
|
email::send_email,
|
||||||
|
settings::structs::{FederationConfig, Settings},
|
||||||
|
utils::generate_random_string,
|
||||||
|
LemmyError,
|
||||||
|
Sensitive,
|
||||||
|
};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
pub async fn blocking<F, T>(pool: &DbPool, f: F) -> Result<T, LemmyError>
|
pub async fn blocking<F, T>(pool: &DbPool, f: F) -> Result<T, LemmyError>
|
||||||
|
@ -252,6 +262,19 @@ pub async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), Le
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn check_private_instance(
|
||||||
|
local_user_view: &Option<LocalUserView>,
|
||||||
|
pool: &DbPool,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
if local_user_view.is_none() {
|
||||||
|
let site = blocking(pool, Site::read_simple).await??;
|
||||||
|
if site.private_instance {
|
||||||
|
return Err(LemmyError::from_message("instance_is_private"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn build_federated_instances(
|
pub async fn build_federated_instances(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
federation_config: &FederationConfig,
|
federation_config: &FederationConfig,
|
||||||
|
@ -320,3 +343,163 @@ pub fn honeypot_check(honeypot: &Option<String>) -> Result<(), LemmyError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn send_email_to_user(
|
||||||
|
local_user_view: &LocalUserView,
|
||||||
|
subject_text: &str,
|
||||||
|
body_text: &str,
|
||||||
|
comment_content: &str,
|
||||||
|
settings: &Settings,
|
||||||
|
) {
|
||||||
|
if local_user_view.person.banned || !local_user_view.local_user.send_notifications_to_email {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(user_email) = &local_user_view.local_user.email {
|
||||||
|
let subject = &format!(
|
||||||
|
"{} - {} {}",
|
||||||
|
subject_text, settings.hostname, local_user_view.person.name,
|
||||||
|
);
|
||||||
|
let html = &format!(
|
||||||
|
"<h1>{}</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
|
||||||
|
body_text,
|
||||||
|
local_user_view.person.name,
|
||||||
|
comment_content,
|
||||||
|
settings.get_protocol_and_hostname()
|
||||||
|
);
|
||||||
|
match send_email(
|
||||||
|
subject,
|
||||||
|
user_email,
|
||||||
|
&local_user_view.person.name,
|
||||||
|
html,
|
||||||
|
settings,
|
||||||
|
) {
|
||||||
|
Ok(_o) => _o,
|
||||||
|
Err(e) => tracing::error!("{}", e),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_password_reset_email(
|
||||||
|
local_user_view: &LocalUserView,
|
||||||
|
pool: &DbPool,
|
||||||
|
settings: &Settings,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
// Generate a random token
|
||||||
|
let token = generate_random_string();
|
||||||
|
|
||||||
|
// Insert the row
|
||||||
|
let token2 = token.clone();
|
||||||
|
let local_user_id = local_user_view.local_user.id;
|
||||||
|
blocking(pool, move |conn| {
|
||||||
|
PasswordResetRequest::create_token(conn, local_user_id, &token2)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let email = &local_user_view.local_user.email.to_owned().expect("email");
|
||||||
|
let subject = &format!("Password reset for {}", local_user_view.person.name);
|
||||||
|
let protocol_and_hostname = settings.get_protocol_and_hostname();
|
||||||
|
let html = &format!("<h1>Password Reset Request for {}</h1><br><a href={}/password_change/{}>Click here to reset your password</a>", local_user_view.person.name, protocol_and_hostname, &token);
|
||||||
|
send_email(subject, email, &local_user_view.person.name, html, settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a verification email
|
||||||
|
pub async fn send_verification_email(
|
||||||
|
local_user_id: LocalUserId,
|
||||||
|
new_email: &str,
|
||||||
|
username: &str,
|
||||||
|
pool: &DbPool,
|
||||||
|
settings: &Settings,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
let form = EmailVerificationForm {
|
||||||
|
local_user_id,
|
||||||
|
email: new_email.to_string(),
|
||||||
|
verification_token: generate_random_string(),
|
||||||
|
};
|
||||||
|
let verify_link = format!(
|
||||||
|
"{}/verify_email/{}",
|
||||||
|
settings.get_protocol_and_hostname(),
|
||||||
|
&form.verification_token
|
||||||
|
);
|
||||||
|
blocking(pool, move |conn| EmailVerification::create(conn, &form)).await??;
|
||||||
|
|
||||||
|
let subject = format!("Verify your email address for {}", settings.hostname);
|
||||||
|
let body = format!(
|
||||||
|
concat!(
|
||||||
|
"Please click the link below to verify your email address ",
|
||||||
|
"for the account @{}@{}. Ignore this email if the account isn't yours.<br><br>",
|
||||||
|
"<a href=\"{}\">Verify your email</a>"
|
||||||
|
),
|
||||||
|
username, settings.hostname, verify_link
|
||||||
|
);
|
||||||
|
send_email(&subject, new_email, username, &body, settings)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_email_verification_success(
|
||||||
|
local_user_view: &LocalUserView,
|
||||||
|
settings: &Settings,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
let email = &local_user_view.local_user.email.to_owned().expect("email");
|
||||||
|
let subject = &format!("Email verified for {}", local_user_view.person.actor_id);
|
||||||
|
let html = "Your email has been verified.";
|
||||||
|
send_email(subject, email, &local_user_view.person.name, html, settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_application_approved_email(
|
||||||
|
local_user_view: &LocalUserView,
|
||||||
|
settings: &Settings,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
let email = &local_user_view.local_user.email.to_owned().expect("email");
|
||||||
|
let subject = &format!(
|
||||||
|
"Registration approved for {}",
|
||||||
|
local_user_view.person.actor_id
|
||||||
|
);
|
||||||
|
let html = &format!(
|
||||||
|
"Your registration application has been approved. Welcome to {}!",
|
||||||
|
settings.hostname
|
||||||
|
);
|
||||||
|
send_email(subject, email, &local_user_view.person.name, html, settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn check_registration_application(
|
||||||
|
site: &Site,
|
||||||
|
local_user_view: &LocalUserView,
|
||||||
|
pool: &DbPool,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
if site.require_application
|
||||||
|
&& !local_user_view.local_user.accepted_application
|
||||||
|
&& !local_user_view.person.admin
|
||||||
|
{
|
||||||
|
// Fetch the registration, see if its denied
|
||||||
|
let local_user_id = local_user_view.local_user.id;
|
||||||
|
let registration = blocking(pool, move |conn| {
|
||||||
|
RegistrationApplication::find_by_local_user_id(conn, local_user_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
if registration.deny_reason.is_some() {
|
||||||
|
return Err(LemmyError::from_message("registration_denied"));
|
||||||
|
} else {
|
||||||
|
return Err(LemmyError::from_message("registration_application_pending"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TODO this check should be removed after https://github.com/LemmyNet/lemmy/issues/868 is done.
|
||||||
|
pub async fn check_private_instance_and_federation_enabled(
|
||||||
|
pool: &DbPool,
|
||||||
|
settings: &Settings,
|
||||||
|
) -> Result<(), LemmyError> {
|
||||||
|
let site_opt = blocking(pool, Site::read_simple).await?;
|
||||||
|
|
||||||
|
if let Ok(site) = site_opt {
|
||||||
|
if site.private_instance && settings.federation.enabled {
|
||||||
|
return Err(LemmyError::from_message(
|
||||||
|
"Cannot have both private instance and federation enabled.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -24,10 +24,13 @@ pub struct Register {
|
||||||
pub password: Sensitive<String>,
|
pub password: Sensitive<String>,
|
||||||
pub password_verify: Sensitive<String>,
|
pub password_verify: Sensitive<String>,
|
||||||
pub show_nsfw: bool,
|
pub show_nsfw: bool,
|
||||||
|
/// email is mandatory if email verification is enabled on the server
|
||||||
pub email: Option<Sensitive<String>>,
|
pub email: Option<Sensitive<String>>,
|
||||||
pub captcha_uuid: Option<String>,
|
pub captcha_uuid: Option<String>,
|
||||||
pub captcha_answer: Option<String>,
|
pub captcha_answer: Option<String>,
|
||||||
pub honeypot: Option<String>,
|
pub honeypot: Option<String>,
|
||||||
|
/// An answer is mandatory if require application is enabled on the server
|
||||||
|
pub answer: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -78,7 +81,10 @@ pub struct ChangePassword {
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct LoginResponse {
|
pub struct LoginResponse {
|
||||||
pub jwt: Sensitive<String>,
|
/// This is None in response to `Register` if email verification is enabled, or the server requires registration applications.
|
||||||
|
pub jwt: Option<Sensitive<String>>,
|
||||||
|
pub registration_created: bool,
|
||||||
|
pub verify_email_sent: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -194,6 +200,9 @@ pub struct DeleteAccount {
|
||||||
pub auth: Sensitive<String>,
|
pub auth: Sensitive<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct DeleteAccountResponse {}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct PasswordReset {
|
pub struct PasswordReset {
|
||||||
pub email: Sensitive<String>,
|
pub email: Sensitive<String>,
|
||||||
|
@ -279,3 +288,11 @@ pub struct GetUnreadCountResponse {
|
||||||
pub mentions: i64,
|
pub mentions: i64,
|
||||||
pub private_messages: i64,
|
pub private_messages: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct VerifyEmail {
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct VerifyEmailResponse {}
|
||||||
|
|
|
@ -3,6 +3,7 @@ use lemmy_db_views::{
|
||||||
comment_view::CommentView,
|
comment_view::CommentView,
|
||||||
local_user_view::LocalUserSettingsView,
|
local_user_view::LocalUserSettingsView,
|
||||||
post_view::PostView,
|
post_view::PostView,
|
||||||
|
registration_application_view::RegistrationApplicationView,
|
||||||
site_view::SiteView,
|
site_view::SiteView,
|
||||||
};
|
};
|
||||||
use lemmy_db_views_actor::{
|
use lemmy_db_views_actor::{
|
||||||
|
@ -71,6 +72,7 @@ pub struct GetModlog {
|
||||||
pub community_id: Option<CommunityId>,
|
pub community_id: Option<CommunityId>,
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
|
pub auth: Option<Sensitive<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -98,6 +100,10 @@ pub struct CreateSite {
|
||||||
pub open_registration: Option<bool>,
|
pub open_registration: Option<bool>,
|
||||||
pub enable_nsfw: Option<bool>,
|
pub enable_nsfw: Option<bool>,
|
||||||
pub community_creation_admin_only: Option<bool>,
|
pub community_creation_admin_only: Option<bool>,
|
||||||
|
pub require_email_verification: Option<bool>,
|
||||||
|
pub require_application: Option<bool>,
|
||||||
|
pub application_question: Option<String>,
|
||||||
|
pub private_instance: Option<bool>,
|
||||||
pub auth: Sensitive<String>,
|
pub auth: Sensitive<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +118,10 @@ pub struct EditSite {
|
||||||
pub open_registration: Option<bool>,
|
pub open_registration: Option<bool>,
|
||||||
pub enable_nsfw: Option<bool>,
|
pub enable_nsfw: Option<bool>,
|
||||||
pub community_creation_admin_only: Option<bool>,
|
pub community_creation_admin_only: Option<bool>,
|
||||||
|
pub require_email_verification: Option<bool>,
|
||||||
|
pub require_application: Option<bool>,
|
||||||
|
pub application_question: Option<String>,
|
||||||
|
pub private_instance: Option<bool>,
|
||||||
pub auth: Sensitive<String>,
|
pub auth: Sensitive<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,3 +183,40 @@ pub struct FederatedInstances {
|
||||||
pub allowed: Option<Vec<String>>,
|
pub allowed: Option<Vec<String>>,
|
||||||
pub blocked: Option<Vec<String>>,
|
pub blocked: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ListRegistrationApplications {
|
||||||
|
/// Only shows the unread applications (IE those without an admin actor)
|
||||||
|
pub unread_only: Option<bool>,
|
||||||
|
pub page: Option<i64>,
|
||||||
|
pub limit: Option<i64>,
|
||||||
|
pub auth: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ListRegistrationApplicationsResponse {
|
||||||
|
pub registration_applications: Vec<RegistrationApplicationView>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ApproveRegistrationApplication {
|
||||||
|
pub id: i32,
|
||||||
|
pub approve: bool,
|
||||||
|
pub deny_reason: Option<String>,
|
||||||
|
pub auth: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct RegistrationApplicationResponse {
|
||||||
|
pub registration_application: RegistrationApplicationView,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct GetUnreadRegistrationApplicationCount {
|
||||||
|
pub auth: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct GetUnreadRegistrationApplicationCountResponse {
|
||||||
|
pub registration_applications: i64,
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lemmy_api_crud"
|
name = "lemmy_api_crud"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A link aggregator for the fediverse"
|
description = "A link aggregator for the fediverse"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
|
@ -8,15 +8,15 @@ homepage = "https://join-lemmy.org/"
|
||||||
documentation = "https://join-lemmy.org/docs/en/index.html"
|
documentation = "https://join-lemmy.org/docs/en/index.html"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_apub = { version = "=0.14.4-rc.4", path = "../apub" }
|
lemmy_apub = { version = "=0.15.0-rc.6", path = "../apub" }
|
||||||
lemmy_apub_lib = { version = "=0.14.4-rc.4", path = "../apub_lib" }
|
lemmy_apub_lib = { version = "=0.15.0-rc.6", path = "../apub_lib" }
|
||||||
lemmy_utils = { version = "=0.14.4-rc.4", path = "../utils" }
|
lemmy_utils = { version = "=0.15.0-rc.6", path = "../utils" }
|
||||||
lemmy_db_schema = { version = "=0.14.4-rc.4", path = "../db_schema" }
|
lemmy_db_schema = { version = "=0.15.0-rc.6", path = "../db_schema" }
|
||||||
lemmy_db_views = { version = "=0.14.4-rc.4", path = "../db_views" }
|
lemmy_db_views = { version = "=0.15.0-rc.6", path = "../db_views" }
|
||||||
lemmy_db_views_moderator = { version = "=0.14.4-rc.4", path = "../db_views_moderator" }
|
lemmy_db_views_moderator = { version = "=0.15.0-rc.6", path = "../db_views_moderator" }
|
||||||
lemmy_db_views_actor = { version = "=0.14.4-rc.4", path = "../db_views_actor" }
|
lemmy_db_views_actor = { version = "=0.15.0-rc.6", path = "../db_views_actor" }
|
||||||
lemmy_api_common = { version = "=0.14.4-rc.4", path = "../api_common" }
|
lemmy_api_common = { version = "=0.15.0-rc.6", path = "../api_common" }
|
||||||
lemmy_websocket = { version = "=0.14.4-rc.4", path = "../websocket" }
|
lemmy_websocket = { version = "=0.15.0-rc.6", path = "../websocket" }
|
||||||
diesel = "1.4.8"
|
diesel = "1.4.8"
|
||||||
bcrypt = "0.10.1"
|
bcrypt = "0.10.1"
|
||||||
chrono = { version = "0.4.19", features = ["serde"] }
|
chrono = { version = "0.4.19", features = ["serde"] }
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
use crate::PerformCrud;
|
use crate::PerformCrud;
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use lemmy_api_common::{blocking, comment::*, get_local_user_view_from_jwt_opt};
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
|
check_private_instance,
|
||||||
|
comment::*,
|
||||||
|
get_local_user_view_from_jwt_opt,
|
||||||
|
};
|
||||||
use lemmy_apub::{
|
use lemmy_apub::{
|
||||||
fetcher::webfinger::webfinger_resolve,
|
fetcher::webfinger::webfinger_resolve,
|
||||||
objects::community::ApubCommunity,
|
objects::community::ApubCommunity,
|
||||||
|
@ -31,6 +36,8 @@ impl PerformCrud for GetComment {
|
||||||
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
check_private_instance(&local_user_view, context.pool()).await?;
|
||||||
|
|
||||||
let person_id = local_user_view.map(|u| u.person.id);
|
let person_id = local_user_view.map(|u| u.person.id);
|
||||||
let id = data.id;
|
let id = data.id;
|
||||||
let comment_view = blocking(context.pool(), move |conn| {
|
let comment_view = blocking(context.pool(), move |conn| {
|
||||||
|
@ -63,6 +70,8 @@ impl PerformCrud for GetComments {
|
||||||
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
check_private_instance(&local_user_view, context.pool()).await?;
|
||||||
|
|
||||||
let show_bot_accounts = local_user_view
|
let show_bot_accounts = local_user_view
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|t| t.local_user.show_bot_accounts);
|
.map(|t| t.local_user.show_bot_accounts);
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
use crate::PerformCrud;
|
use crate::PerformCrud;
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use lemmy_api_common::{blocking, community::*, get_local_user_view_from_jwt_opt};
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
|
check_private_instance,
|
||||||
|
community::*,
|
||||||
|
get_local_user_view_from_jwt_opt,
|
||||||
|
};
|
||||||
use lemmy_apub::{
|
use lemmy_apub::{
|
||||||
fetcher::webfinger::webfinger_resolve,
|
fetcher::webfinger::webfinger_resolve,
|
||||||
objects::community::ApubCommunity,
|
objects::community::ApubCommunity,
|
||||||
|
@ -34,6 +39,9 @@ impl PerformCrud for GetCommunity {
|
||||||
let local_user_view =
|
let local_user_view =
|
||||||
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
check_private_instance(&local_user_view, context.pool()).await?;
|
||||||
|
|
||||||
let person_id = local_user_view.map(|u| u.person.id);
|
let person_id = local_user_view.map(|u| u.person.id);
|
||||||
|
|
||||||
let community_id = match data.id {
|
let community_id = match data.id {
|
||||||
|
@ -105,6 +113,8 @@ impl PerformCrud for ListCommunities {
|
||||||
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
check_private_instance(&local_user_view, context.pool()).await?;
|
||||||
|
|
||||||
let person_id = local_user_view.to_owned().map(|l| l.person.id);
|
let person_id = local_user_view.to_owned().map(|l| l.person.id);
|
||||||
|
|
||||||
// Don't show NSFW by default
|
// Don't show NSFW by default
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
use crate::PerformCrud;
|
use crate::PerformCrud;
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use lemmy_api_common::{blocking, get_local_user_view_from_jwt_opt, mark_post_as_read, post::*};
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
|
check_private_instance,
|
||||||
|
get_local_user_view_from_jwt_opt,
|
||||||
|
mark_post_as_read,
|
||||||
|
post::*,
|
||||||
|
};
|
||||||
use lemmy_apub::{
|
use lemmy_apub::{
|
||||||
fetcher::webfinger::webfinger_resolve,
|
fetcher::webfinger::webfinger_resolve,
|
||||||
objects::community::ApubCommunity,
|
objects::community::ApubCommunity,
|
||||||
|
@ -38,6 +44,8 @@ impl PerformCrud for GetPost {
|
||||||
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
check_private_instance(&local_user_view, context.pool()).await?;
|
||||||
|
|
||||||
let show_bot_accounts = local_user_view
|
let show_bot_accounts = local_user_view
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|t| t.local_user.show_bot_accounts);
|
.map(|t| t.local_user.show_bot_accounts);
|
||||||
|
@ -130,6 +138,8 @@ impl PerformCrud for GetPosts {
|
||||||
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
check_private_instance(&local_user_view, context.pool()).await?;
|
||||||
|
|
||||||
let person_id = local_user_view.to_owned().map(|l| l.person.id);
|
let person_id = local_user_view.to_owned().map(|l| l.person.id);
|
||||||
|
|
||||||
let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
|
let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
|
||||||
|
|
|
@ -5,6 +5,7 @@ use lemmy_api_common::{
|
||||||
check_person_block,
|
check_person_block,
|
||||||
get_local_user_view_from_jwt,
|
get_local_user_view_from_jwt,
|
||||||
person::{CreatePrivateMessage, PrivateMessageResponse},
|
person::{CreatePrivateMessage, PrivateMessageResponse},
|
||||||
|
send_email_to_user,
|
||||||
};
|
};
|
||||||
use lemmy_apub::{
|
use lemmy_apub::{
|
||||||
generate_local_apub_endpoint,
|
generate_local_apub_endpoint,
|
||||||
|
@ -20,11 +21,7 @@ use lemmy_db_schema::{
|
||||||
};
|
};
|
||||||
use lemmy_db_views::local_user_view::LocalUserView;
|
use lemmy_db_views::local_user_view::LocalUserView;
|
||||||
use lemmy_utils::{utils::remove_slurs, ConnectionId, LemmyError};
|
use lemmy_utils::{utils::remove_slurs, ConnectionId, LemmyError};
|
||||||
use lemmy_websocket::{
|
use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud};
|
||||||
send::{send_email_to_user, send_pm_ws_message},
|
|
||||||
LemmyContext,
|
|
||||||
UserOperationCrud,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl PerformCrud for CreatePrivateMessage {
|
impl PerformCrud for CreatePrivateMessage {
|
||||||
|
|
|
@ -66,8 +66,8 @@ impl PerformCrud for CreateSite {
|
||||||
enable_downvotes: data.enable_downvotes,
|
enable_downvotes: data.enable_downvotes,
|
||||||
open_registration: data.open_registration,
|
open_registration: data.open_registration,
|
||||||
enable_nsfw: data.enable_nsfw,
|
enable_nsfw: data.enable_nsfw,
|
||||||
updated: None,
|
|
||||||
community_creation_admin_only: data.community_creation_admin_only,
|
community_creation_admin_only: data.community_creation_admin_only,
|
||||||
|
..SiteForm::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let create_site = move |conn: &'_ _| Site::create(conn, &site_form);
|
let create_site = move |conn: &'_ _| Site::create(conn, &site_form);
|
||||||
|
|
|
@ -45,8 +45,13 @@ impl PerformCrud for GetSite {
|
||||||
captcha_uuid: None,
|
captcha_uuid: None,
|
||||||
captcha_answer: None,
|
captcha_answer: None,
|
||||||
honeypot: None,
|
honeypot: None,
|
||||||
|
answer: None,
|
||||||
};
|
};
|
||||||
let login_response = register.perform(context, websocket_id).await?;
|
let admin_jwt = register
|
||||||
|
.perform(context, websocket_id)
|
||||||
|
.await?
|
||||||
|
.jwt
|
||||||
|
.expect("jwt is returned from registration on newly created site");
|
||||||
info!("Admin {} created", setup.admin_username);
|
info!("Admin {} created", setup.admin_username);
|
||||||
|
|
||||||
let create_site = CreateSite {
|
let create_site = CreateSite {
|
||||||
|
@ -59,7 +64,11 @@ impl PerformCrud for GetSite {
|
||||||
open_registration: setup.open_registration,
|
open_registration: setup.open_registration,
|
||||||
enable_nsfw: setup.enable_nsfw,
|
enable_nsfw: setup.enable_nsfw,
|
||||||
community_creation_admin_only: setup.community_creation_admin_only,
|
community_creation_admin_only: setup.community_creation_admin_only,
|
||||||
auth: login_response.jwt,
|
require_email_verification: setup.require_email_verification,
|
||||||
|
require_application: setup.require_application,
|
||||||
|
application_question: setup.application_question.to_owned(),
|
||||||
|
private_instance: setup.private_instance,
|
||||||
|
auth: admin_jwt,
|
||||||
};
|
};
|
||||||
create_site.perform(context, websocket_id).await?;
|
create_site.perform(context, websocket_id).await?;
|
||||||
info!("Site {} created", setup.site_name);
|
info!("Site {} created", setup.site_name);
|
||||||
|
|
|
@ -11,7 +11,10 @@ use lemmy_db_schema::{
|
||||||
diesel_option_overwrite,
|
diesel_option_overwrite,
|
||||||
diesel_option_overwrite_to_url,
|
diesel_option_overwrite_to_url,
|
||||||
naive_now,
|
naive_now,
|
||||||
source::site::{Site, SiteForm},
|
source::{
|
||||||
|
local_user::LocalUser,
|
||||||
|
site::{Site, SiteForm},
|
||||||
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::site_view::SiteView;
|
use lemmy_db_views::site_view::SiteView;
|
||||||
|
@ -42,6 +45,7 @@ impl PerformCrud for EditSite {
|
||||||
|
|
||||||
let sidebar = diesel_option_overwrite(&data.sidebar);
|
let sidebar = diesel_option_overwrite(&data.sidebar);
|
||||||
let description = diesel_option_overwrite(&data.description);
|
let description = diesel_option_overwrite(&data.description);
|
||||||
|
let application_question = diesel_option_overwrite(&data.application_question);
|
||||||
let icon = diesel_option_overwrite_to_url(&data.icon)?;
|
let icon = diesel_option_overwrite_to_url(&data.icon)?;
|
||||||
let banner = diesel_option_overwrite_to_url(&data.banner)?;
|
let banner = diesel_option_overwrite_to_url(&data.banner)?;
|
||||||
|
|
||||||
|
@ -61,14 +65,42 @@ impl PerformCrud for EditSite {
|
||||||
open_registration: data.open_registration,
|
open_registration: data.open_registration,
|
||||||
enable_nsfw: data.enable_nsfw,
|
enable_nsfw: data.enable_nsfw,
|
||||||
community_creation_admin_only: data.community_creation_admin_only,
|
community_creation_admin_only: data.community_creation_admin_only,
|
||||||
|
require_email_verification: data.require_email_verification,
|
||||||
|
require_application: data.require_application,
|
||||||
|
application_question,
|
||||||
|
private_instance: data.private_instance,
|
||||||
};
|
};
|
||||||
|
|
||||||
let update_site = move |conn: &'_ _| Site::update(conn, 1, &site_form);
|
let update_site = blocking(context.pool(), move |conn| {
|
||||||
blocking(context.pool(), update_site)
|
Site::update(conn, 1, &site_form)
|
||||||
|
})
|
||||||
.await?
|
.await?
|
||||||
.map_err(LemmyError::from)
|
.map_err(LemmyError::from)
|
||||||
.map_err(|e| e.with_message("couldnt_update_site"))?;
|
.map_err(|e| e.with_message("couldnt_update_site"))?;
|
||||||
|
|
||||||
|
// TODO can't think of a better way to do this.
|
||||||
|
// If the server suddenly requires email verification, or required applications, no old users
|
||||||
|
// will be able to log in. It really only wants this to be a requirement for NEW signups.
|
||||||
|
// So if it was set from false, to true, you need to update all current users columns to be verified.
|
||||||
|
|
||||||
|
if !found_site.require_application && update_site.require_application {
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
LocalUser::set_all_users_registration_applications_accepted(conn)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
.map_err(LemmyError::from)
|
||||||
|
.map_err(|e| e.with_message("couldnt_set_all_registrations_accepted"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found_site.require_email_verification && update_site.require_email_verification {
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
LocalUser::set_all_users_email_verified(conn)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
.map_err(LemmyError::from)
|
||||||
|
.map_err(|e| e.with_message("couldnt_set_all_email_verified"))?;
|
||||||
|
}
|
||||||
|
|
||||||
let site_view = blocking(context.pool(), SiteView::read).await??;
|
let site_view = blocking(context.pool(), SiteView::read).await??;
|
||||||
|
|
||||||
let res = SiteResponse { site_view };
|
let res = SiteResponse { site_view };
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
use crate::PerformCrud;
|
use crate::PerformCrud;
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use lemmy_api_common::{blocking, honeypot_check, password_length_check, person::*};
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
|
honeypot_check,
|
||||||
|
password_length_check,
|
||||||
|
person::*,
|
||||||
|
send_verification_email,
|
||||||
|
};
|
||||||
use lemmy_apub::{
|
use lemmy_apub::{
|
||||||
generate_followers_url,
|
generate_followers_url,
|
||||||
generate_inbox_url,
|
generate_inbox_url,
|
||||||
|
@ -21,11 +27,10 @@ use lemmy_db_schema::{
|
||||||
},
|
},
|
||||||
local_user::{LocalUser, LocalUserForm},
|
local_user::{LocalUser, LocalUserForm},
|
||||||
person::{Person, PersonForm},
|
person::{Person, PersonForm},
|
||||||
|
registration_application::{RegistrationApplication, RegistrationApplicationForm},
|
||||||
site::Site,
|
site::Site,
|
||||||
},
|
},
|
||||||
traits::{Crud, Followable, Joinable},
|
traits::{Crud, Followable, Joinable},
|
||||||
ListingType,
|
|
||||||
SortType,
|
|
||||||
};
|
};
|
||||||
use lemmy_db_views_actor::person_view::PersonViewSafe;
|
use lemmy_db_views_actor::person_view::PersonViewSafe;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
|
@ -49,16 +54,31 @@ impl PerformCrud for Register {
|
||||||
) -> Result<LoginResponse, LemmyError> {
|
) -> Result<LoginResponse, LemmyError> {
|
||||||
let data: &Register = self;
|
let data: &Register = self;
|
||||||
|
|
||||||
|
// no email verification, or applications if the site is not setup yet
|
||||||
|
let (mut email_verification, mut require_application) = (false, false);
|
||||||
|
|
||||||
// Make sure site has open registration
|
// Make sure site has open registration
|
||||||
if let Ok(site) = blocking(context.pool(), Site::read_simple).await? {
|
if let Ok(site) = blocking(context.pool(), Site::read_simple).await? {
|
||||||
if !site.open_registration {
|
if !site.open_registration {
|
||||||
return Err(LemmyError::from_message("registration_closed"));
|
return Err(LemmyError::from_message("registration_closed"));
|
||||||
}
|
}
|
||||||
|
email_verification = site.require_email_verification;
|
||||||
|
require_application = site.require_application;
|
||||||
}
|
}
|
||||||
|
|
||||||
password_length_check(&data.password)?;
|
password_length_check(&data.password)?;
|
||||||
honeypot_check(&data.honeypot)?;
|
honeypot_check(&data.honeypot)?;
|
||||||
|
|
||||||
|
if email_verification && data.email.is_none() {
|
||||||
|
return Err(LemmyError::from_message("email_required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if require_application && data.answer.is_none() {
|
||||||
|
return Err(LemmyError::from_message(
|
||||||
|
"registration_application_answer_required",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure passwords match
|
// Make sure passwords match
|
||||||
if data.password != data.password_verify {
|
if data.password != data.password_verify {
|
||||||
return Err(LemmyError::from_message("passwords_dont_match"));
|
return Err(LemmyError::from_message("passwords_dont_match"));
|
||||||
|
@ -125,22 +145,13 @@ impl PerformCrud for Register {
|
||||||
.map_err(|e| e.with_message("user_already_exists"))?;
|
.map_err(|e| e.with_message("user_already_exists"))?;
|
||||||
|
|
||||||
// Create the local user
|
// Create the local user
|
||||||
// TODO some of these could probably use the DB defaults
|
|
||||||
let local_user_form = LocalUserForm {
|
let local_user_form = LocalUserForm {
|
||||||
person_id: inserted_person.id,
|
person_id: Some(inserted_person.id),
|
||||||
email: Some(data.email.as_deref().map(|s| s.to_owned())),
|
email: Some(data.email.as_deref().map(|s| s.to_owned())),
|
||||||
password_encrypted: data.password.to_string(),
|
password_encrypted: Some(data.password.to_string()),
|
||||||
show_nsfw: Some(data.show_nsfw),
|
show_nsfw: Some(data.show_nsfw),
|
||||||
show_bot_accounts: Some(true),
|
email_verified: Some(false),
|
||||||
theme: Some("browser".into()),
|
..LocalUserForm::default()
|
||||||
default_sort_type: Some(SortType::Active as i16),
|
|
||||||
default_listing_type: Some(ListingType::Subscribed as i16),
|
|
||||||
lang: Some("browser".into()),
|
|
||||||
show_avatars: Some(true),
|
|
||||||
show_scores: Some(true),
|
|
||||||
show_read_posts: Some(true),
|
|
||||||
show_new_post_notifs: Some(false),
|
|
||||||
send_notifications_to_email: Some(false),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_local_user = match blocking(context.pool(), move |conn| {
|
let inserted_local_user = match blocking(context.pool(), move |conn| {
|
||||||
|
@ -168,6 +179,21 @@ impl PerformCrud for Register {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if require_application {
|
||||||
|
// Create the registration application
|
||||||
|
let form = RegistrationApplicationForm {
|
||||||
|
local_user_id: Some(inserted_local_user.id),
|
||||||
|
// We already made sure answer was not null above
|
||||||
|
answer: data.answer.to_owned(),
|
||||||
|
..RegistrationApplicationForm::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
RegistrationApplication::create(conn, &form)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
}
|
||||||
|
|
||||||
let main_community_keypair = generate_actor_keypair()?;
|
let main_community_keypair = generate_actor_keypair()?;
|
||||||
|
|
||||||
// Create the main community if it doesn't exist
|
// Create the main community if it doesn't exist
|
||||||
|
@ -231,14 +257,41 @@ impl PerformCrud for Register {
|
||||||
.map_err(|e| e.with_message("community_moderator_already_exists"))?;
|
.map_err(|e| e.with_message("community_moderator_already_exists"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the jwt
|
let mut login_response = LoginResponse {
|
||||||
Ok(LoginResponse {
|
jwt: None,
|
||||||
jwt: Claims::jwt(
|
registration_created: false,
|
||||||
|
verify_email_sent: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Log the user in directly if email verification and application aren't required
|
||||||
|
if !require_application && !email_verification {
|
||||||
|
login_response.jwt = Some(
|
||||||
|
Claims::jwt(
|
||||||
inserted_local_user.id.0,
|
inserted_local_user.id.0,
|
||||||
&context.secret().jwt_secret,
|
&context.secret().jwt_secret,
|
||||||
&context.settings().hostname,
|
&context.settings().hostname,
|
||||||
)?
|
)?
|
||||||
.into(),
|
.into(),
|
||||||
})
|
);
|
||||||
|
} else {
|
||||||
|
if email_verification {
|
||||||
|
send_verification_email(
|
||||||
|
inserted_local_user.id,
|
||||||
|
// we check at the beginning of this method that email is set
|
||||||
|
&inserted_local_user.email.expect("email was provided"),
|
||||||
|
&inserted_person.name,
|
||||||
|
context.pool(),
|
||||||
|
&context.settings(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
login_response.verify_email_sent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if require_application {
|
||||||
|
login_response.registration_created = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(login_response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,15 +8,15 @@ use lemmy_websocket::LemmyContext;
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl PerformCrud for DeleteAccount {
|
impl PerformCrud for DeleteAccount {
|
||||||
type Response = LoginResponse;
|
type Response = DeleteAccountResponse;
|
||||||
|
|
||||||
#[tracing::instrument(skip(self, context, _websocket_id))]
|
#[tracing::instrument(skip(self, context, _websocket_id))]
|
||||||
async fn perform(
|
async fn perform(
|
||||||
&self,
|
&self,
|
||||||
context: &Data<LemmyContext>,
|
context: &Data<LemmyContext>,
|
||||||
_websocket_id: Option<ConnectionId>,
|
_websocket_id: Option<ConnectionId>,
|
||||||
) -> Result<LoginResponse, LemmyError> {
|
) -> Result<Self::Response, LemmyError> {
|
||||||
let data: &DeleteAccount = self;
|
let data = self;
|
||||||
let local_user_view =
|
let local_user_view =
|
||||||
get_local_user_view_from_jwt(data.auth.as_ref(), context.pool(), context.secret()).await?;
|
get_local_user_view_from_jwt(data.auth.as_ref(), context.pool(), context.secret()).await?;
|
||||||
|
|
||||||
|
@ -50,8 +50,6 @@ impl PerformCrud for DeleteAccount {
|
||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
Ok(LoginResponse {
|
Ok(DeleteAccountResponse {})
|
||||||
jwt: data.auth.clone(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
use crate::PerformCrud;
|
use crate::PerformCrud;
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use lemmy_api_common::{blocking, get_local_user_view_from_jwt_opt, person::*};
|
use lemmy_api_common::{
|
||||||
|
blocking,
|
||||||
|
check_private_instance,
|
||||||
|
get_local_user_view_from_jwt_opt,
|
||||||
|
person::*,
|
||||||
|
};
|
||||||
use lemmy_apub::{
|
use lemmy_apub::{
|
||||||
fetcher::webfinger::webfinger_resolve,
|
fetcher::webfinger::webfinger_resolve,
|
||||||
objects::person::ApubPerson,
|
objects::person::ApubPerson,
|
||||||
|
@ -31,6 +36,8 @@ impl PerformCrud for GetPersonDetails {
|
||||||
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
check_private_instance(&local_user_view, context.pool()).await?;
|
||||||
|
|
||||||
let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
|
let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
|
||||||
let show_bot_accounts = local_user_view
|
let show_bot_accounts = local_user_view
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lemmy_apub"
|
name = "lemmy_apub"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A link aggregator for the fediverse"
|
description = "A link aggregator for the fediverse"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
|
@ -13,13 +13,13 @@ path = "src/lib.rs"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_utils = { version = "=0.14.4-rc.4", path = "../utils" }
|
lemmy_utils = { version = "=0.15.0-rc.6", path = "../utils" }
|
||||||
lemmy_apub_lib = { version = "=0.14.4-rc.4", path = "../apub_lib" }
|
lemmy_apub_lib = { version = "=0.15.0-rc.6", path = "../apub_lib" }
|
||||||
lemmy_db_schema = { version = "=0.14.4-rc.4", path = "../db_schema" }
|
lemmy_db_schema = { version = "=0.15.0-rc.6", path = "../db_schema" }
|
||||||
lemmy_db_views = { version = "=0.14.4-rc.4", path = "../db_views" }
|
lemmy_db_views = { version = "=0.15.0-rc.6", path = "../db_views" }
|
||||||
lemmy_db_views_actor = { version = "=0.14.4-rc.4", path = "../db_views_actor" }
|
lemmy_db_views_actor = { version = "=0.15.0-rc.6", path = "../db_views_actor" }
|
||||||
lemmy_api_common = { version = "=0.14.4-rc.4", path = "../api_common" }
|
lemmy_api_common = { version = "=0.15.0-rc.6", path = "../api_common" }
|
||||||
lemmy_websocket = { version = "=0.14.4-rc.4", path = "../websocket" }
|
lemmy_websocket = { version = "=0.15.0-rc.6", path = "../websocket" }
|
||||||
diesel = "1.4.8"
|
diesel = "1.4.8"
|
||||||
activitystreams-kinds = "0.1.2"
|
activitystreams-kinds = "0.1.2"
|
||||||
bcrypt = "0.10.1"
|
bcrypt = "0.10.1"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lemmy_apub_lib"
|
name = "lemmy_apub_lib"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A link aggregator for the fediverse"
|
description = "A link aggregator for the fediverse"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
|
@ -8,8 +8,8 @@ homepage = "https://join-lemmy.org/"
|
||||||
documentation = "https://join-lemmy.org/docs/en/index.html"
|
documentation = "https://join-lemmy.org/docs/en/index.html"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_utils = { version = "=0.14.4-rc.4", path = "../utils" }
|
lemmy_utils = { version = "=0.15.0-rc.6", path = "../utils" }
|
||||||
lemmy_apub_lib_derive = { version = "=0.14.4-rc.4", path = "../apub_lib_derive" }
|
lemmy_apub_lib_derive = { version = "=0.15.0-rc.6", path = "../apub_lib_derive" }
|
||||||
activitystreams = "0.7.0-alpha.14"
|
activitystreams = "0.7.0-alpha.14"
|
||||||
serde = { version = "1.0.131", features = ["derive"] }
|
serde = { version = "1.0.131", features = ["derive"] }
|
||||||
async-trait = "0.1.52"
|
async-trait = "0.1.52"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lemmy_apub_lib_derive"
|
name = "lemmy_apub_lib_derive"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A link aggregator for the fediverse"
|
description = "A link aggregator for the fediverse"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lemmy_db_schema"
|
name = "lemmy_db_schema"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A link aggregator for the fediverse"
|
description = "A link aggregator for the fediverse"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
|
@ -11,8 +11,8 @@ documentation = "https://join-lemmy.org/docs/en/index.html"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_utils = { version = "=0.14.4-rc.4", path = "../utils" }
|
lemmy_utils = { version = "=0.15.0-rc.6", path = "../utils" }
|
||||||
lemmy_apub_lib = { version = "=0.14.4-rc.4", path = "../apub_lib" }
|
lemmy_apub_lib = { version = "=0.15.0-rc.6", path = "../apub_lib" }
|
||||||
diesel = { version = "1.4.8", features = ["postgres","chrono","r2d2","serde_json"] }
|
diesel = { version = "1.4.8", features = ["postgres","chrono","r2d2","serde_json"] }
|
||||||
diesel_migrations = "1.4.0"
|
diesel_migrations = "1.4.0"
|
||||||
chrono = { version = "0.4.19", features = ["serde"] }
|
chrono = { version = "0.4.19", features = ["serde"] }
|
||||||
|
|
|
@ -65,6 +65,10 @@ mod tests {
|
||||||
enable_nsfw: None,
|
enable_nsfw: None,
|
||||||
updated: None,
|
updated: None,
|
||||||
community_creation_admin_only: Some(false),
|
community_creation_admin_only: Some(false),
|
||||||
|
require_email_verification: None,
|
||||||
|
require_application: None,
|
||||||
|
application_question: None,
|
||||||
|
private_instance: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
Site::create(&conn, &site_form).unwrap();
|
Site::create(&conn, &site_form).unwrap();
|
||||||
|
|
55
crates/db_schema/src/impls/email_verification.rs
Normal file
55
crates/db_schema/src/impls/email_verification.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use crate::{newtypes::LocalUserId, source::email_verification::*, traits::Crud};
|
||||||
|
use diesel::{
|
||||||
|
dsl::*,
|
||||||
|
insert_into,
|
||||||
|
result::Error,
|
||||||
|
ExpressionMethods,
|
||||||
|
PgConnection,
|
||||||
|
QueryDsl,
|
||||||
|
RunQueryDsl,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Crud for EmailVerification {
|
||||||
|
type Form = EmailVerificationForm;
|
||||||
|
type IdType = i32;
|
||||||
|
fn create(conn: &PgConnection, form: &EmailVerificationForm) -> Result<Self, Error> {
|
||||||
|
use crate::schema::email_verification::dsl::*;
|
||||||
|
insert_into(email_verification)
|
||||||
|
.values(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(conn: &PgConnection, id_: i32) -> Result<Self, Error> {
|
||||||
|
use crate::schema::email_verification::dsl::*;
|
||||||
|
email_verification.find(id_).first::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(conn: &PgConnection, id_: i32, form: &EmailVerificationForm) -> Result<Self, Error> {
|
||||||
|
use crate::schema::email_verification::dsl::*;
|
||||||
|
diesel::update(email_verification.find(id_))
|
||||||
|
.set(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(conn: &PgConnection, id_: i32) -> Result<usize, Error> {
|
||||||
|
use crate::schema::email_verification::dsl::*;
|
||||||
|
diesel::delete(email_verification.find(id_)).execute(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EmailVerification {
|
||||||
|
pub fn read_for_token(conn: &PgConnection, token: &str) -> Result<Self, Error> {
|
||||||
|
use crate::schema::email_verification::dsl::*;
|
||||||
|
email_verification
|
||||||
|
.filter(verification_token.eq(token))
|
||||||
|
.filter(published.gt(now - 7.days()))
|
||||||
|
.first::<Self>(conn)
|
||||||
|
}
|
||||||
|
pub fn delete_old_tokens_for_local_user(
|
||||||
|
conn: &PgConnection,
|
||||||
|
local_user_id_: LocalUserId,
|
||||||
|
) -> Result<usize, Error> {
|
||||||
|
use crate::schema::email_verification::dsl::*;
|
||||||
|
diesel::delete(email_verification.filter(local_user_id.eq(local_user_id_))).execute(conn)
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,6 +31,8 @@ mod safe_settings_type {
|
||||||
show_scores,
|
show_scores,
|
||||||
show_read_posts,
|
show_read_posts,
|
||||||
show_new_post_notifs,
|
show_new_post_notifs,
|
||||||
|
email_verified,
|
||||||
|
accepted_application,
|
||||||
);
|
);
|
||||||
|
|
||||||
impl ToSafeSettings for LocalUser {
|
impl ToSafeSettings for LocalUser {
|
||||||
|
@ -54,6 +56,8 @@ mod safe_settings_type {
|
||||||
show_scores,
|
show_scores,
|
||||||
show_read_posts,
|
show_read_posts,
|
||||||
show_new_post_notifs,
|
show_new_post_notifs,
|
||||||
|
email_verified,
|
||||||
|
accepted_application,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,8 +66,10 @@ mod safe_settings_type {
|
||||||
impl LocalUser {
|
impl LocalUser {
|
||||||
pub fn register(conn: &PgConnection, form: &LocalUserForm) -> Result<Self, Error> {
|
pub fn register(conn: &PgConnection, form: &LocalUserForm) -> Result<Self, Error> {
|
||||||
let mut edited_user = form.clone();
|
let mut edited_user = form.clone();
|
||||||
let password_hash =
|
let password_hash = form
|
||||||
hash(&form.password_encrypted, DEFAULT_COST).expect("Couldn't hash password");
|
.password_encrypted
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| hash(p, DEFAULT_COST).expect("Couldn't hash password"));
|
||||||
edited_user.password_encrypted = password_hash;
|
edited_user.password_encrypted = password_hash;
|
||||||
|
|
||||||
Self::create(conn, &edited_user)
|
Self::create(conn, &edited_user)
|
||||||
|
@ -83,6 +89,20 @@ impl LocalUser {
|
||||||
))
|
))
|
||||||
.get_result::<Self>(conn)
|
.get_result::<Self>(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_all_users_email_verified(conn: &PgConnection) -> Result<Vec<Self>, Error> {
|
||||||
|
diesel::update(local_user)
|
||||||
|
.set(email_verified.eq(true))
|
||||||
|
.get_results::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_all_users_registration_applications_accepted(
|
||||||
|
conn: &PgConnection,
|
||||||
|
) -> Result<Vec<Self>, Error> {
|
||||||
|
diesel::update(local_user)
|
||||||
|
.set(accepted_application.eq(true))
|
||||||
|
.get_results::<Self>(conn)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Crud for LocalUser {
|
impl Crud for LocalUser {
|
||||||
|
|
|
@ -3,6 +3,7 @@ pub mod comment;
|
||||||
pub mod comment_report;
|
pub mod comment_report;
|
||||||
pub mod community;
|
pub mod community;
|
||||||
pub mod community_block;
|
pub mod community_block;
|
||||||
|
pub mod email_verification;
|
||||||
pub mod local_user;
|
pub mod local_user;
|
||||||
pub mod moderator;
|
pub mod moderator;
|
||||||
pub mod password_reset_request;
|
pub mod password_reset_request;
|
||||||
|
@ -12,5 +13,6 @@ pub mod person_mention;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod post_report;
|
pub mod post_report;
|
||||||
pub mod private_message;
|
pub mod private_message;
|
||||||
|
pub mod registration_application;
|
||||||
pub mod secret;
|
pub mod secret;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
|
|
|
@ -93,8 +93,8 @@ mod tests {
|
||||||
let inserted_person = Person::create(&conn, &new_person).unwrap();
|
let inserted_person = Person::create(&conn, &new_person).unwrap();
|
||||||
|
|
||||||
let new_local_user = LocalUserForm {
|
let new_local_user = LocalUserForm {
|
||||||
person_id: inserted_person.id,
|
person_id: Some(inserted_person.id),
|
||||||
password_encrypted: "pass".to_string(),
|
password_encrypted: Some("pass".to_string()),
|
||||||
..LocalUserForm::default()
|
..LocalUserForm::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
42
crates/db_schema/src/impls/registration_application.rs
Normal file
42
crates/db_schema/src/impls/registration_application.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use crate::{newtypes::LocalUserId, source::registration_application::*, traits::Crud};
|
||||||
|
use diesel::{insert_into, result::Error, ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
|
||||||
|
|
||||||
|
impl Crud for RegistrationApplication {
|
||||||
|
type Form = RegistrationApplicationForm;
|
||||||
|
type IdType = i32;
|
||||||
|
fn create(conn: &PgConnection, form: &Self::Form) -> Result<Self, Error> {
|
||||||
|
use crate::schema::registration_application::dsl::*;
|
||||||
|
insert_into(registration_application)
|
||||||
|
.values(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(conn: &PgConnection, id_: Self::IdType) -> Result<Self, Error> {
|
||||||
|
use crate::schema::registration_application::dsl::*;
|
||||||
|
registration_application.find(id_).first::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(conn: &PgConnection, id_: Self::IdType, form: &Self::Form) -> Result<Self, Error> {
|
||||||
|
use crate::schema::registration_application::dsl::*;
|
||||||
|
diesel::update(registration_application.find(id_))
|
||||||
|
.set(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(conn: &PgConnection, id_: Self::IdType) -> Result<usize, Error> {
|
||||||
|
use crate::schema::registration_application::dsl::*;
|
||||||
|
diesel::delete(registration_application.find(id_)).execute(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegistrationApplication {
|
||||||
|
pub fn find_by_local_user_id(
|
||||||
|
conn: &PgConnection,
|
||||||
|
local_user_id_: LocalUserId,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
use crate::schema::registration_application::dsl::*;
|
||||||
|
registration_application
|
||||||
|
.filter(local_user_id.eq(local_user_id_))
|
||||||
|
.first::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,7 +43,9 @@ impl fmt::Display for CommentId {
|
||||||
)]
|
)]
|
||||||
pub struct CommunityId(pub i32);
|
pub struct CommunityId(pub i32);
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
|
#[derive(
|
||||||
|
Debug, Copy, Clone, Hash, Eq, PartialEq, Default, Serialize, Deserialize, DieselNewType,
|
||||||
|
)]
|
||||||
pub struct LocalUserId(pub i32);
|
pub struct LocalUserId(pub i32);
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
|
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
|
||||||
|
|
|
@ -157,6 +157,8 @@ table! {
|
||||||
show_scores -> Bool,
|
show_scores -> Bool,
|
||||||
show_read_posts -> Bool,
|
show_read_posts -> Bool,
|
||||||
show_new_post_notifs -> Bool,
|
show_new_post_notifs -> Bool,
|
||||||
|
email_verified -> Bool,
|
||||||
|
accepted_application -> Bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,6 +449,10 @@ table! {
|
||||||
banner -> Nullable<Varchar>,
|
banner -> Nullable<Varchar>,
|
||||||
description -> Nullable<Text>,
|
description -> Nullable<Text>,
|
||||||
community_creation_admin_only -> Bool,
|
community_creation_admin_only -> Bool,
|
||||||
|
require_email_verification -> Bool,
|
||||||
|
require_application -> Bool,
|
||||||
|
application_question -> Nullable<Text>,
|
||||||
|
private_instance -> Bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,6 +564,27 @@ table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
email_verification (id) {
|
||||||
|
id -> Int4,
|
||||||
|
local_user_id -> Int4,
|
||||||
|
email -> Text,
|
||||||
|
verification_token -> Varchar,
|
||||||
|
published -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
registration_application (id) {
|
||||||
|
id -> Int4,
|
||||||
|
local_user_id -> Int4,
|
||||||
|
answer -> Text,
|
||||||
|
admin_id -> Nullable<Int4>,
|
||||||
|
deny_reason -> Nullable<Text>,
|
||||||
|
published -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
joinable!(comment_alias_1 -> person_alias_1 (creator_id));
|
joinable!(comment_alias_1 -> person_alias_1 (creator_id));
|
||||||
joinable!(comment -> comment_alias_1 (parent_id));
|
joinable!(comment -> comment_alias_1 (parent_id));
|
||||||
joinable!(person_mention -> person_alias_1 (recipient_id));
|
joinable!(person_mention -> person_alias_1 (recipient_id));
|
||||||
|
@ -619,6 +646,9 @@ joinable!(post_saved -> person (person_id));
|
||||||
joinable!(post_saved -> post (post_id));
|
joinable!(post_saved -> post (post_id));
|
||||||
joinable!(site -> person (creator_id));
|
joinable!(site -> person (creator_id));
|
||||||
joinable!(site_aggregates -> site (site_id));
|
joinable!(site_aggregates -> site (site_id));
|
||||||
|
joinable!(email_verification -> local_user (local_user_id));
|
||||||
|
joinable!(registration_application -> local_user (local_user_id));
|
||||||
|
joinable!(registration_application -> person (admin_id));
|
||||||
|
|
||||||
allow_tables_to_appear_in_same_query!(
|
allow_tables_to_appear_in_same_query!(
|
||||||
activity,
|
activity,
|
||||||
|
@ -662,4 +692,6 @@ allow_tables_to_appear_in_same_query!(
|
||||||
comment_alias_1,
|
comment_alias_1,
|
||||||
person_alias_1,
|
person_alias_1,
|
||||||
person_alias_2,
|
person_alias_2,
|
||||||
|
email_verification,
|
||||||
|
registration_application
|
||||||
);
|
);
|
||||||
|
|
19
crates/db_schema/src/source/email_verification.rs
Normal file
19
crates/db_schema/src/source/email_verification.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
use crate::{newtypes::LocalUserId, schema::email_verification};
|
||||||
|
|
||||||
|
#[derive(Queryable, Identifiable, Clone)]
|
||||||
|
#[table_name = "email_verification"]
|
||||||
|
pub struct EmailVerification {
|
||||||
|
pub id: i32,
|
||||||
|
pub local_user_id: LocalUserId,
|
||||||
|
pub email: String,
|
||||||
|
pub verification_code: String,
|
||||||
|
pub published: chrono::NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable, AsChangeset)]
|
||||||
|
#[table_name = "email_verification"]
|
||||||
|
pub struct EmailVerificationForm {
|
||||||
|
pub local_user_id: LocalUserId,
|
||||||
|
pub email: String,
|
||||||
|
pub verification_token: String,
|
||||||
|
}
|
|
@ -23,14 +23,16 @@ pub struct LocalUser {
|
||||||
pub show_scores: bool,
|
pub show_scores: bool,
|
||||||
pub show_read_posts: bool,
|
pub show_read_posts: bool,
|
||||||
pub show_new_post_notifs: bool,
|
pub show_new_post_notifs: bool,
|
||||||
|
pub email_verified: bool,
|
||||||
|
pub accepted_application: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO redo these, check table defaults
|
// TODO redo these, check table defaults
|
||||||
#[derive(Insertable, AsChangeset, Clone, Default)]
|
#[derive(Insertable, AsChangeset, Clone, Default)]
|
||||||
#[table_name = "local_user"]
|
#[table_name = "local_user"]
|
||||||
pub struct LocalUserForm {
|
pub struct LocalUserForm {
|
||||||
pub person_id: PersonId,
|
pub person_id: Option<PersonId>,
|
||||||
pub password_encrypted: String,
|
pub password_encrypted: Option<String>,
|
||||||
pub email: Option<Option<String>>,
|
pub email: Option<Option<String>>,
|
||||||
pub show_nsfw: Option<bool>,
|
pub show_nsfw: Option<bool>,
|
||||||
pub theme: Option<String>,
|
pub theme: Option<String>,
|
||||||
|
@ -43,6 +45,8 @@ pub struct LocalUserForm {
|
||||||
pub show_scores: Option<bool>,
|
pub show_scores: Option<bool>,
|
||||||
pub show_read_posts: Option<bool>,
|
pub show_read_posts: Option<bool>,
|
||||||
pub show_new_post_notifs: Option<bool>,
|
pub show_new_post_notifs: Option<bool>,
|
||||||
|
pub email_verified: Option<bool>,
|
||||||
|
pub accepted_application: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A local user view that removes password encrypted
|
/// A local user view that removes password encrypted
|
||||||
|
@ -64,4 +68,6 @@ pub struct LocalUserSettings {
|
||||||
pub show_scores: bool,
|
pub show_scores: bool,
|
||||||
pub show_read_posts: bool,
|
pub show_read_posts: bool,
|
||||||
pub show_new_post_notifs: bool,
|
pub show_new_post_notifs: bool,
|
||||||
|
pub email_verified: bool,
|
||||||
|
pub accepted_application: bool,
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ pub mod comment;
|
||||||
pub mod comment_report;
|
pub mod comment_report;
|
||||||
pub mod community;
|
pub mod community;
|
||||||
pub mod community_block;
|
pub mod community_block;
|
||||||
|
pub mod email_verification;
|
||||||
pub mod local_user;
|
pub mod local_user;
|
||||||
pub mod moderator;
|
pub mod moderator;
|
||||||
pub mod password_reset_request;
|
pub mod password_reset_request;
|
||||||
|
@ -12,5 +13,6 @@ pub mod person_mention;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod post_report;
|
pub mod post_report;
|
||||||
pub mod private_message;
|
pub mod private_message;
|
||||||
|
pub mod registration_application;
|
||||||
pub mod secret;
|
pub mod secret;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
|
|
25
crates/db_schema/src/source/registration_application.rs
Normal file
25
crates/db_schema/src/source/registration_application.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
use crate::{
|
||||||
|
newtypes::{LocalUserId, PersonId},
|
||||||
|
schema::registration_application,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
|
||||||
|
#[table_name = "registration_application"]
|
||||||
|
pub struct RegistrationApplication {
|
||||||
|
pub id: i32,
|
||||||
|
pub local_user_id: LocalUserId,
|
||||||
|
pub answer: String,
|
||||||
|
pub admin_id: Option<PersonId>,
|
||||||
|
pub deny_reason: Option<String>,
|
||||||
|
pub published: chrono::NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable, AsChangeset, Default)]
|
||||||
|
#[table_name = "registration_application"]
|
||||||
|
pub struct RegistrationApplicationForm {
|
||||||
|
pub local_user_id: Option<LocalUserId>,
|
||||||
|
pub answer: Option<String>,
|
||||||
|
pub admin_id: Option<PersonId>,
|
||||||
|
pub deny_reason: Option<Option<String>>,
|
||||||
|
}
|
|
@ -20,9 +20,13 @@ pub struct Site {
|
||||||
pub banner: Option<DbUrl>,
|
pub banner: Option<DbUrl>,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub community_creation_admin_only: bool,
|
pub community_creation_admin_only: bool,
|
||||||
|
pub require_email_verification: bool,
|
||||||
|
pub require_application: bool,
|
||||||
|
pub application_question: Option<String>,
|
||||||
|
pub private_instance: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Insertable, AsChangeset)]
|
#[derive(Insertable, AsChangeset, Default)]
|
||||||
#[table_name = "site"]
|
#[table_name = "site"]
|
||||||
pub struct SiteForm {
|
pub struct SiteForm {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -37,4 +41,8 @@ pub struct SiteForm {
|
||||||
pub banner: Option<Option<DbUrl>>,
|
pub banner: Option<Option<DbUrl>>,
|
||||||
pub description: Option<Option<String>>,
|
pub description: Option<Option<String>>,
|
||||||
pub community_creation_admin_only: Option<bool>,
|
pub community_creation_admin_only: Option<bool>,
|
||||||
|
pub require_email_verification: Option<bool>,
|
||||||
|
pub require_application: Option<bool>,
|
||||||
|
pub application_question: Option<Option<String>>,
|
||||||
|
pub private_instance: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lemmy_db_views"
|
name = "lemmy_db_views"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A link aggregator for the fediverse"
|
description = "A link aggregator for the fediverse"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
|
@ -11,7 +11,7 @@ documentation = "https://join-lemmy.org/docs/en/index.html"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_db_schema = { version = "=0.14.4-rc.4", path = "../db_schema" }
|
lemmy_db_schema = { version = "=0.15.0-rc.6", path = "../db_schema" }
|
||||||
diesel = { version = "1.4.8", features = ["postgres","chrono","r2d2","serde_json"] }
|
diesel = { version = "1.4.8", features = ["postgres","chrono","r2d2","serde_json"] }
|
||||||
serde = { version = "1.0.131", features = ["derive"] }
|
serde = { version = "1.0.131", features = ["derive"] }
|
||||||
tracing = "0.1.29"
|
tracing = "0.1.29"
|
||||||
|
|
|
@ -7,4 +7,5 @@ pub mod local_user_view;
|
||||||
pub mod post_report_view;
|
pub mod post_report_view;
|
||||||
pub mod post_view;
|
pub mod post_view;
|
||||||
pub mod private_message_view;
|
pub mod private_message_view;
|
||||||
|
pub mod registration_application_view;
|
||||||
pub mod site_view;
|
pub mod site_view;
|
||||||
|
|
396
crates/db_views/src/registration_application_view.rs
Normal file
396
crates/db_views/src/registration_application_view.rs
Normal file
|
@ -0,0 +1,396 @@
|
||||||
|
use diesel::{dsl::count, result::Error, *};
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
limit_and_offset,
|
||||||
|
schema::{local_user, person, person_alias_1, registration_application},
|
||||||
|
source::{
|
||||||
|
local_user::{LocalUser, LocalUserSettings},
|
||||||
|
person::{Person, PersonAlias1, PersonSafe, PersonSafeAlias1},
|
||||||
|
registration_application::RegistrationApplication,
|
||||||
|
},
|
||||||
|
traits::{MaybeOptional, ToSafe, ToSafeSettings, ViewToVec},
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct RegistrationApplicationView {
|
||||||
|
pub registration_application: RegistrationApplication,
|
||||||
|
pub creator_local_user: LocalUserSettings,
|
||||||
|
pub creator: PersonSafe,
|
||||||
|
pub admin: Option<PersonSafeAlias1>,
|
||||||
|
}
|
||||||
|
|
||||||
|
type RegistrationApplicationViewTuple = (
|
||||||
|
RegistrationApplication,
|
||||||
|
LocalUserSettings,
|
||||||
|
PersonSafe,
|
||||||
|
Option<PersonSafeAlias1>,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl RegistrationApplicationView {
|
||||||
|
pub fn read(conn: &PgConnection, registration_application_id: i32) -> Result<Self, Error> {
|
||||||
|
let (registration_application, creator_local_user, creator, admin) =
|
||||||
|
registration_application::table
|
||||||
|
.find(registration_application_id)
|
||||||
|
.inner_join(
|
||||||
|
local_user::table.on(registration_application::local_user_id.eq(local_user::id)),
|
||||||
|
)
|
||||||
|
.inner_join(person::table.on(local_user::person_id.eq(person::id)))
|
||||||
|
.left_join(
|
||||||
|
person_alias_1::table
|
||||||
|
.on(registration_application::admin_id.eq(person_alias_1::id.nullable())),
|
||||||
|
)
|
||||||
|
.order_by(registration_application::published.desc())
|
||||||
|
.select((
|
||||||
|
registration_application::all_columns,
|
||||||
|
LocalUser::safe_settings_columns_tuple(),
|
||||||
|
Person::safe_columns_tuple(),
|
||||||
|
PersonAlias1::safe_columns_tuple().nullable(),
|
||||||
|
))
|
||||||
|
.first::<RegistrationApplicationViewTuple>(conn)?;
|
||||||
|
|
||||||
|
Ok(RegistrationApplicationView {
|
||||||
|
registration_application,
|
||||||
|
creator_local_user,
|
||||||
|
creator,
|
||||||
|
admin,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current unread registration_application count
|
||||||
|
pub fn get_unread_count(conn: &PgConnection, verified_email_only: bool) -> Result<i64, Error> {
|
||||||
|
let mut query = registration_application::table
|
||||||
|
.inner_join(local_user::table.on(registration_application::local_user_id.eq(local_user::id)))
|
||||||
|
.inner_join(person::table.on(local_user::person_id.eq(person::id)))
|
||||||
|
.left_join(
|
||||||
|
person_alias_1::table
|
||||||
|
.on(registration_application::admin_id.eq(person_alias_1::id.nullable())),
|
||||||
|
)
|
||||||
|
.filter(registration_application::admin_id.is_null())
|
||||||
|
.into_boxed();
|
||||||
|
|
||||||
|
if verified_email_only {
|
||||||
|
query = query.filter(local_user::email_verified.eq(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
query
|
||||||
|
.select(count(registration_application::id))
|
||||||
|
.first::<i64>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RegistrationApplicationQueryBuilder<'a> {
|
||||||
|
conn: &'a PgConnection,
|
||||||
|
unread_only: Option<bool>,
|
||||||
|
verified_email_only: Option<bool>,
|
||||||
|
page: Option<i64>,
|
||||||
|
limit: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RegistrationApplicationQueryBuilder<'a> {
|
||||||
|
pub fn create(conn: &'a PgConnection) -> Self {
|
||||||
|
RegistrationApplicationQueryBuilder {
|
||||||
|
conn,
|
||||||
|
unread_only: None,
|
||||||
|
verified_email_only: None,
|
||||||
|
page: None,
|
||||||
|
limit: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unread_only<T: MaybeOptional<bool>>(mut self, unread_only: T) -> Self {
|
||||||
|
self.unread_only = unread_only.get_optional();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verified_email_only<T: MaybeOptional<bool>>(mut self, verified_email_only: T) -> Self {
|
||||||
|
self.verified_email_only = verified_email_only.get_optional();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
|
||||||
|
self.page = page.get_optional();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
|
||||||
|
self.limit = limit.get_optional();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list(self) -> Result<Vec<RegistrationApplicationView>, Error> {
|
||||||
|
let mut query = registration_application::table
|
||||||
|
.inner_join(local_user::table.on(registration_application::local_user_id.eq(local_user::id)))
|
||||||
|
.inner_join(person::table.on(local_user::person_id.eq(person::id)))
|
||||||
|
.left_join(
|
||||||
|
person_alias_1::table
|
||||||
|
.on(registration_application::admin_id.eq(person_alias_1::id.nullable())),
|
||||||
|
)
|
||||||
|
.order_by(registration_application::published.desc())
|
||||||
|
.select((
|
||||||
|
registration_application::all_columns,
|
||||||
|
LocalUser::safe_settings_columns_tuple(),
|
||||||
|
Person::safe_columns_tuple(),
|
||||||
|
PersonAlias1::safe_columns_tuple().nullable(),
|
||||||
|
))
|
||||||
|
.into_boxed();
|
||||||
|
|
||||||
|
if self.unread_only.unwrap_or(false) {
|
||||||
|
query = query.filter(registration_application::admin_id.is_null())
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.verified_email_only.unwrap_or(false) {
|
||||||
|
query = query.filter(local_user::email_verified.eq(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
let (limit, offset) = limit_and_offset(self.page, self.limit);
|
||||||
|
|
||||||
|
query = query
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset)
|
||||||
|
.order_by(registration_application::published.desc());
|
||||||
|
|
||||||
|
let res = query.load::<RegistrationApplicationViewTuple>(self.conn)?;
|
||||||
|
|
||||||
|
Ok(RegistrationApplicationView::from_tuple_to_vec(res))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewToVec for RegistrationApplicationView {
|
||||||
|
type DbTuple = RegistrationApplicationViewTuple;
|
||||||
|
fn from_tuple_to_vec(items: Vec<Self::DbTuple>) -> Vec<Self> {
|
||||||
|
items
|
||||||
|
.iter()
|
||||||
|
.map(|a| Self {
|
||||||
|
registration_application: a.0.to_owned(),
|
||||||
|
creator_local_user: a.1.to_owned(),
|
||||||
|
creator: a.2.to_owned(),
|
||||||
|
admin: a.3.to_owned(),
|
||||||
|
})
|
||||||
|
.collect::<Vec<Self>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::registration_application_view::{
|
||||||
|
RegistrationApplicationQueryBuilder,
|
||||||
|
RegistrationApplicationView,
|
||||||
|
};
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
establish_unpooled_connection,
|
||||||
|
source::{
|
||||||
|
local_user::{LocalUser, LocalUserForm, LocalUserSettings},
|
||||||
|
person::*,
|
||||||
|
registration_application::{RegistrationApplication, RegistrationApplicationForm},
|
||||||
|
},
|
||||||
|
traits::Crud,
|
||||||
|
};
|
||||||
|
use serial_test::serial;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_crud() {
|
||||||
|
let conn = establish_unpooled_connection();
|
||||||
|
|
||||||
|
let timmy_person_form = PersonForm {
|
||||||
|
name: "timmy_rav".into(),
|
||||||
|
admin: Some(true),
|
||||||
|
..PersonForm::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_timmy_person = Person::create(&conn, &timmy_person_form).unwrap();
|
||||||
|
|
||||||
|
let timmy_local_user_form = LocalUserForm {
|
||||||
|
person_id: Some(inserted_timmy_person.id),
|
||||||
|
password_encrypted: Some("nada".to_string()),
|
||||||
|
..LocalUserForm::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let _inserted_timmy_local_user = LocalUser::create(&conn, &timmy_local_user_form).unwrap();
|
||||||
|
|
||||||
|
let sara_person_form = PersonForm {
|
||||||
|
name: "sara_rav".into(),
|
||||||
|
..PersonForm::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_sara_person = Person::create(&conn, &sara_person_form).unwrap();
|
||||||
|
|
||||||
|
let sara_local_user_form = LocalUserForm {
|
||||||
|
person_id: Some(inserted_sara_person.id),
|
||||||
|
password_encrypted: Some("nada".to_string()),
|
||||||
|
..LocalUserForm::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_sara_local_user = LocalUser::create(&conn, &sara_local_user_form).unwrap();
|
||||||
|
|
||||||
|
// Sara creates an application
|
||||||
|
let sara_app_form = RegistrationApplicationForm {
|
||||||
|
local_user_id: Some(inserted_sara_local_user.id),
|
||||||
|
answer: Some("LET ME IIIIINN".to_string()),
|
||||||
|
..RegistrationApplicationForm::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let sara_app = RegistrationApplication::create(&conn, &sara_app_form).unwrap();
|
||||||
|
|
||||||
|
let read_sara_app_view = RegistrationApplicationView::read(&conn, sara_app.id).unwrap();
|
||||||
|
|
||||||
|
let jess_person_form = PersonForm {
|
||||||
|
name: "jess_rav".into(),
|
||||||
|
..PersonForm::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_jess_person = Person::create(&conn, &jess_person_form).unwrap();
|
||||||
|
|
||||||
|
let jess_local_user_form = LocalUserForm {
|
||||||
|
person_id: Some(inserted_jess_person.id),
|
||||||
|
password_encrypted: Some("nada".to_string()),
|
||||||
|
..LocalUserForm::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_jess_local_user = LocalUser::create(&conn, &jess_local_user_form).unwrap();
|
||||||
|
|
||||||
|
// Sara creates an application
|
||||||
|
let jess_app_form = RegistrationApplicationForm {
|
||||||
|
local_user_id: Some(inserted_jess_local_user.id),
|
||||||
|
answer: Some("LET ME IIIIINN".to_string()),
|
||||||
|
..RegistrationApplicationForm::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let jess_app = RegistrationApplication::create(&conn, &jess_app_form).unwrap();
|
||||||
|
|
||||||
|
let read_jess_app_view = RegistrationApplicationView::read(&conn, jess_app.id).unwrap();
|
||||||
|
|
||||||
|
let mut expected_sara_app_view = RegistrationApplicationView {
|
||||||
|
registration_application: sara_app.to_owned(),
|
||||||
|
creator_local_user: LocalUserSettings {
|
||||||
|
id: inserted_sara_local_user.id,
|
||||||
|
person_id: inserted_sara_local_user.person_id,
|
||||||
|
email: inserted_sara_local_user.email,
|
||||||
|
show_nsfw: inserted_sara_local_user.show_nsfw,
|
||||||
|
theme: inserted_sara_local_user.theme,
|
||||||
|
default_sort_type: inserted_sara_local_user.default_sort_type,
|
||||||
|
default_listing_type: inserted_sara_local_user.default_listing_type,
|
||||||
|
lang: inserted_sara_local_user.lang,
|
||||||
|
show_avatars: inserted_sara_local_user.show_avatars,
|
||||||
|
send_notifications_to_email: inserted_sara_local_user.send_notifications_to_email,
|
||||||
|
validator_time: inserted_sara_local_user.validator_time,
|
||||||
|
show_bot_accounts: inserted_sara_local_user.show_bot_accounts,
|
||||||
|
show_scores: inserted_sara_local_user.show_scores,
|
||||||
|
show_read_posts: inserted_sara_local_user.show_read_posts,
|
||||||
|
show_new_post_notifs: inserted_sara_local_user.show_new_post_notifs,
|
||||||
|
email_verified: inserted_sara_local_user.email_verified,
|
||||||
|
accepted_application: inserted_sara_local_user.accepted_application,
|
||||||
|
},
|
||||||
|
creator: PersonSafe {
|
||||||
|
id: inserted_sara_person.id,
|
||||||
|
name: inserted_sara_person.name.to_owned(),
|
||||||
|
display_name: None,
|
||||||
|
published: inserted_sara_person.published,
|
||||||
|
avatar: None,
|
||||||
|
actor_id: inserted_sara_person.actor_id.to_owned(),
|
||||||
|
local: true,
|
||||||
|
banned: false,
|
||||||
|
deleted: false,
|
||||||
|
admin: false,
|
||||||
|
bot_account: false,
|
||||||
|
bio: None,
|
||||||
|
banner: None,
|
||||||
|
updated: None,
|
||||||
|
inbox_url: inserted_sara_person.inbox_url.to_owned(),
|
||||||
|
shared_inbox_url: None,
|
||||||
|
matrix_user_id: None,
|
||||||
|
},
|
||||||
|
admin: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(read_sara_app_view, expected_sara_app_view);
|
||||||
|
|
||||||
|
// Do a batch read of the applications
|
||||||
|
let apps = RegistrationApplicationQueryBuilder::create(&conn)
|
||||||
|
.unread_only(true)
|
||||||
|
.list()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
apps,
|
||||||
|
[
|
||||||
|
read_jess_app_view.to_owned(),
|
||||||
|
expected_sara_app_view.to_owned()
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make sure the counts are correct
|
||||||
|
let unread_count = RegistrationApplicationView::get_unread_count(&conn, false).unwrap();
|
||||||
|
assert_eq!(unread_count, 2);
|
||||||
|
|
||||||
|
// Approve the application
|
||||||
|
let approve_form = RegistrationApplicationForm {
|
||||||
|
admin_id: Some(inserted_timmy_person.id),
|
||||||
|
deny_reason: None,
|
||||||
|
..RegistrationApplicationForm::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
RegistrationApplication::update(&conn, sara_app.id, &approve_form).unwrap();
|
||||||
|
|
||||||
|
// Update the local_user row
|
||||||
|
let approve_local_user_form = LocalUserForm {
|
||||||
|
accepted_application: Some(true),
|
||||||
|
..LocalUserForm::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
LocalUser::update(&conn, inserted_sara_local_user.id, &approve_local_user_form).unwrap();
|
||||||
|
|
||||||
|
let read_sara_app_view_after_approve =
|
||||||
|
RegistrationApplicationView::read(&conn, sara_app.id).unwrap();
|
||||||
|
|
||||||
|
// Make sure the columns changed
|
||||||
|
expected_sara_app_view
|
||||||
|
.creator_local_user
|
||||||
|
.accepted_application = true;
|
||||||
|
expected_sara_app_view.registration_application.admin_id = Some(inserted_timmy_person.id);
|
||||||
|
|
||||||
|
expected_sara_app_view.admin = Some(PersonSafeAlias1 {
|
||||||
|
id: inserted_timmy_person.id,
|
||||||
|
name: inserted_timmy_person.name.to_owned(),
|
||||||
|
display_name: None,
|
||||||
|
published: inserted_timmy_person.published,
|
||||||
|
avatar: None,
|
||||||
|
actor_id: inserted_timmy_person.actor_id.to_owned(),
|
||||||
|
local: true,
|
||||||
|
banned: false,
|
||||||
|
deleted: false,
|
||||||
|
admin: true,
|
||||||
|
bot_account: false,
|
||||||
|
bio: None,
|
||||||
|
banner: None,
|
||||||
|
updated: None,
|
||||||
|
inbox_url: inserted_timmy_person.inbox_url.to_owned(),
|
||||||
|
shared_inbox_url: None,
|
||||||
|
matrix_user_id: None,
|
||||||
|
});
|
||||||
|
assert_eq!(read_sara_app_view_after_approve, expected_sara_app_view);
|
||||||
|
|
||||||
|
// Do a batch read of apps again
|
||||||
|
// It should show only jessicas which is unresolved
|
||||||
|
let apps_after_resolve = RegistrationApplicationQueryBuilder::create(&conn)
|
||||||
|
.unread_only(true)
|
||||||
|
.list()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(apps_after_resolve, vec![read_jess_app_view]);
|
||||||
|
|
||||||
|
// Make sure the counts are correct
|
||||||
|
let unread_count_after_approve =
|
||||||
|
RegistrationApplicationView::get_unread_count(&conn, false).unwrap();
|
||||||
|
assert_eq!(unread_count_after_approve, 1);
|
||||||
|
|
||||||
|
// Make sure the not undenied_only has all the apps
|
||||||
|
let all_apps = RegistrationApplicationQueryBuilder::create(&conn)
|
||||||
|
.list()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(all_apps.len(), 2);
|
||||||
|
|
||||||
|
Person::delete(&conn, inserted_timmy_person.id).unwrap();
|
||||||
|
Person::delete(&conn, inserted_sara_person.id).unwrap();
|
||||||
|
Person::delete(&conn, inserted_jess_person.id).unwrap();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lemmy_db_views_actor"
|
name = "lemmy_db_views_actor"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A link aggregator for the fediverse"
|
description = "A link aggregator for the fediverse"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
|
@ -11,6 +11,6 @@ documentation = "https://join-lemmy.org/docs/en/index.html"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_db_schema = { version = "=0.14.4-rc.4", path = "../db_schema" }
|
lemmy_db_schema = { version = "=0.15.0-rc.6", path = "../db_schema" }
|
||||||
diesel = { version = "1.4.8", features = ["postgres","chrono","r2d2","serde_json"] }
|
diesel = { version = "1.4.8", features = ["postgres","chrono","r2d2","serde_json"] }
|
||||||
serde = { version = "1.0.131", features = ["derive"] }
|
serde = { version = "1.0.131", features = ["derive"] }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lemmy_db_views_moderator"
|
name = "lemmy_db_views_moderator"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A link aggregator for the fediverse"
|
description = "A link aggregator for the fediverse"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
|
@ -11,6 +11,6 @@ documentation = "https://join-lemmy.org/docs/en/index.html"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_db_schema = { version = "=0.14.4-rc.4", path = "../db_schema" }
|
lemmy_db_schema = { version = "=0.15.0-rc.6", path = "../db_schema" }
|
||||||
diesel = { version = "1.4.8", features = ["postgres","chrono","r2d2","serde_json"] }
|
diesel = { version = "1.4.8", features = ["postgres","chrono","r2d2","serde_json"] }
|
||||||
serde = { version = "1.0.131", features = ["derive"] }
|
serde = { version = "1.0.131", features = ["derive"] }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lemmy_routes"
|
name = "lemmy_routes"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A link aggregator for the fediverse"
|
description = "A link aggregator for the fediverse"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
|
@ -11,13 +11,13 @@ documentation = "https://join-lemmy.org/docs/en/index.html"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_utils = { version = "=0.14.4-rc.4", path = "../utils" }
|
lemmy_utils = { version = "=0.15.0-rc.6", path = "../utils" }
|
||||||
lemmy_websocket = { version = "=0.14.4-rc.4", path = "../websocket" }
|
lemmy_websocket = { version = "=0.15.0-rc.6", path = "../websocket" }
|
||||||
lemmy_db_views = { version = "=0.14.4-rc.4", path = "../db_views" }
|
lemmy_db_views = { version = "=0.15.0-rc.6", path = "../db_views" }
|
||||||
lemmy_db_views_actor = { version = "=0.14.4-rc.4", path = "../db_views_actor" }
|
lemmy_db_views_actor = { version = "=0.15.0-rc.6", path = "../db_views_actor" }
|
||||||
lemmy_db_schema = { version = "=0.14.4-rc.4", path = "../db_schema" }
|
lemmy_db_schema = { version = "=0.15.0-rc.6", path = "../db_schema" }
|
||||||
lemmy_api_common = { version = "=0.14.4-rc.4", path = "../api_common" }
|
lemmy_api_common = { version = "=0.15.0-rc.6", path = "../api_common" }
|
||||||
lemmy_apub = { version = "=0.14.4-rc.4", path = "../apub" }
|
lemmy_apub = { version = "=0.15.0-rc.6", path = "../apub" }
|
||||||
diesel = "1.4.8"
|
diesel = "1.4.8"
|
||||||
actix = "0.12.0"
|
actix = "0.12.0"
|
||||||
actix-web = { version = "4.0.0-beta.14", default-features = false, features = ["rustls"] }
|
actix-web = { version = "4.0.0-beta.14", default-features = false, features = ["rustls"] }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lemmy_utils"
|
name = "lemmy_utils"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A link aggregator for the fediverse"
|
description = "A link aggregator for the fediverse"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::settings::structs::Settings;
|
use crate::{settings::structs::Settings, LemmyError};
|
||||||
use lettre::{
|
use lettre::{
|
||||||
message::{header, Mailbox, MultiPart, SinglePart},
|
message::{header, Mailbox, MultiPart, SinglePart},
|
||||||
transport::smtp::{
|
transport::smtp::{
|
||||||
|
@ -20,12 +20,21 @@ pub fn send_email(
|
||||||
to_username: &str,
|
to_username: &str,
|
||||||
html: &str,
|
html: &str,
|
||||||
settings: &Settings,
|
settings: &Settings,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), LemmyError> {
|
||||||
let email_config = settings.email.to_owned().ok_or("no_email_setup")?;
|
let email_config = settings
|
||||||
|
.email
|
||||||
|
.to_owned()
|
||||||
|
.ok_or_else(|| LemmyError::from_message("no_email_setup"))?;
|
||||||
let domain = settings.hostname.to_owned();
|
let domain = settings.hostname.to_owned();
|
||||||
|
|
||||||
let (smtp_server, smtp_port) = {
|
let (smtp_server, smtp_port) = {
|
||||||
let email_and_port = email_config.smtp_server.split(':').collect::<Vec<&str>>();
|
let email_and_port = email_config.smtp_server.split(':').collect::<Vec<&str>>();
|
||||||
|
if email_and_port.len() == 1 {
|
||||||
|
return Err(LemmyError::from_message(
|
||||||
|
"email.smtp_server needs a port, IE smtp.xxx.com:465",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
(
|
(
|
||||||
email_and_port[0],
|
email_and_port[0],
|
||||||
email_and_port[1]
|
email_and_port[1]
|
||||||
|
@ -87,6 +96,6 @@ pub fn send_email(
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(e) => Err(e.to_string()),
|
Err(e) => Err(LemmyError::from(e).with_message("email_send_failed")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -190,4 +190,12 @@ pub struct SetupConfig {
|
||||||
pub enable_nsfw: Option<bool>,
|
pub enable_nsfw: Option<bool>,
|
||||||
#[default(None)]
|
#[default(None)]
|
||||||
pub community_creation_admin_only: Option<bool>,
|
pub community_creation_admin_only: Option<bool>,
|
||||||
|
#[default(None)]
|
||||||
|
pub require_email_verification: Option<bool>,
|
||||||
|
#[default(None)]
|
||||||
|
pub require_application: Option<bool>,
|
||||||
|
#[default(None)]
|
||||||
|
pub application_question: Option<String>,
|
||||||
|
#[default(None)]
|
||||||
|
pub private_instance: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lemmy_websocket"
|
name = "lemmy_websocket"
|
||||||
version = "0.14.4-rc.4"
|
version = "0.15.0-rc.6"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A link aggregator for the fediverse"
|
description = "A link aggregator for the fediverse"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
|
@ -13,11 +13,11 @@ path = "src/lib.rs"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_utils = { version = "=0.14.4-rc.4", path = "../utils" }
|
lemmy_utils = { version = "=0.15.0-rc.6", path = "../utils" }
|
||||||
lemmy_api_common = { version = "=0.14.4-rc.4", path = "../api_common" }
|
lemmy_api_common = { version = "=0.15.0-rc.6", path = "../api_common" }
|
||||||
lemmy_db_schema = { version = "=0.14.4-rc.4", path = "../db_schema" }
|
lemmy_db_schema = { version = "=0.15.0-rc.6", path = "../db_schema" }
|
||||||
lemmy_db_views = { version = "=0.14.4-rc.4", path = "../db_views" }
|
lemmy_db_views = { version = "=0.15.0-rc.6", path = "../db_views" }
|
||||||
lemmy_db_views_actor = { version = "=0.14.4-rc.4", path = "../db_views_actor" }
|
lemmy_db_views_actor = { version = "=0.15.0-rc.6", path = "../db_views_actor" }
|
||||||
reqwest = { version = "0.11.7", features = ["json"] }
|
reqwest = { version = "0.11.7", features = ["json"] }
|
||||||
reqwest-middleware = "0.1.3"
|
reqwest-middleware = "0.1.3"
|
||||||
tracing = "0.1.29"
|
tracing = "0.1.29"
|
||||||
|
|
|
@ -117,6 +117,7 @@ pub enum UserOperation {
|
||||||
ListPostReports,
|
ListPostReports,
|
||||||
GetReportCount,
|
GetReportCount,
|
||||||
GetUnreadCount,
|
GetUnreadCount,
|
||||||
|
VerifyEmail,
|
||||||
FollowCommunity,
|
FollowCommunity,
|
||||||
GetReplies,
|
GetReplies,
|
||||||
GetPersonMentions,
|
GetPersonMentions,
|
||||||
|
@ -125,6 +126,9 @@ pub enum UserOperation {
|
||||||
BanFromCommunity,
|
BanFromCommunity,
|
||||||
AddModToCommunity,
|
AddModToCommunity,
|
||||||
AddAdmin,
|
AddAdmin,
|
||||||
|
GetUnreadRegistrationApplicationCount,
|
||||||
|
ListRegistrationApplications,
|
||||||
|
ApproveRegistrationApplication,
|
||||||
BanPerson,
|
BanPerson,
|
||||||
Search,
|
Search,
|
||||||
ResolveObject,
|
ResolveObject,
|
||||||
|
|
|
@ -10,6 +10,7 @@ use lemmy_api_common::{
|
||||||
community::CommunityResponse,
|
community::CommunityResponse,
|
||||||
person::PrivateMessageResponse,
|
person::PrivateMessageResponse,
|
||||||
post::PostResponse,
|
post::PostResponse,
|
||||||
|
send_email_to_user,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId, PrivateMessageId},
|
newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId, PrivateMessageId},
|
||||||
|
@ -28,14 +29,7 @@ use lemmy_db_views::{
|
||||||
private_message_view::PrivateMessageView,
|
private_message_view::PrivateMessageView,
|
||||||
};
|
};
|
||||||
use lemmy_db_views_actor::community_view::CommunityView;
|
use lemmy_db_views_actor::community_view::CommunityView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{utils::MentionData, ConnectionId, LemmyError};
|
||||||
email::send_email,
|
|
||||||
settings::structs::Settings,
|
|
||||||
utils::MentionData,
|
|
||||||
ConnectionId,
|
|
||||||
LemmyError,
|
|
||||||
};
|
|
||||||
use tracing::error;
|
|
||||||
|
|
||||||
pub async fn send_post_ws_message<OP: ToString + Send + OperationType + 'static>(
|
pub async fn send_post_ws_message<OP: ToString + Send + OperationType + 'static>(
|
||||||
post_id: PostId,
|
post_id: PostId,
|
||||||
|
@ -296,39 +290,3 @@ pub async fn send_local_notifs(
|
||||||
};
|
};
|
||||||
Ok(recipient_ids)
|
Ok(recipient_ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_email_to_user(
|
|
||||||
local_user_view: &LocalUserView,
|
|
||||||
subject_text: &str,
|
|
||||||
body_text: &str,
|
|
||||||
comment_content: &str,
|
|
||||||
settings: &Settings,
|
|
||||||
) {
|
|
||||||
if local_user_view.person.banned || !local_user_view.local_user.send_notifications_to_email {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(user_email) = &local_user_view.local_user.email {
|
|
||||||
let subject = &format!(
|
|
||||||
"{} - {} {}",
|
|
||||||
subject_text, settings.hostname, local_user_view.person.name,
|
|
||||||
);
|
|
||||||
let html = &format!(
|
|
||||||
"<h1>{}</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
|
|
||||||
body_text,
|
|
||||||
local_user_view.person.name,
|
|
||||||
comment_content,
|
|
||||||
settings.get_protocol_and_hostname()
|
|
||||||
);
|
|
||||||
match send_email(
|
|
||||||
subject,
|
|
||||||
user_email,
|
|
||||||
&local_user_view.person.name,
|
|
||||||
html,
|
|
||||||
settings,
|
|
||||||
) {
|
|
||||||
Ok(_o) => _o,
|
|
||||||
Err(e) => error!("{}", e),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
8
migrations/2021-11-23-132840_email_verification/down.sql
Normal file
8
migrations/2021-11-23-132840_email_verification/down.sql
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
-- revert defaults from db for local user init
|
||||||
|
alter table local_user alter column theme set default 'darkly';
|
||||||
|
alter table local_user alter column default_listing_type set default 1;
|
||||||
|
|
||||||
|
-- remove tables and columns for optional email verification
|
||||||
|
alter table site drop column require_email_verification;
|
||||||
|
alter table local_user drop column email_verified;
|
||||||
|
drop table email_verification;
|
14
migrations/2021-11-23-132840_email_verification/up.sql
Normal file
14
migrations/2021-11-23-132840_email_verification/up.sql
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
-- use defaults from db for local user init
|
||||||
|
alter table local_user alter column theme set default 'browser';
|
||||||
|
alter table local_user alter column default_listing_type set default 2;
|
||||||
|
|
||||||
|
-- add tables and columns for optional email verification
|
||||||
|
alter table site add column require_email_verification boolean not null default false;
|
||||||
|
alter table local_user add column email_verified boolean not null default false;
|
||||||
|
|
||||||
|
create table email_verification (
|
||||||
|
id serial primary key,
|
||||||
|
local_user_id int references local_user(id) on update cascade on delete cascade not null,
|
||||||
|
email text not null,
|
||||||
|
verification_token text not null
|
||||||
|
);
|
|
@ -0,0 +1,9 @@
|
||||||
|
-- Add columns to site table
|
||||||
|
alter table site drop column require_application;
|
||||||
|
alter table site drop column application_question;
|
||||||
|
alter table site drop column private_instance;
|
||||||
|
|
||||||
|
-- Add pending to local_user
|
||||||
|
alter table local_user drop column accepted_application;
|
||||||
|
|
||||||
|
drop table registration_application;
|
19
migrations/2021-11-23-153753_add_invite_only_columns/up.sql
Normal file
19
migrations/2021-11-23-153753_add_invite_only_columns/up.sql
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
-- Add columns to site table
|
||||||
|
alter table site add column require_application boolean not null default false;
|
||||||
|
alter table site add column application_question text;
|
||||||
|
alter table site add column private_instance boolean not null default false;
|
||||||
|
|
||||||
|
-- Add pending to local_user
|
||||||
|
alter table local_user add column accepted_application boolean not null default false;
|
||||||
|
|
||||||
|
create table registration_application (
|
||||||
|
id serial primary key,
|
||||||
|
local_user_id int references local_user on update cascade on delete cascade not null,
|
||||||
|
answer text not null,
|
||||||
|
admin_id int references person on update cascade on delete cascade,
|
||||||
|
deny_reason text,
|
||||||
|
published timestamp not null default now(),
|
||||||
|
unique(local_user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
create index idx_registration_application_published on registration_application (published desc);
|
|
@ -0,0 +1 @@
|
||||||
|
alter table email_verification drop column published;
|
|
@ -0,0 +1 @@
|
||||||
|
alter table email_verification add column published timestamp not null default now();
|
|
@ -210,13 +210,26 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
||||||
web::put().to(route_post::<ChangePassword>),
|
web::put().to(route_post::<ChangePassword>),
|
||||||
)
|
)
|
||||||
.route("/report_count", web::get().to(route_get::<GetReportCount>))
|
.route("/report_count", web::get().to(route_get::<GetReportCount>))
|
||||||
.route("/unread_count", web::get().to(route_get::<GetUnreadCount>)),
|
.route("/unread_count", web::get().to(route_get::<GetUnreadCount>))
|
||||||
|
.route("/verify_email", web::post().to(route_post::<VerifyEmail>)),
|
||||||
)
|
)
|
||||||
// Admin Actions
|
// Admin Actions
|
||||||
.service(
|
.service(
|
||||||
web::resource("/admin/add")
|
web::scope("/admin")
|
||||||
.wrap(rate_limit.message())
|
.wrap(rate_limit.message())
|
||||||
.route(web::post().to(route_post::<AddAdmin>)),
|
.route("/add", web::post().to(route_post::<AddAdmin>))
|
||||||
|
.route(
|
||||||
|
"/registration_application/count",
|
||||||
|
web::get().to(route_get::<GetUnreadRegistrationApplicationCount>),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/registration_application/list",
|
||||||
|
web::get().to(route_get::<ListRegistrationApplications>),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/registration_application/approve",
|
||||||
|
web::put().to(route_post::<ApproveRegistrationApplication>),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use diesel::{
|
||||||
};
|
};
|
||||||
use doku::json::{AutoComments, Formatting};
|
use doku::json::{AutoComments, Formatting};
|
||||||
use lemmy_api::match_websocket_operation;
|
use lemmy_api::match_websocket_operation;
|
||||||
use lemmy_api_common::blocking;
|
use lemmy_api_common::{blocking, check_private_instance_and_federation_enabled};
|
||||||
use lemmy_api_crud::match_websocket_operation_crud;
|
use lemmy_api_crud::match_websocket_operation_crud;
|
||||||
use lemmy_apub_lib::activity_queue::create_activity_queue;
|
use lemmy_apub_lib::activity_queue::create_activity_queue;
|
||||||
use lemmy_db_schema::{get_database_url_from_env, source::secret::Secret};
|
use lemmy_db_schema::{get_database_url_from_env, source::secret::Secret};
|
||||||
|
@ -103,6 +103,8 @@ async fn main() -> Result<(), LemmyError> {
|
||||||
|
|
||||||
let activity_queue = queue_manager.queue_handle().clone();
|
let activity_queue = queue_manager.queue_handle().clone();
|
||||||
|
|
||||||
|
check_private_instance_and_federation_enabled(&pool, &settings).await?;
|
||||||
|
|
||||||
let chat_server = ChatServer::startup(
|
let chat_server = ChatServer::startup(
|
||||||
pool.clone(),
|
pool.clone(),
|
||||||
rate_limiter.clone(),
|
rate_limiter.clone(),
|
||||||
|
|
Loading…
Reference in a new issue