Merge from main.

This commit is contained in:
Dessalines 2021-12-20 19:56:22 -05:00
parent 442c69b270
commit c294992699
83 changed files with 1919 additions and 884 deletions

552
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -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,43 +31,43 @@ 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"] }
serde = { version = "1.0.130", features = ["derive"] } serde = { version = "1.0.131", features = ["derive"] }
actix = "0.12.0" actix = "0.12.0"
actix-web = { version = "4.0.0-beta.9", default-features = false, features = ["rustls"] } actix-web = { version = "4.0.0-beta.14", default-features = false, features = ["rustls"] }
tracing = "0.1.29" tracing = "0.1.29"
tracing-actix-web = { version = "0.5.0-beta.3", default-features = false } tracing-actix-web = { version = "0.5.0-beta.5", default-features = false }
tracing-error = "0.2.0" tracing-error = "0.2.0"
tracing-log = "0.1.2" tracing-log = "0.1.2"
tracing-subscriber = { version = "0.3.2", features = ["env-filter"] } tracing-subscriber = { version = "0.3.3", features = ["env-filter"] }
strum = "0.21.0" strum = "0.23.0"
url = { version = "2.2.2", features = ["serde"] } url = { version = "2.2.2", features = ["serde"] }
openssl = "0.10.36" openssl = "0.10.38"
http-signature-normalization-actix = { version = "0.5.0-beta.10", default-features = false, features = ["sha-2"] } http-signature-normalization-actix = { version = "0.5.0-beta.14", default-features = false, features = ["sha-2"] }
tokio = { version = "1.12.0", features = ["sync"] } tokio = { version = "1.14.0", features = ["sync"] }
anyhow = "1.0.44" anyhow = "1.0.51"
reqwest = { version = "0.11.4", features = ["json"] } reqwest = { version = "0.11.7", features = ["json"] }
reqwest-middleware = "0.1.2" reqwest-middleware = "0.1.3"
reqwest-tracing = { version = "0.2.0", features = ["opentelemetry_0_16"] } reqwest-tracing = "0.2.0"
activitystreams = "0.7.0-alpha.11" activitystreams = "0.7.0-alpha.14"
actix-rt = { version = "2.2.0", default-features = false } actix-rt = { version = "2.5.0", default-features = false }
serde_json = { version = "1.0.68", features = ["preserve_order"] } serde_json = { version = "1.0.72", features = ["preserve_order"] }
clokwerk = "0.3.5" clokwerk = "0.3.5"
doku = "0.10.1" doku = "0.10.2"
opentelemetry = { version = "0.16", features = ["rt-tokio"] } opentelemetry = { version = "0.16", features = ["rt-tokio"] }
opentelemetry-otlp = "0.9" opentelemetry-otlp = "0.9"
tracing-opentelemetry = "0.16" tracing-opentelemetry = "0.16"

View file

@ -1 +0,0 @@
0.13.3

View file

@ -1,53 +0,0 @@
version: '2.2'
services:
lemmy:
image: {{ lemmy_docker_image }}
ports:
- "127.0.0.1:8536:8536"
restart: always
environment:
- RUST_LOG="warn,lemmy_server=info,lemmy_api=info,lemmy_api_common=info,lemmy_api_crud=info,lemmy_apub=info,lemmy_db_schema=info,lemmy_db_views=info,lemmy_db_views_actor=info,lemmy_db_views_moderator=info,lemmy_routes=info,lemmy_utils=info,lemmy_websocket=info"
volumes:
- ./lemmy.hjson:/config/config.hjson:ro
depends_on:
- postgres
- pictrs
lemmy-ui:
image: {{ lemmy_docker_ui_image }}
ports:
- "127.0.0.1:1235:1234"
restart: always
environment:
- LEMMY_INTERNAL_HOST=lemmy:8536
- LEMMY_EXTERNAL_HOST={{ domain }}
- LEMMY_HTTPS=true
depends_on:
- lemmy
postgres:
image: postgres:12-alpine
environment:
- POSTGRES_USER=lemmy
- POSTGRES_PASSWORD={{ postgres_password }}
- POSTGRES_DB=lemmy
volumes:
- ./volumes/postgres:/var/lib/postgresql/data
restart: always
pictrs:
image: asonix/pictrs:v0.2.6-r2
user: 991:991
ports:
- "127.0.0.1:8537:8080"
volumes:
- ./volumes/pictrs:/mnt
restart: always
mem_limit: 200m
postfix:
image: mwader/postfix-relay
environment:
- POSTFIX_myhostname={{ domain }}
restart: "always"

View file

@ -649,7 +649,7 @@ export function wrapper(form: any): string {
export function randomString(length: number): string { export function randomString(length: number): string {
var result = ''; var result = '';
var characters = 'abcdefghijklmnopqrstuvwxyz0123456789_'; var characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_';
var charactersLength = characters.length; var charactersLength = characters.length;
for (var i = 0; i < length; i++) { for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength)); result += characters.charAt(Math.floor(Math.random() * charactersLength));

View file

@ -83,8 +83,8 @@
setup: { setup: {
# Username for the admin user # Username for the admin user
admin_username: "admin" admin_username: "admin"
# Password for the admin user # Password for the admin user. It must be at least 10 characters.
admin_password: "my_passwd" admin_password: "my_passwd_longer_than_ten_characters"
# Name of the site (can be changed later) # Name of the site (can be changed later)
site_name: "My Lemmy Instance" site_name: "My Lemmy Instance"
# Email for the admin user (optional, can be omitted and set later through the website) # Email for the admin user (optional, can be omitted and set later through the website)
@ -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"

View file

@ -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,40 +13,40 @@ 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"] }
serde_json = { version = "1.0.68", features = ["preserve_order"] } serde_json = { version = "1.0.72", features = ["preserve_order"] }
serde = { version = "1.0.130", features = ["derive"] } serde = { version = "1.0.131", features = ["derive"] }
actix = "0.12.0" actix = "0.12.0"
actix-web = { version = "4.0.0-beta.9", default-features = false } actix-web = { version = "4.0.0-beta.14", default-features = false }
actix-rt = { version = "2.2.0", default-features = false } actix-rt = { version = "2.5.0", default-features = false }
rand = "0.8.4" rand = "0.8.4"
strum = "0.21.0" strum = "0.23.0"
strum_macros = "0.21.1" strum_macros = "0.23.1"
url = { version = "2.2.2", features = ["serde"] } url = { version = "2.2.2", features = ["serde"] }
openssl = "0.10.36" openssl = "0.10.38"
http = "0.2.5" http = "0.2.5"
http-signature-normalization-actix = { version = "0.5.0-beta.10", default-features = false, features = ["sha-2"] } http-signature-normalization-actix = { version = "0.5.0-beta.14", default-features = false, features = ["sha-2"] }
base64 = "0.13.0" base64 = "0.13.0"
tokio = "1.12.0" tokio = "1.14.0"
futures = "0.3.17" futures = "0.3.18"
itertools = "0.10.1" itertools = "0.10.3"
uuid = { version = "0.8.2", features = ["serde", "v4"] } uuid = { version = "0.8.2", features = ["serde", "v4"] }
sha2 = "0.9.8" sha2 = "0.10.0"
async-trait = "0.1.51" async-trait = "0.1.52"
captcha = "0.0.8" captcha = "0.0.8"
anyhow = "1.0.44" anyhow = "1.0.51"
thiserror = "1.0.29" thiserror = "1.0.30"
tracing = "0.1.29" tracing = "0.1.29"
background-jobs = "0.11.0" background-jobs = "0.11.0"
reqwest = { version = "0.11.4", features = ["json"] } reqwest = { version = "0.11.7", features = ["json"] }

View file

@ -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()
}; };

View file

@ -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(
local_user_view.local_user.id.0, Claims::jwt(
&context.secret().jwt_secret, local_user_view.local_user.id.0,
&context.settings().hostname, &context.secret().jwt_secret,
)? &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(
updated_local_user.id.0, Claims::jwt(
&context.secret().jwt_secret, updated_local_user.id.0,
&context.settings().hostname, &context.secret().jwt_secret,
)? &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(
updated_local_user.id.0, Claims::jwt(
&context.secret().jwt_secret, updated_local_user.id.0,
&context.settings().hostname, &context.secret().jwt_secret,
)? &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(
updated_local_user.id.0, Claims::jwt(
&context.secret().jwt_secret, updated_local_user.id.0,
&context.settings().hostname, &context.secret().jwt_secret,
)? &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 {})
}
}

View file

@ -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,
})
}
}

View file

@ -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,15 +13,15 @@ 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.130", features = ["derive"] } serde = { version = "1.0.131", features = ["derive"] }
diesel = "1.4.8" diesel = "1.4.8"
actix-web = { version = "4.0.0-beta.9", default-features = false, features = ["cookies"] } actix-web = { version = "4.0.0-beta.14", default-features = false, features = ["cookies"] }
chrono = { version = "0.4.19", features = ["serde"] } chrono = { version = "0.4.19", features = ["serde"] }
serde_json = { version = "1.0.68", features = ["preserve_order"] } serde_json = { version = "1.0.72", features = ["preserve_order"] }
tracing = "0.1.29" tracing = "0.1.29"
url = "2.2.2" url = "2.2.2"

View file

@ -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>
@ -264,6 +274,20 @@ pub async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), Le
Ok(()) Ok(())
} }
#[tracing::instrument(skip_all)]
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(())
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn build_federated_instances( pub async fn build_federated_instances(
pool: &DbPool, pool: &DbPool,
@ -333,3 +357,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(())
}

View file

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

View file

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

View file

@ -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,40 +8,40 @@ 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"] }
serde_json = { version = "1.0.68", features = ["preserve_order"] } serde_json = { version = "1.0.72", features = ["preserve_order"] }
serde = { version = "1.0.130", features = ["derive"] } serde = { version = "1.0.131", features = ["derive"] }
actix = "0.12.0" actix = "0.12.0"
actix-web = { version = "4.0.0-beta.9", default-features = false } actix-web = { version = "4.0.0-beta.14", default-features = false }
actix-rt = { version = "2.2.0", default-features = false } actix-rt = { version = "2.5.0", default-features = false }
tracing = "0.1.29" tracing = "0.1.29"
rand = "0.8.4" rand = "0.8.4"
strum = "0.21.0" strum = "0.23.0"
strum_macros = "0.21.1" strum_macros = "0.23.1"
url = { version = "2.2.2", features = ["serde"] } url = { version = "2.2.2", features = ["serde"] }
openssl = "0.10.36" openssl = "0.10.38"
http = "0.2.5" http = "0.2.5"
http-signature-normalization-actix = { version = "0.5.0-beta.10", default-features = false, features = ["sha-2"] } http-signature-normalization-actix = { version = "0.5.0-beta.14", default-features = false, features = ["sha-2"] }
base64 = "0.13.0" base64 = "0.13.0"
tokio = "1.12.0" tokio = "1.14.0"
futures = "0.3.17" futures = "0.3.18"
itertools = "0.10.1" itertools = "0.10.3"
uuid = { version = "0.8.2", features = ["serde", "v4"] } uuid = { version = "0.8.2", features = ["serde", "v4"] }
sha2 = "0.9.8" sha2 = "0.10.0"
async-trait = "0.1.51" async-trait = "0.1.52"
anyhow = "1.0.44" anyhow = "1.0.51"
thiserror = "1.0.29" thiserror = "1.0.30"
background-jobs = "0.11.0" background-jobs = "0.11.0"
reqwest = { version = "0.11.4", features = ["json"] } reqwest = { version = "0.11.7", features = ["json"] }
webmention = "0.4.0" webmention = "0.4.0"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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,13 +65,41 @@ 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?
.map_err(LemmyError::from)
.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? .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_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??;

View file

@ -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,
inserted_local_user.id.0, verify_email_sent: false,
&context.secret().jwt_secret, };
&context.settings().hostname,
)? // Log the user in directly if email verification and application aren't required
.into(), if !require_application && !email_verification {
}) login_response.jwt = Some(
Claims::jwt(
inserted_local_user.id.0,
&context.secret().jwt_secret,
&context.settings().hostname,
)?
.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)
} }
} }

View file

@ -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(),
})
} }
} }

View file

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

View file

@ -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,45 +13,45 @@ 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"
chrono = { version = "0.4.19", features = ["serde"] } chrono = { version = "0.4.19", features = ["serde"] }
serde_json = { version = "1.0.68", features = ["preserve_order"] } serde_json = { version = "1.0.72", features = ["preserve_order"] }
serde = { version = "1.0.130", features = ["derive"] } serde = { version = "1.0.131", features = ["derive"] }
serde_with = "1.10.0" serde_with = "1.11.0"
actix = "0.12.0" actix = "0.12.0"
actix-web = { version = "4.0.0-beta.9", default-features = false } actix-web = { version = "4.0.0-beta.14", default-features = false }
actix-rt = { version = "2.2.0", default-features = false } actix-rt = { version = "2.5.0", default-features = false }
tracing = "0.1.29" tracing = "0.1.29"
rand = "0.8.4" rand = "0.8.4"
strum = "0.21.0" strum = "0.23.0"
strum_macros = "0.21.1" strum_macros = "0.23.1"
url = { version = "2.2.2", features = ["serde"] } url = { version = "2.2.2", features = ["serde"] }
percent-encoding = "2.1.0" percent-encoding = "2.1.0"
http = "0.2.5" http = "0.2.5"
http-signature-normalization-actix = { version = "0.5.0-beta.10", default-features = false, features = ["server", "sha-2"] } http-signature-normalization-actix = { version = "0.5.0-beta.14", default-features = false, features = ["server", "sha-2"] }
tokio = "1.12.0" tokio = "1.14.0"
futures = "0.3.17" futures = "0.3.18"
itertools = "0.10.1" itertools = "0.10.3"
uuid = { version = "0.8.2", features = ["serde", "v4"] } uuid = { version = "0.8.2", features = ["serde", "v4"] }
sha2 = "0.9.8" sha2 = "0.10.0"
async-trait = "0.1.51" async-trait = "0.1.52"
anyhow = "1.0.44" anyhow = "1.0.51"
thiserror = "1.0.29" thiserror = "1.0.30"
background-jobs = "0.11.0" background-jobs = "0.11.0"
reqwest = { version = "0.11.4", features = ["json"] } reqwest = { version = "0.11.7", features = ["json"] }
html2md = "0.2.13" html2md = "0.2.13"
once_cell = "1.8.0" once_cell = "1.8.0"
[dev-dependencies] [dev-dependencies]
serial_test = "0.5.1" serial_test = "0.5.1"
assert-json-diff = "2.0.1" assert-json-diff = "2.0.1"
reqwest-middleware = "0.1.2" reqwest-middleware = "0.1.3"

View file

@ -0,0 +1,18 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://mycrowd.ca/schemas/litepub-0.1.jsonld",
{
"@language": "und"
}
],
"actor": "https://mycrowd.ca/users/kinetix",
"cc": [],
"id": "https://mycrowd.ca/activities/dab6a4d3-0db0-41ee-8aab-7bfa4929b4fd",
"object": "https://lemmy.ca/u/kinetix",
"state": "pending",
"to": [
"https://lemmy.ca/u/kinetix"
],
"type": "Follow"
}

View file

@ -2,7 +2,7 @@ use crate::{
http::{create_apub_response, create_apub_tombstone_response}, http::{create_apub_response, create_apub_tombstone_response},
objects::comment::ApubComment, objects::comment::ApubComment,
}; };
use actix_web::{body::AnyBody, web, web::Path, HttpResponse}; use actix_web::{web, web::Path, HttpResponse};
use diesel::result::Error::NotFound; use diesel::result::Error::NotFound;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::traits::ApubObject; use lemmy_apub_lib::traits::ApubObject;
@ -21,7 +21,7 @@ pub(crate) struct CommentQuery {
pub(crate) async fn get_apub_comment( pub(crate) async fn get_apub_comment(
info: Path<CommentQuery>, info: Path<CommentQuery>,
context: web::Data<LemmyContext>, context: web::Data<LemmyContext>,
) -> Result<HttpResponse<AnyBody>, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let id = CommentId(info.comment_id.parse::<i32>()?); let id = CommentId(info.comment_id.parse::<i32>()?);
let comment: ApubComment = blocking(context.pool(), move |conn| Comment::read(conn, id)) let comment: ApubComment = blocking(context.pool(), move |conn| Comment::read(conn, id))
.await?? .await??

View file

@ -21,7 +21,7 @@ use crate::{
collections::group_followers::GroupFollowers, collections::group_followers::GroupFollowers,
}, },
}; };
use actix_web::{body::AnyBody, web, web::Payload, HttpRequest, HttpResponse}; use actix_web::{web, web::Payload, HttpRequest, HttpResponse};
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::{object_id::ObjectId, traits::ApubObject}; use lemmy_apub_lib::{object_id::ObjectId, traits::ApubObject};
use lemmy_db_schema::source::community::Community; use lemmy_db_schema::source::community::Community;
@ -40,7 +40,7 @@ pub(crate) struct CommunityQuery {
pub(crate) async fn get_apub_community_http( pub(crate) async fn get_apub_community_http(
info: web::Path<CommunityQuery>, info: web::Path<CommunityQuery>,
context: web::Data<LemmyContext>, context: web::Data<LemmyContext>,
) -> Result<HttpResponse<AnyBody>, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let community: ApubCommunity = blocking(context.pool(), move |conn| { let community: ApubCommunity = blocking(context.pool(), move |conn| {
Community::read_from_name(conn, &info.community_name) Community::read_from_name(conn, &info.community_name)
}) })
@ -98,7 +98,7 @@ pub(in crate::http) async fn receive_group_inbox(
pub(crate) async fn get_apub_community_followers( pub(crate) async fn get_apub_community_followers(
info: web::Path<CommunityQuery>, info: web::Path<CommunityQuery>,
context: web::Data<LemmyContext>, context: web::Data<LemmyContext>,
) -> Result<HttpResponse<AnyBody>, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let community = blocking(context.pool(), move |conn| { let community = blocking(context.pool(), move |conn| {
Community::read_from_name(conn, &info.community_name) Community::read_from_name(conn, &info.community_name)
}) })
@ -112,7 +112,7 @@ pub(crate) async fn get_apub_community_followers(
pub(crate) async fn get_apub_community_outbox( pub(crate) async fn get_apub_community_outbox(
info: web::Path<CommunityQuery>, info: web::Path<CommunityQuery>,
context: web::Data<LemmyContext>, context: web::Data<LemmyContext>,
) -> Result<HttpResponse<AnyBody>, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let community = blocking(context.pool(), move |conn| { let community = blocking(context.pool(), move |conn| {
Community::read_from_name(conn, &info.community_name) Community::read_from_name(conn, &info.community_name)
}) })
@ -129,7 +129,7 @@ pub(crate) async fn get_apub_community_outbox(
pub(crate) async fn get_apub_community_moderators( pub(crate) async fn get_apub_community_moderators(
info: web::Path<CommunityQuery>, info: web::Path<CommunityQuery>,
context: web::Data<LemmyContext>, context: web::Data<LemmyContext>,
) -> Result<HttpResponse<AnyBody>, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let community: ApubCommunity = blocking(context.pool(), move |conn| { let community: ApubCommunity = blocking(context.pool(), move |conn| {
Community::read_from_name(conn, &info.community_name) Community::read_from_name(conn, &info.community_name)
}) })

View file

@ -7,7 +7,6 @@ use crate::{
insert_activity, insert_activity,
}; };
use actix_web::{ use actix_web::{
body::AnyBody,
web, web,
web::{Bytes, BytesMut, Payload}, web::{Bytes, BytesMut, Payload},
HttpRequest, HttpRequest,
@ -119,7 +118,7 @@ where
/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub /// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
/// headers. /// headers.
fn create_apub_response<T>(data: &T) -> HttpResponse<AnyBody> fn create_apub_response<T>(data: &T) -> HttpResponse
where where
T: Serialize, T: Serialize,
{ {
@ -128,13 +127,13 @@ where
.json(WithContext::new(data)) .json(WithContext::new(data))
} }
fn create_json_apub_response(data: serde_json::Value) -> HttpResponse<AnyBody> { fn create_json_apub_response(data: serde_json::Value) -> HttpResponse {
HttpResponse::Ok() HttpResponse::Ok()
.content_type(APUB_JSON_CONTENT_TYPE) .content_type(APUB_JSON_CONTENT_TYPE)
.json(data) .json(data)
} }
fn create_apub_tombstone_response<T>(data: &T) -> HttpResponse<AnyBody> fn create_apub_tombstone_response<T>(data: &T) -> HttpResponse
where where
T: Serialize, T: Serialize,
{ {
@ -155,7 +154,7 @@ pub struct ActivityQuery {
pub(crate) async fn get_activity( pub(crate) async fn get_activity(
info: web::Path<ActivityQuery>, info: web::Path<ActivityQuery>,
context: web::Data<LemmyContext>, context: web::Data<LemmyContext>,
) -> Result<HttpResponse<AnyBody>, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let settings = context.settings(); let settings = context.settings();
let activity_id = Url::parse(&format!( let activity_id = Url::parse(&format!(
"{}/activities/{}/{}", "{}/activities/{}/{}",

View file

@ -11,7 +11,7 @@ use crate::{
objects::person::ApubPerson, objects::person::ApubPerson,
protocol::collections::person_outbox::PersonOutbox, protocol::collections::person_outbox::PersonOutbox,
}; };
use actix_web::{body::AnyBody, web, web::Payload, HttpRequest, HttpResponse}; use actix_web::{web, web::Payload, HttpRequest, HttpResponse};
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::traits::ApubObject; use lemmy_apub_lib::traits::ApubObject;
use lemmy_db_schema::source::person::Person; use lemmy_db_schema::source::person::Person;
@ -30,7 +30,7 @@ pub struct PersonQuery {
pub(crate) async fn get_apub_person_http( pub(crate) async fn get_apub_person_http(
info: web::Path<PersonQuery>, info: web::Path<PersonQuery>,
context: web::Data<LemmyContext>, context: web::Data<LemmyContext>,
) -> Result<HttpResponse<AnyBody>, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let user_name = info.into_inner().user_name; let user_name = info.into_inner().user_name;
// TODO: this needs to be able to read deleted persons, so that it can send tombstones // TODO: this needs to be able to read deleted persons, so that it can send tombstones
let person: ApubPerson = blocking(context.pool(), move |conn| { let person: ApubPerson = blocking(context.pool(), move |conn| {
@ -75,7 +75,7 @@ pub(in crate::http) async fn receive_person_inbox(
pub(crate) async fn get_apub_person_outbox( pub(crate) async fn get_apub_person_outbox(
info: web::Path<PersonQuery>, info: web::Path<PersonQuery>,
context: web::Data<LemmyContext>, context: web::Data<LemmyContext>,
) -> Result<HttpResponse<AnyBody>, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let person = blocking(context.pool(), move |conn| { let person = blocking(context.pool(), move |conn| {
Person::find_by_name(conn, &info.user_name) Person::find_by_name(conn, &info.user_name)
}) })

View file

@ -2,7 +2,7 @@ use crate::{
http::{create_apub_response, create_apub_tombstone_response}, http::{create_apub_response, create_apub_tombstone_response},
objects::post::ApubPost, objects::post::ApubPost,
}; };
use actix_web::{body::AnyBody, web, HttpResponse}; use actix_web::{web, HttpResponse};
use diesel::result::Error::NotFound; use diesel::result::Error::NotFound;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_apub_lib::traits::ApubObject; use lemmy_apub_lib::traits::ApubObject;
@ -21,7 +21,7 @@ pub(crate) struct PostQuery {
pub(crate) async fn get_apub_post( pub(crate) async fn get_apub_post(
info: web::Path<PostQuery>, info: web::Path<PostQuery>,
context: web::Data<LemmyContext>, context: web::Data<LemmyContext>,
) -> Result<HttpResponse<AnyBody>, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let id = PostId(info.post_id.parse::<i32>()?); let id = PostId(info.post_id.parse::<i32>()?);
let post: ApubPost = blocking(context.pool(), move |conn| Post::read(conn, id)) let post: ApubPost = blocking(context.pool(), move |conn| Post::read(conn, id))
.await?? .await??

View file

@ -4,6 +4,7 @@ pub mod post;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use crate::{
context::WithContext,
objects::tests::file_to_json_object, objects::tests::file_to_json_object,
protocol::{ protocol::{
activities::create_or_update::{comment::CreateOrUpdateComment, post::CreateOrUpdatePost}, activities::create_or_update::{comment::CreateOrUpdateComment, post::CreateOrUpdatePost},
@ -23,8 +24,12 @@ mod tests {
"assets/lemmy/activities/create_or_update/create_note.json", "assets/lemmy/activities/create_or_update/create_note.json",
); );
file_to_json_object::<CreateOrUpdateComment>("assets/pleroma/activities/create_note.json"); file_to_json_object::<WithContext<CreateOrUpdateComment>>(
file_to_json_object::<CreateOrUpdateComment>("assets/smithereen/activities/create_note.json"); "assets/pleroma/activities/create_note.json",
);
file_to_json_object::<WithContext<CreateOrUpdateComment>>(
"assets/smithereen/activities/create_note.json",
);
file_to_json_object::<CreateOrUpdateComment>("assets/mastodon/activities/create_note.json"); file_to_json_object::<CreateOrUpdateComment>("assets/mastodon/activities/create_note.json");
file_to_json_object::<CreateOrUpdatePost>("assets/lotide/activities/create_page.json"); file_to_json_object::<CreateOrUpdatePost>("assets/lotide/activities/create_page.json");

View file

@ -4,13 +4,17 @@ pub mod undo_follow;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::protocol::{ use crate::{
activities::following::{ context::WithContext,
accept::AcceptFollowCommunity, objects::tests::file_to_json_object,
follow::FollowCommunity, protocol::{
undo_follow::UndoFollowCommunity, activities::following::{
accept::AcceptFollowCommunity,
follow::FollowCommunity,
undo_follow::UndoFollowCommunity,
},
tests::test_parse_lemmy_item,
}, },
tests::test_parse_lemmy_item,
}; };
#[actix_rt::test] #[actix_rt::test]
@ -20,5 +24,7 @@ mod tests {
test_parse_lemmy_item::<UndoFollowCommunity>( test_parse_lemmy_item::<UndoFollowCommunity>(
"assets/lemmy/activities/following/undo_follow.json", "assets/lemmy/activities/following/undo_follow.json",
); );
file_to_json_object::<WithContext<FollowCommunity>>("assets/pleroma/activities/follow.json");
} }
} }

View file

@ -1,5 +1,5 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum_macros::ToString; use strum_macros::Display;
pub mod community; pub mod community;
pub mod create_or_update; pub mod create_or_update;
@ -8,7 +8,7 @@ pub mod following;
pub mod private_message; pub mod private_message;
pub mod voting; pub mod voting;
#[derive(Clone, Debug, ToString, Deserialize, Serialize, PartialEq)] #[derive(Clone, Debug, Display, Deserialize, Serialize, PartialEq)]
pub enum CreateOrUpdateType { pub enum CreateOrUpdateType {
Create, Create,
Update, Update,

View file

@ -7,7 +7,7 @@ use lemmy_apub_lib::object_id::ObjectId;
use lemmy_utils::LemmyError; use lemmy_utils::LemmyError;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::convert::TryFrom; use std::convert::TryFrom;
use strum_macros::ToString; use strum_macros::Display;
use url::Url; use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
@ -26,7 +26,7 @@ pub struct Vote {
pub(crate) unparsed: Unparsed, pub(crate) unparsed: Unparsed,
} }
#[derive(Clone, Debug, ToString, Deserialize, Serialize)] #[derive(Clone, Debug, Display, Deserialize, Serialize)]
pub enum VoteType { pub enum VoteType {
Like, Like,
Dislike, Dislike,

View file

@ -18,6 +18,7 @@ pub struct Endpoints {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use crate::{
context::WithContext,
objects::tests::file_to_json_object, objects::tests::file_to_json_object,
protocol::{ protocol::{
objects::{chat_message::ChatMessage, group::Group, note::Note, page::Page, person::Person}, objects::{chat_message::ChatMessage, group::Group, note::Note, page::Page, person::Person},
@ -33,11 +34,11 @@ mod tests {
test_parse_lemmy_item::<Note>("assets/lemmy/objects/note.json"); test_parse_lemmy_item::<Note>("assets/lemmy/objects/note.json");
test_parse_lemmy_item::<ChatMessage>("assets/lemmy/objects/chat_message.json"); test_parse_lemmy_item::<ChatMessage>("assets/lemmy/objects/chat_message.json");
file_to_json_object::<Person>("assets/pleroma/objects/person.json"); file_to_json_object::<WithContext<Person>>("assets/pleroma/objects/person.json");
file_to_json_object::<Note>("assets/pleroma/objects/note.json"); file_to_json_object::<WithContext<Note>>("assets/pleroma/objects/note.json");
file_to_json_object::<ChatMessage>("assets/pleroma/objects/chat_message.json"); file_to_json_object::<WithContext<ChatMessage>>("assets/pleroma/objects/chat_message.json");
file_to_json_object::<Person>("assets/smithereen/objects/person.json"); file_to_json_object::<WithContext<Person>>("assets/smithereen/objects/person.json");
file_to_json_object::<Note>("assets/smithereen/objects/note.json"); file_to_json_object::<Note>("assets/smithereen/objects/note.json");
file_to_json_object::<Person>("assets/mastodon/objects/person.json"); file_to_json_object::<Person>("assets/mastodon/objects/person.json");

View file

@ -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,24 +8,24 @@ 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.11" activitystreams = "0.7.0-alpha.14"
serde = { version = "1.0.130", features = ["derive"] } serde = { version = "1.0.131", features = ["derive"] }
async-trait = "0.1.51" async-trait = "0.1.52"
url = { version = "2.2.2", features = ["serde"] } url = { version = "2.2.2", features = ["serde"] }
serde_json = { version = "1.0.68", features = ["preserve_order"] } serde_json = { version = "1.0.72", features = ["preserve_order"] }
anyhow = "1.0.44" anyhow = "1.0.51"
reqwest = { version = "0.11.4", features = ["json"] } reqwest = { version = "0.11.7", features = ["json"] }
reqwest-middleware = "0.1.2" reqwest-middleware = "0.1.3"
tracing = "0.1.29" tracing = "0.1.29"
base64 = "0.13.0" base64 = "0.13.0"
openssl = "0.10.36" openssl = "0.10.38"
once_cell = "1.8.0" once_cell = "1.8.0"
http = "0.2.5" http = "0.2.5"
sha2 = "0.9.8" sha2 = "0.10.0"
actix-web = { version = "4.0.0-beta.9", default-features = false } actix-web = { version = "4.0.0-beta.14", default-features = false }
http-signature-normalization-actix = { version = "0.5.0-beta.10", default-features = false, features = ["server", "sha-2"] } http-signature-normalization-actix = { version = "0.5.0-beta.14", default-features = false, features = ["server", "sha-2"] }
http-signature-normalization-reqwest = { version = "0.3.0", default-features = false, features = ["sha-2", "middleware"] } http-signature-normalization-reqwest = { version = "0.4.0", default-features = false, features = ["sha-2", "middleware"] }
background-jobs = "0.11.0" background-jobs = "0.11.0"
diesel = "1.4.8" diesel = "1.4.8"

View file

@ -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"
@ -11,9 +11,9 @@ documentation = "https://join-lemmy.org/docs/en/index.html"
proc-macro = true proc-macro = true
[dev-dependencies] [dev-dependencies]
trybuild = { version = "1.0.45", features = ["diff"] } trybuild = { version = "1.0.53", features = ["diff"] }
[dependencies] [dependencies]
proc-macro2 = "1.0.29" proc-macro2 = "1.0.33"
syn = "1.0.77" syn = "1.0.82"
quote = "1.0.9" quote = "1.0.10"

View file

@ -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"
@ -8,23 +8,25 @@ homepage = "https://join-lemmy.org/"
documentation = "https://join-lemmy.org/docs/en/index.html" documentation = "https://join-lemmy.org/docs/en/index.html"
[lib] [lib]
name = "lemmy_db_schema"
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" }
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"] }
serde = { version = "1.0.130", features = ["derive"] } serde = { version = "1.0.131", features = ["derive"] }
serde_json = { version = "1.0.68", features = ["preserve_order"] } serde_json = { version = "1.0.72", features = ["preserve_order"] }
url = { version = "2.2.2", features = ["serde"] } url = { version = "2.2.2", features = ["serde"] }
diesel-derive-newtype = "0.1.2" diesel-derive-newtype = "0.1.2"
regex = "1.5.4" regex = "1.5.4"
once_cell = "1.8.0" once_cell = "1.8.0"
strum = "0.21.0" strum = "0.23.0"
strum_macros = "0.21.1" strum_macros = "0.23.1"
sha2 = "0.9.8" sha2 = "0.10.0"
bcrypt = "0.10.1" bcrypt = "0.10.1"
[dev-dependencies] [dev-dependencies]

View file

@ -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();

View file

@ -1,4 +1,5 @@
use crate::{ use crate::{
functions::lower,
naive_now, naive_now,
newtypes::{CommunityId, DbUrl, PersonId}, newtypes::{CommunityId, DbUrl, PersonId},
source::community::{ source::community::{
@ -95,7 +96,7 @@ impl Community {
use crate::schema::community::dsl::*; use crate::schema::community::dsl::*;
community community
.filter(local.eq(true)) .filter(local.eq(true))
.filter(name.eq(community_name)) .filter(lower(name).eq(lower(community_name)))
.first::<Self>(conn) .first::<Self>(conn)
} }

View 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)
}
}

View file

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

View file

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

View file

@ -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()
}; };

View file

@ -1,4 +1,5 @@
use crate::{ use crate::{
functions::lower,
naive_now, naive_now,
newtypes::{DbUrl, PersonId}, newtypes::{DbUrl, PersonId},
schema::person::dsl::*, schema::person::dsl::*,
@ -194,7 +195,7 @@ impl Person {
person person
.filter(deleted.eq(false)) .filter(deleted.eq(false))
.filter(local.eq(true)) .filter(local.eq(true))
.filter(name.eq(from_name)) .filter(lower(name).eq(lower(from_name)))
.first::<Person>(conn) .first::<Person>(conn)
} }

View 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)
}
}

View file

@ -32,7 +32,7 @@ pub fn get_database_url_from_env() -> Result<String, VarError> {
env::var("LEMMY_DATABASE_URL") env::var("LEMMY_DATABASE_URL")
} }
#[derive(EnumString, ToString, Debug, Serialize, Deserialize, Clone, Copy)] #[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy)]
pub enum SortType { pub enum SortType {
Active, Active,
Hot, Hot,
@ -46,7 +46,7 @@ pub enum SortType {
NewComments, NewComments,
} }
#[derive(EnumString, ToString, Debug, Serialize, Deserialize, Clone, Copy)] #[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy)]
pub enum ListingType { pub enum ListingType {
All, All,
Local, Local,
@ -54,7 +54,7 @@ pub enum ListingType {
Community, Community,
} }
#[derive(EnumString, ToString, Debug, Serialize, Deserialize, Clone, Copy)] #[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy)]
pub enum SearchType { pub enum SearchType {
All, All,
Comments, Comments,
@ -143,6 +143,8 @@ pub mod functions {
sql_function! { sql_function! {
fn hot_rank(score: BigInt, time: Timestamp) -> Integer; fn hot_rank(score: BigInt, time: Timestamp) -> Integer;
} }
sql_function!(fn lower(x: Text) -> Text);
} }
#[cfg(test)] #[cfg(test)]

View file

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

View file

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

View 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,
}

View file

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

View file

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

View 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>>,
}

View file

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

View file

@ -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,9 +11,9 @@ 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.130", features = ["derive"] } serde = { version = "1.0.131", features = ["derive"] }
tracing = "0.1.29" tracing = "0.1.29"
url = "2.2.2" url = "2.2.2"

View file

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

View 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();
}
}

View file

@ -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.130", features = ["derive"] } serde = { version = "1.0.131", features = ["derive"] }

View file

@ -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.130", features = ["derive"] } serde = { version = "1.0.131", features = ["derive"] }

View file

@ -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,28 +11,28 @@ 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.9", default-features = false, features = ["rustls"] } actix-web = { version = "4.0.0-beta.14", default-features = false, features = ["rustls"] }
actix-web-actors = { version = "4.0.0-beta.7", default-features = false } actix-web-actors = { version = "4.0.0-beta.8", default-features = false }
actix-http = "3.0.0-beta.10" actix-http = "3.0.0-beta.15"
sha2 = "0.9.8" sha2 = "0.10.0"
anyhow = "1.0.44" anyhow = "1.0.51"
chrono = { version = "0.4.19", features = ["serde"] } chrono = { version = "0.4.19", features = ["serde"] }
futures = "0.3.18" futures = "0.3.18"
reqwest = { version = "0.11.7", features = ["stream"] } reqwest = { version = "0.11.7", features = ["stream"] }
reqwest-middleware = "0.1.2" reqwest-middleware = "0.1.3"
rss = "1.10.0" rss = "2.0.0"
serde = { version = "1.0.130", features = ["derive"] } serde = { version = "1.0.131", features = ["derive"] }
url = { version = "2.2.2", features = ["serde"] } url = { version = "2.2.2", features = ["serde"] }
strum = "0.21.0" strum = "0.23.0"
once_cell = "1.8.0" once_cell = "1.8.0"
tracing = "0.1.29" tracing = "0.1.29"
tokio = { version = "1", features = ["sync"] } tokio = { version = "1.14.0", features = ["sync"] }

View file

@ -27,7 +27,7 @@ use rss::{
ItemBuilder, ItemBuilder,
}; };
use serde::Deserialize; use serde::Deserialize;
use std::{collections::HashMap, str::FromStr}; use std::{collections::BTreeMap, str::FromStr};
use strum::ParseError; use strum::ParseError;
#[derive(Deserialize)] #[derive(Deserialize)]
@ -49,8 +49,8 @@ pub fn config(cfg: &mut web::ServiceConfig) {
.route("/feeds/local.xml", web::get().to(get_local_feed)); .route("/feeds/local.xml", web::get().to(get_local_feed));
} }
static RSS_NAMESPACE: Lazy<HashMap<String, String>> = Lazy::new(|| { static RSS_NAMESPACE: Lazy<BTreeMap<String, String>> = Lazy::new(|| {
let mut h = HashMap::new(); let mut h = BTreeMap::new();
h.insert( h.insert(
"dc".to_string(), "dc".to_string(),
rss::extension::dublincore::NAMESPACE.to_string(), rss::extension::dublincore::NAMESPACE.to_string(),
@ -109,7 +109,7 @@ async fn get_feed_data(
channel_builder.description(&site_desc); channel_builder.description(&site_desc);
} }
let rss = channel_builder.build().map_err(|e| anyhow!(e))?.to_string(); let rss = channel_builder.build().to_string();
Ok( Ok(
HttpResponse::Ok() HttpResponse::Ok()
.content_type("application/rss+xml") .content_type("application/rss+xml")
@ -154,7 +154,7 @@ async fn get_feed(
.await? .await?
.map_err(ErrorBadRequest)?; .map_err(ErrorBadRequest)?;
let rss = builder.build().map_err(ErrorBadRequest)?.to_string(); let rss = builder.build().to_string();
Ok( Ok(
HttpResponse::Ok() HttpResponse::Ok()
@ -373,17 +373,13 @@ fn build_item(
let dt = DateTime::<Utc>::from_utc(*published, Utc); let dt = DateTime::<Utc>::from_utc(*published, Utc);
i.pub_date(dt.to_rfc2822()); i.pub_date(dt.to_rfc2822());
i.comments(url.to_owned()); i.comments(url.to_owned());
let guid = GuidBuilder::default() let guid = GuidBuilder::default().permalink(true).value(url).build();
.permalink(true)
.value(url)
.build()
.map_err(|e| anyhow!(e))?;
i.guid(guid); i.guid(guid);
i.link(url.to_owned()); i.link(url.to_owned());
// TODO add images // TODO add images
let html = markdown_to_html(&content.to_string()); let html = markdown_to_html(&content.to_string());
i.description(html); i.description(html);
Ok(i.build().map_err(|e| anyhow!(e))?) Ok(i.build())
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
@ -410,8 +406,7 @@ fn create_post_items(
let guid = GuidBuilder::default() let guid = GuidBuilder::default()
.permalink(true) .permalink(true)
.value(&post_url) .value(&post_url)
.build() .build();
.map_err(|e| anyhow!(e))?;
i.guid(guid); i.guid(guid);
let community_url = format!("{}/c/{}", protocol_and_hostname, p.community.name); let community_url = format!("{}/c/{}", protocol_and_hostname, p.community.name);
@ -439,8 +434,8 @@ fn create_post_items(
i.description(description); i.description(description);
i.dublin_core_ext(dc_extension.build().map_err(|e| anyhow!(e))?); i.dublin_core_ext(dc_extension.build());
items.push(i.build().map_err(|e| anyhow!(e))?); items.push(i.build());
} }
Ok(items) Ok(items)

View file

@ -1,7 +1,4 @@
use actix_http::{ use actix_http::header::{HeaderName, ACCEPT_ENCODING, HOST};
header::{HeaderName, HOST},
http::header::ACCEPT_ENCODING,
};
use actix_web::{body::BodyStream, http::StatusCode, web::Data, *}; use actix_web::{body::BodyStream, http::StatusCode, web::Data, *};
use anyhow::anyhow; use anyhow::anyhow;
use futures::stream::{Stream, StreamExt}; use futures::stream::{Stream, StreamExt};

View file

@ -1,4 +1,4 @@
use actix_web::{body::AnyBody, error::ErrorBadRequest, *}; use actix_web::{error::ErrorBadRequest, *};
use anyhow::anyhow; use anyhow::anyhow;
use lemmy_api_common::blocking; use lemmy_api_common::blocking;
use lemmy_db_views::site_view::SiteView; use lemmy_db_views::site_view::SiteView;
@ -15,7 +15,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
async fn node_info_well_known( async fn node_info_well_known(
context: web::Data<LemmyContext>, context: web::Data<LemmyContext>,
) -> Result<HttpResponse<AnyBody>, LemmyError> { ) -> Result<HttpResponse, LemmyError> {
let node_info = NodeInfoWellKnown { let node_info = NodeInfoWellKnown {
links: vec![NodeInfoWellKnownLinks { links: vec![NodeInfoWellKnownLinks {
rel: Url::parse("http://nodeinfo.diaspora.software/ns/schema/2.0")?, rel: Url::parse("http://nodeinfo.diaspora.software/ns/schema/2.0")?,

View file

@ -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"
@ -15,33 +15,33 @@ doctest = false
[dependencies] [dependencies]
regex = "1.5.4" regex = "1.5.4"
chrono = { version = "0.4.19", features = ["serde"] } chrono = { version = "0.4.19", features = ["serde"] }
lettre = "0.10.0-rc.3" lettre = "0.10.0-rc.4"
tracing = "0.1.29" tracing = "0.1.29"
tracing-error = "0.2.0" tracing-error = "0.2.0"
itertools = "0.10.1" itertools = "0.10.3"
rand = "0.8.4" rand = "0.8.4"
percent-encoding = "2.1.0" percent-encoding = "2.1.0"
serde = { version = "1.0.130", features = ["derive"] } serde = { version = "1.0.131", features = ["derive"] }
serde_json = { version = "1.0.68", features = ["preserve_order"] } serde_json = { version = "1.0.72", features = ["preserve_order"] }
thiserror = "1.0.29" thiserror = "1.0.30"
comrak = { version = "0.12.1", default-features = false } comrak = { version = "0.12.1", default-features = false }
once_cell = "1.8.0" once_cell = "1.8.0"
openssl = "0.10.36" openssl = "0.10.38"
url = { version = "2.2.2", features = ["serde"] } url = { version = "2.2.2", features = ["serde"] }
actix-web = { version = "4.0.0-beta.9", default-features = false, features = ["rustls"] } actix-web = { version = "4.0.0-beta.14", default-features = false, features = ["rustls"] }
actix-rt = { version = "2.2.0", default-features = false } actix-rt = { version = "2.5.0", default-features = false }
anyhow = "1.0.44" anyhow = "1.0.51"
reqwest = { version = "0.11.4", features = ["json"] } reqwest = { version = "0.11.7", features = ["json"] }
reqwest-middleware = "0.1.2" reqwest-middleware = "0.1.3"
tokio = { version = "1.12.0", features = ["sync"] } tokio = { version = "1.14.0", features = ["sync"] }
strum = "0.21.0" strum = "0.23.0"
strum_macros = "0.21.1" strum_macros = "0.23.1"
futures = "0.3.17" futures = "0.3.18"
diesel = "1.4.8" diesel = "1.4.8"
http = "0.2.5" http = "0.2.5"
deser-hjson = "1.0.2" deser-hjson = "1.0.2"
smart-default = "0.6.0" smart-default = "0.6.0"
webpage = { version = "1.3.0", default-features = false, features = ["serde"] } webpage = { version = "1.4.0", default-features = false, features = ["serde"] }
jsonwebtoken = "7.2.0" jsonwebtoken = "7.2.0"
doku = "0.10.1" doku = "0.10.2"
uuid = { version = "0.8.2", features = ["serde", "v4"] } uuid = { version = "0.8.2", features = ["serde", "v4"] }

View file

@ -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")),
} }
} }

View file

@ -13,7 +13,7 @@ static SETTINGS: Lazy<RwLock<Settings>> =
Lazy::new(|| RwLock::new(Settings::init().expect("Failed to load settings file"))); Lazy::new(|| RwLock::new(Settings::init().expect("Failed to load settings file")));
static WEBFINGER_REGEX: Lazy<Regex> = Lazy::new(|| { static WEBFINGER_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(&format!( Regex::new(&format!(
"^acct:([a-z0-9_]{{3,}})@{}$", "^acct:([a-zA-Z0-9_]{{3,}})@{}$",
Settings::get().hostname Settings::get().hostname
)) ))
.expect("compile webfinger regex") .expect("compile webfinger regex")

View file

@ -169,8 +169,8 @@ pub struct SetupConfig {
/// Username for the admin user /// Username for the admin user
#[doku(example = "admin")] #[doku(example = "admin")]
pub admin_username: String, pub admin_username: String,
/// Password for the admin user /// Password for the admin user. It must be at least 10 characters.
#[doku(example = "my_passwd")] #[doku(example = "my_passwd_longer_than_ten_characters")]
pub admin_password: String, pub admin_password: String,
/// Name of the site (can be changed later) /// Name of the site (can be changed later)
#[doku(example = "My Lemmy Instance")] #[doku(example = "My Lemmy Instance")]
@ -194,4 +194,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>,
} }

View file

@ -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,26 +13,26 @@ 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.4", features = ["json"] } reqwest = { version = "0.11.7", features = ["json"] }
reqwest-middleware = "0.1.2" reqwest-middleware = "0.1.3"
tracing = "0.1.29" tracing = "0.1.29"
rand = "0.8.4" rand = "0.8.4"
serde = { version = "1.0.130", features = ["derive"] } serde = { version = "1.0.131", features = ["derive"] }
serde_json = { version = "1.0.68", features = ["preserve_order"] } serde_json = { version = "1.0.72", features = ["preserve_order"] }
actix = "0.12.0" actix = "0.12.0"
anyhow = "1.0.44" anyhow = "1.0.51"
diesel = "1.4.8" diesel = "1.4.8"
background-jobs = "0.11.0" background-jobs = "0.11.0"
tokio = "1.12.0" tokio = "1.14.0"
strum = "0.21.0" strum = "0.23.0"
strum_macros = "0.21.1" strum_macros = "0.23.1"
chrono = { version = "0.4.19", features = ["serde"] } chrono = { version = "0.4.19", features = ["serde"] }
actix-web = { version = "4.0.0-beta.9", default-features = false, features = ["rustls"] } actix-web = { version = "4.0.0-beta.14", default-features = false, features = ["rustls"] }
actix-web-actors = { version = "4.0.0-beta.7", default-features = false } actix-web-actors = { version = "4.0.0-beta.8", default-features = false }
opentelemetry = "0.16" opentelemetry = "0.16"
tracing-opentelemetry = "0.16" tracing-opentelemetry = "0.16"

View file

@ -97,7 +97,7 @@ where
Ok(serde_json::to_string(&response)?) Ok(serde_json::to_string(&response)?)
} }
#[derive(EnumString, ToString, Debug, Clone)] #[derive(EnumString, Display, Debug, Clone)]
pub enum UserOperation { pub enum UserOperation {
Login, Login,
GetCaptcha, GetCaptcha,
@ -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,
@ -147,7 +151,7 @@ pub enum UserOperation {
BlockPerson, BlockPerson,
} }
#[derive(EnumString, ToString, Debug, Clone)] #[derive(EnumString, Display, Debug, Clone)]
pub enum UserOperationCrud { pub enum UserOperationCrud {
// Site // Site
CreateSite, CreateSite,

View file

@ -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;
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn send_post_ws_message<OP: ToString + Send + OperationType + 'static>( pub async fn send_post_ws_message<OP: ToString + Send + OperationType + 'static>(
@ -302,40 +296,3 @@ pub async fn send_local_notifs(
}; };
Ok(recipient_ids) Ok(recipient_ids)
} }
#[tracing::instrument(skip_all)]
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),
};
}
}

View file

@ -22,7 +22,7 @@ services:
pictrs: pictrs:
restart: always restart: always
image: asonix/pictrs:v0.2.6-r2 image: asonix/pictrs:0.3.0-beta.11
user: 991:991 user: 991:991
volumes: volumes:
- ./volumes/pictrs_alpha:/mnt - ./volumes/pictrs_alpha:/mnt

View file

@ -14,7 +14,7 @@ services:
pictrs: pictrs:
restart: always restart: always
image: asonix/pictrs:v0.2.6-r2 image: asonix/pictrs:0.3.0-beta.11
user: 991:991 user: 991:991
volumes: volumes:
- ./volumes/pictrs:/mnt - ./volumes/pictrs:/mnt

View file

@ -37,7 +37,7 @@ services:
- lemmy - lemmy
pictrs: pictrs:
image: asonix/pictrs:v0.2.6-r2 image: asonix/pictrs:0.3.0-beta.11
ports: ports:
- "127.0.0.1:8537:8080" - "127.0.0.1:8537:8080"
user: 991:991 user: 991:991

View 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;

View 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
);

View file

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

View 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);

View file

@ -0,0 +1 @@
alter table email_verification drop column published;

View file

@ -0,0 +1 @@
alter table email_verification add column published timestamp not null default now();

View file

@ -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>),
),
), ),
); );
} }

View file

@ -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(),