Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 61 additions & 65 deletions src/controllers/user/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@ use crate::auth::AuthCheck;
use crate::controllers::helpers::ok_true;
use crate::models::NewEmail;
use crate::schema::{emails, users};
use crate::tasks::spawn_blocking;
use crate::util::diesel::prelude::*;
use crate::util::errors::{bad_request, server_error, AppResult};
use axum::extract::Path;
use axum::response::Response;
use axum::Json;
use diesel_async::async_connection_wrapper::AsyncConnectionWrapper;
use http::request::Parts;
use lettre::Address;
use secrecy::{ExposeSecret, SecretString};
Expand All @@ -32,85 +30,83 @@ pub async fn update_user(
req: Parts,
Json(user_update): Json<UserUpdate>,
) -> AppResult<Response> {
use diesel_async::RunQueryDsl;

let mut conn = state.db_write().await?;
let auth = AuthCheck::default().check(&req, &mut conn).await?;
spawn_blocking(move || {
use diesel::RunQueryDsl;

let conn: &mut AsyncConnectionWrapper<_> = &mut conn.into();

let user = auth.user();
let user = auth.user();

// need to check if current user matches user to be updated
if user.id != param_user_id {
return Err(bad_request("current user does not match requested user"));
}
// need to check if current user matches user to be updated
if user.id != param_user_id {
return Err(bad_request("current user does not match requested user"));
}

if let Some(publish_notifications) = &user_update.user.publish_notifications {
if user.publish_notifications != *publish_notifications {
diesel::update(user)
.set(users::publish_notifications.eq(*publish_notifications))
.execute(conn)?;
if let Some(publish_notifications) = &user_update.user.publish_notifications {
if user.publish_notifications != *publish_notifications {
diesel::update(user)
.set(users::publish_notifications.eq(*publish_notifications))
.execute(&mut conn)
.await?;

if !publish_notifications {
let email_address = user.verified_email(conn)?;
if !publish_notifications {
let email_address = user.async_verified_email(&mut conn).await?;

if let Some(email_address) = email_address {
let email = PublishNotificationsUnsubscribeEmail {
user_name: &user.gh_login,
domain: &state.emails.domain,
};
if let Some(email_address) = email_address {
let email = PublishNotificationsUnsubscribeEmail {
user_name: &user.gh_login,
domain: &state.emails.domain,
};

if let Err(error) = state.emails.send(&email_address, email) {
warn!("Failed to send publish notifications unsubscribe email to {email_address}: {error}");
}
if let Err(error) = state.emails.async_send(&email_address, email).await {
warn!("Failed to send publish notifications unsubscribe email to {email_address}: {error}");
}
}
}
}
}

if let Some(user_email) = &user_update.user.email {
let user_email = user_email.trim();

if user_email.is_empty() {
return Err(bad_request("empty email rejected"));
}
if let Some(user_email) = &user_update.user.email {
let user_email = user_email.trim();

user_email
.parse::<Address>()
.map_err(|_| bad_request("invalid email address"))?;

let new_email = NewEmail {
user_id: user.id,
email: user_email,
};

let token = diesel::insert_into(emails::table)
.values(&new_email)
.on_conflict(emails::user_id)
.do_update()
.set(&new_email)
.returning(emails::token)
.get_result::<String>(conn)
.map(SecretString::from)
.map_err(|_| server_error("Error in creating token"))?;

// This swallows any errors that occur while attempting to send the email. Some users have
// an invalid email set in their GitHub profile, and we should let them sign in even though
// we're trying to silently use their invalid address during signup and can't send them an
// email. They'll then have to provide a valid email address.
let email = UserConfirmEmail {
user_name: &user.gh_login,
domain: &state.emails.domain,
token,
};

let _ = state.emails.send(user_email, email);
if user_email.is_empty() {
return Err(bad_request("empty email rejected"));
}

ok_true()
})
.await
user_email
.parse::<Address>()
.map_err(|_| bad_request("invalid email address"))?;

let new_email = NewEmail {
user_id: user.id,
email: user_email,
};

let token = diesel::insert_into(emails::table)
.values(&new_email)
.on_conflict(emails::user_id)
.do_update()
.set(&new_email)
.returning(emails::token)
.get_result::<String>(&mut conn)
.await
.map(SecretString::from)
.map_err(|_| server_error("Error in creating token"))?;

// This swallows any errors that occur while attempting to send the email. Some users have
// an invalid email set in their GitHub profile, and we should let them sign in even though
// we're trying to silently use their invalid address during signup and can't send them an
// email. They'll then have to provide a valid email address.
let email = UserConfirmEmail {
user_name: &user.gh_login,
domain: &state.emails.domain,
token,
};

let _ = state.emails.async_send(user_email, email).await;
}

ok_true()
}

pub struct UserConfirmEmail<'a> {
Expand Down
16 changes: 16 additions & 0 deletions src/models/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,22 @@ impl User {
.optional()
}

/// Queries the database for the verified emails
/// belonging to a given user
pub async fn async_verified_email(
&self,
conn: &mut AsyncPgConnection,
) -> QueryResult<Option<String>> {
use diesel_async::RunQueryDsl;

Email::belonging_to(self)
.select(emails::email)
.filter(emails::verified.eq(true))
.first(conn)
.await
.optional()
}

/// Queries for the email belonging to a particular user
pub fn email(&self, conn: &mut impl Conn) -> QueryResult<Option<String>> {
use diesel::RunQueryDsl;
Expand Down