Skip to content
This repository has been archived by the owner on Nov 23, 2022. It is now read-only.

Implement fetch bots by user #71

Merged
merged 14 commits into from
Nov 24, 2021
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
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion ferrischat_webserver/src/entrypoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ pub async fn entrypoint() {
expand_version!("users/{user_id}/bots"),
web::post().to(create_bot),
)
// GET /users/{user_id}/bots
.route(
expand_version!("users/{user_id}/bots"),
web::get().to(get_bots_by_user),
)
// PATCH /users/{user_id}/bots/{bot_id}
.route(
expand_version!("users/{user_id}/bots/{bot_id}"),
Expand Down Expand Up @@ -224,7 +229,7 @@ pub async fn entrypoint() {
.max_connection_rate(8192)
.bind_uds(format!(
"{}/webserver.sock",
std::env::var("FERRISCHAT_HOME").unwrap_or("/etc/ferrischat/".to_string())
std::env::var("FERRISCHAT_HOME").unwrap_or_else(|_| "/etc/ferrischat/".to_string())
))
.expect("failed to bind to unix socket!")
.run()
Expand Down
51 changes: 51 additions & 0 deletions ferrischat_webserver/src/users/bots/get_bots_by_user.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use actix_web::{HttpResponse, Responder};
use ferrischat_common::types::{BotsOwnedByUser, InternalServerErrorJson, User, UserFlags};

/// GET `/api/v0/users/{user_id}/bots`
/// Get all bots owned by the user
pub async fn get_bots_by_user(auth: crate::Authorization) -> impl Responder {
let bigint_user_id = u128_to_bigdecimal!(auth.0);

let db = get_db_or_fail!();

let resp = sqlx::query!("SELECT * FROM bots WHERE owner_id = $1", bigint_user_id)
.fetch_all(db)
.await;
match resp {
Ok(resp) => {
let mut bots = Vec::with_capacity(resp.len());
for x in resp {
let resp = sqlx::query!("SELECT * FROM users WHERE id = $1", x.user_id.clone())
.fetch_one(db)
.await;

match resp {
Ok(user) => bots.push(User {
id: bigdecimal_to_u128!(user.id),
name: user.name,
avatar: None,
guilds: None,
discriminator: user.discriminator,
flags: UserFlags::from_bits_truncate(user.flags),
pronouns: user
.pronouns
.and_then(ferrischat_common::types::Pronouns::from_i16),
}),
Err(e) => {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: format!("database returned a error: {}", e),
is_bug: false,
link: None,
})
}
}
}
HttpResponse::Ok().json(BotsOwnedByUser { bots })
}
Err(e) => HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: format!("database returned a error: {}", e),
is_bug: false,
link: None,
}),
}
}
2 changes: 2 additions & 0 deletions ferrischat_webserver/src/users/bots/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod create_bot;
mod delete_bot;
mod edit_bot;
mod get_bots_by_user;

pub use create_bot::*;
pub use delete_bot::*;
pub use edit_bot::*;
pub use get_bots_by_user::*;
4 changes: 1 addition & 3 deletions ferrischat_webserver/src/users/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ mod edit_user;
mod get_user;
mod verify_user;

pub use bots::create_bot;
pub use bots::delete_bot;
pub use bots::edit_bot;
pub use bots::*;
pub use create_user::*;
pub use delete_user::*;
pub use edit_user::*;
Expand Down
224 changes: 108 additions & 116 deletions ferrischat_webserver/src/users/verify_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,59 +59,63 @@ pub async fn send_verification_email(auth: crate::Authorization) -> impl Respond
checker_input.set_smtp_timeout(Duration::new(5, 0));
let checked_email = check_email(&checker_input).await;
if checked_email[0].syntax.is_valid_syntax {
if checked_email[0].is_reachable != Reachable::Invalid {
// Get configurations, they're set in redis for speed reasons. Set them with redis-cli `set config:email:<setting> <value>`
let mut redis = REDIS_MANAGER
.get()
.expect("redis not initialized: call load_redis before this")
.clone();
let host = match redis
// FQDN of the SMTP server
.get::<String, String>("config:email:host".to_string())
.await
{
Ok(r) => r,
Err(_) => {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: "No SMTP server host set.".to_string(),
is_bug: false,
link: None,
});
}
};
let username = match redis
// FULL SMTP username, e.g. `[email protected]`
.get::<String, String>("config:email:username".to_string())
.await
{
Ok(r) => r,
Err(_) => {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: "No SMTP server username set.".to_string(),
is_bug: false,
link: None,
});
}
};
let password = match redis
// SMTP password
.get::<String, String>("config:email:password".to_string())
.await
{
Ok(r) => r,
Err(_) => {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: "No SMTP server password set.".to_string(),
is_bug: false,
link: None,
});
}
};
// This generates a random string that can be used to verify that the request is actually from the email owner
let token = match crate::auth::generate_random_bits() {
Some(b) => base64::encode_config(b, base64::URL_SAFE),
None => {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
if checked_email[0].is_reachable == Reachable::Invalid {
return HttpResponse::Conflict().json(Json {
message: "Email deemed unsafe to send to. Is it a real email?".to_string(),
});
}
// Get configurations, they're set in redis for speed reasons. Set them with redis-cli `set config:email:<setting> <value>`
let mut redis = REDIS_MANAGER
.get()
.expect("redis not initialized: call load_redis before this")
.clone();
let host = match redis
// FQDN of the SMTP server
.get::<String, String>("config:email:host".to_string())
.await
{
Ok(r) => r,
Err(_) => {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: "No SMTP server host set.".to_string(),
is_bug: false,
link: None,
});
}
};
let username = match redis
// FULL SMTP username, e.g. `[email protected]`
.get::<String, String>("config:email:username".to_string())
.await
{
Ok(r) => r,
Err(_) => {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: "No SMTP server username set.".to_string(),
is_bug: false,
link: None,
});
}
};
let password = match redis
// SMTP password
.get::<String, String>("config:email:password".to_string())
.await
{
Ok(r) => r,
Err(_) => {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: "No SMTP server password set.".to_string(),
is_bug: false,
link: None,
});
}
};
// This generates a random string that can be used to verify that the request is actually from the email owner
let token = match crate::auth::generate_random_bits() {
Some(b) => base64::encode_config(b, base64::URL_SAFE),
None => {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: "failed to generate random bits for token generation".to_string(),
is_bug: true,
link: Some(
Expand All @@ -120,25 +124,25 @@ pub async fn send_verification_email(auth: crate::Authorization) -> impl Respond
.to_string(),
),
});
}
};
// Default email.
// TODO HTML rather then plaintext
// Also encodes the email to be URL-safe, however some work is needed on it still
let default_email = format!(
"Click here to verify your email: https://api.ferris.chat/v0/verify/{}",
urlencoding::encode(token.as_str()).into_owned()
);
// Builds the message with a hardcoded subject and sender full name
let message = match Message::builder()
.from(format!("Ferris <{}>", username).parse().unwrap())
.to(user_email.parse().unwrap())
.subject("FerrisChat Email Verification")
.body(default_email)
{
Ok(m) => m,
Err(e) => {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
}
};
// Default email.
// TODO HTML rather then plaintext
// Also encodes the email to be URL-safe, however some work is needed on it still
let default_email = format!(
"Click here to verify your email: https://api.ferris.chat/v0/verify/{}",
urlencoding::encode(token.as_str()).into_owned()
);
// Builds the message with a hardcoded subject and sender full name
let message = match Message::builder()
.from(format!("Ferris <{}>", username).parse().unwrap())
.to(user_email.parse().unwrap())
.subject("FerrisChat Email Verification")
.body(default_email)
{
Ok(m) => m,
Err(e) => {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: format!(
"This should not have happened. Submit a bug report with the error `{}`",
e
Expand All @@ -150,17 +154,14 @@ pub async fn send_verification_email(auth: crate::Authorization) -> impl Respond
.to_string(),
),
});
}
};
// simply gets credentials for the SMTP server
let mail_creds = Credentials::new(username.to_string(), password.to_string());
}
};
// simply gets credentials for the SMTP server
let mail_creds = Credentials::new(username.to_string(), password.to_string());

// Open a remote, asynchronous connection to the mailserver
let mailer: AsyncSmtpTransport<Tokio1Executor> = match AsyncSmtpTransport::<
Tokio1Executor,
>::starttls_relay(
host.as_str()
) {
// Open a remote, asynchronous connection to the mailserver
let mailer: AsyncSmtpTransport<Tokio1Executor> =
match AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(host.as_str()) {
Ok(m) => m.credentials(mail_creds).build(),
Err(e) => {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
Expand All @@ -175,42 +176,33 @@ pub async fn send_verification_email(auth: crate::Authorization) -> impl Respond
}
};

// Send the email
if let Err(e) = mailer.send(message).await {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: format!(
"Mailer failed to send correctly! Contact the administrator of this node and \
// Send the email
if let Err(e) = mailer.send(message).await {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: format!(
"Mailer failed to send correctly! Contact the administrator of this node and \
let them know you got an error while sending the verification email: `{}`",
e
),
is_bug: false,
link: None,
});
}
// writes the token to redis.
// The reason we use the token as the key rather then the value is so we can check against it more easily later, when it's part of the URL.
if let Err(e) = redis
.set_ex::<String, String, String>(
format!("email:tokens:{}", token),
user_email,
86400,
)
.await
{
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: format!("Redis returned a error: {}", e),
is_bug: false,
link: None,
});
}
HttpResponse::Ok().json(Json {
message: "Sent verification, please check your email.".to_string(),
})
} else {
HttpResponse::Conflict().json(Json {
message: "Email deemed unsafe to send to. Is it a real email?".to_string(),
})
e
),
is_bug: false,
link: None,
});
}
// writes the token to redis.
// The reason we use the token as the key rather then the value is so we can check against it more easily later, when it's part of the URL.
if let Err(e) = redis
.set_ex::<String, String, String>(format!("email:tokens:{}", token), user_email, 86400)
.await
{
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: format!("Redis returned a error: {}", e),
is_bug: false,
link: None,
});
}
HttpResponse::Ok().json(Json {
message: "Sent verification, please check your email.".to_string(),
})
} else {
HttpResponse::Conflict().json(Json {
message: format!("Email {} is invalid.", user_email),
Expand Down
Loading