A few fixes for private sites:

- Added a check_registration_application function.
- Only send a verification email if its been changed.
- VerifyEmail now returns LoginResponse.
- Deleting the old tokens after a successful email verify.
- If port is missing on email config, display a better error message.
invite_instances
Dessalines 2021-12-09 16:15:51 -05:00
parent b9978cc141
commit eecb710104
11 changed files with 137 additions and 69 deletions

View File

@ -6,6 +6,7 @@ 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,
@ -97,9 +98,7 @@ impl Perform for Login {
return Err(LemmyError::from_message("email_not_verified")); return Err(LemmyError::from_message("email_not_verified"));
} }
if site.require_application && !local_user_view.local_user.accepted_application { check_registration_application(&site, &local_user_view, context.pool()).await?;
return Err(LemmyError::from_message("registration_application_pending"));
}
// Return the jwt // Return the jwt
Ok(LoginResponse { Ok(LoginResponse {
@ -188,6 +187,9 @@ impl Perform for SaveUserSettings {
let email = if let Some(email) = &email_deref { let email = if let Some(email) = &email_deref {
let site = blocking(context.pool(), Site::read_simple).await??; let site = blocking(context.pool(), Site::read_simple).await??;
if site.require_email_verification { if site.require_email_verification {
if let Some(previous_email) = local_user_view.local_user.email {
// Only send the verification email if there was an email change
if previous_email.ne(email) {
send_verification_email( send_verification_email(
local_user_view.local_user.id, local_user_view.local_user.id,
email, email,
@ -196,6 +198,10 @@ impl Perform for SaveUserSettings {
&context.settings(), &context.settings(),
) )
.await?; .await?;
}
}
// Fine to return None here, because the actual email is also in the email_verification
// table, and gets set in the function below.
None None
} else { } else {
diesel_option_overwrite(&email_deref) diesel_option_overwrite(&email_deref)
@ -918,7 +924,7 @@ impl Perform for GetUnreadCount {
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl Perform for VerifyEmail { impl Perform for VerifyEmail {
type Response = VerifyEmailResponse; type Response = LoginResponse;
async fn perform( async fn perform(
&self, &self,
@ -953,6 +959,26 @@ impl Perform for VerifyEmail {
send_email_verification_success(&local_user_view, &context.settings())?; send_email_verification_success(&local_user_view, &context.settings())?;
Ok(VerifyEmailResponse {}) blocking(context.pool(), move |conn| {
EmailVerification::delete_old_tokens_for_local_user(conn, local_user_id)
})
.await??;
let site = blocking(context.pool(), Site::read_simple).await??;
check_registration_application(&site, &local_user_view, context.pool()).await?;
// Return the jwt
Ok(LoginResponse {
jwt: Some(
Claims::jwt(
local_user_view.local_user.id.0,
&context.secret().jwt_secret,
&context.settings().hostname,
)?
.into(),
),
verify_email_sent: false,
registration_created: false,
})
} }
} }

View File

@ -38,6 +38,7 @@ 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::{ registration_application_view::{
RegistrationApplicationQueryBuilder, RegistrationApplicationQueryBuilder,
@ -662,7 +663,11 @@ impl Perform for ApproveRegistrationApplication {
.require_email_verification; .require_email_verification;
if require_email_verification && data.approve { if require_email_verification && data.approve {
send_application_approved_email(&local_user_view, &context.settings())?; let approved_local_user_view = blocking(context.pool(), move |conn| {
LocalUserView::read(conn, approved_user_id)
})
.await??;
send_application_approved_email(&approved_local_user_view, &context.settings())?;
} }
// Read the view // Read the view

View File

@ -14,6 +14,7 @@ use lemmy_db_schema::{
password_reset_request::PasswordResetRequest, 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,
}, },
@ -426,8 +427,8 @@ pub async fn send_verification_email(
let body = format!( let body = format!(
concat!( concat!(
"Please click the link below to verify your email address ", "Please click the link below to verify your email address ",
"for the account @{}@{}. Ignore this email if the account isn't yours.\n\n", "for the account @{}@{}. Ignore this email if the account isn't yours.<br><br>",
"<a href=\"{}\"></a>" "<a href=\"{}\">Verify your email</a>"
), ),
username, settings.hostname, verify_link username, settings.hostname, verify_link
); );
@ -441,7 +442,7 @@ pub fn send_email_verification_success(
settings: &Settings, settings: &Settings,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let email = &local_user_view.local_user.email.to_owned().expect("email"); let email = &local_user_view.local_user.email.to_owned().expect("email");
let subject = &format!("Email verified for {}", local_user_view.person.name); let subject = &format!("Email verified for {}", local_user_view.person.actor_id);
let html = "Your email has been verified."; let html = "Your email has been verified.";
send_email(subject, email, &local_user_view.person.name, html, settings) send_email(subject, email, &local_user_view.person.name, html, settings)
} }
@ -452,8 +453,8 @@ pub fn send_application_approved_email(
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let email = &local_user_view.local_user.email.to_owned().expect("email"); let email = &local_user_view.local_user.email.to_owned().expect("email");
let subject = &format!( let subject = &format!(
"Registration to {} approved for {}", "Registration approved for {}",
settings.hostname, local_user_view.person.name local_user_view.person.actor_id
); );
let html = &format!( let html = &format!(
"Your registration application has been approved. Welcome to {}!", "Your registration application has been approved. Welcome to {}!",
@ -461,3 +462,24 @@ pub fn send_application_approved_email(
); );
send_email(subject, email, &local_user_view.person.name, html, settings) 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 {
// 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(())
}

View File

@ -293,6 +293,3 @@ pub struct GetUnreadCountResponse {
pub struct VerifyEmail { pub struct VerifyEmail {
pub token: String, pub token: String,
} }
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct VerifyEmailResponse {}

View File

@ -147,12 +147,6 @@ impl PerformCrud for GetSite {
person_blocks, person_blocks,
}) })
} else { } else {
// If the site is setup, private, and there is no auth, return an error
if let Some(site_view) = site_view.to_owned() {
if site_view.site.private_instance {
return Err(LemmyError::from_message("instance_is_private"));
}
}
None None
}; };

View File

@ -257,8 +257,24 @@ impl PerformCrud for Register {
.map_err(|e| e.with_message("community_moderator_already_exists"))?; .map_err(|e| e.with_message("community_moderator_already_exists"))?;
} }
// Log the user in directly if email verification is not required let mut login_response = LoginResponse {
let login_response = if email_verification { jwt: None,
registration_created: false,
verify_email_sent: false,
};
// Log the user in directly if email verification and application aren't required
if !require_application && !email_verification {
login_response.jwt = Some(
Claims::jwt(
inserted_local_user.id.0,
&context.secret().jwt_secret,
&context.settings().hostname,
)?
.into(),
);
} else {
if email_verification {
send_verification_email( send_verification_email(
inserted_local_user.id, inserted_local_user.id,
// we check at the beginning of this method that email is set // we check at the beginning of this method that email is set
@ -268,31 +284,13 @@ impl PerformCrud for Register {
&context.settings(), &context.settings(),
) )
.await?; .await?;
LoginResponse { login_response.verify_email_sent = true;
jwt: None,
verify_email_sent: true,
registration_created: false,
} }
} else if require_application {
LoginResponse { if require_application {
jwt: None, login_response.registration_created = true;
verify_email_sent: false,
registration_created: true,
} }
} else {
LoginResponse {
jwt: Some(
Claims::jwt(
inserted_local_user.id.0,
&context.secret().jwt_secret,
&context.settings().hostname,
)?
.into(),
),
verify_email_sent: false,
registration_created: false,
} }
};
Ok(login_response) Ok(login_response)
} }

View File

@ -1,4 +1,4 @@
use crate::{source::email_verification::*, traits::Crud}; use crate::{newtypes::LocalUserId, source::email_verification::*, traits::Crud};
use diesel::{insert_into, result::Error, ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl}; use diesel::{insert_into, result::Error, ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
impl Crud for EmailVerification { impl Crud for EmailVerification {
@ -36,4 +36,11 @@ impl EmailVerification {
.filter(verification_token.eq(token)) .filter(verification_token.eq(token))
.first::<Self>(conn) .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

@ -1,5 +1,5 @@
use crate::{source::registration_application::*, traits::Crud}; use crate::{newtypes::LocalUserId, source::registration_application::*, traits::Crud};
use diesel::{insert_into, result::Error, PgConnection, QueryDsl, RunQueryDsl}; use diesel::{insert_into, result::Error, ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
impl Crud for RegistrationApplication { impl Crud for RegistrationApplication {
type Form = RegistrationApplicationForm; type Form = RegistrationApplicationForm;
@ -28,3 +28,15 @@ impl Crud for RegistrationApplication {
diesel::delete(registration_application.find(id_)).execute(conn) 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

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

@ -29,6 +29,12 @@ pub fn send_email(
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]

View File

@ -11,5 +11,4 @@ create table email_verification (
local_user_id int references local_user(id) on update cascade on delete cascade not null, local_user_id int references local_user(id) on update cascade on delete cascade not null,
email text not null, email text not null,
verification_token text not null verification_token text not null
); );