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

Add email verification #63

Merged
merged 40 commits into from
Nov 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
5e58fc3
Make the mail sending work
randomairborne Nov 16, 2021
c90bfe0
fix an issue zero caught instantly
randomairborne Nov 16, 2021
aecf7b6
A really horrible commit that adds full verification support.
randomairborne Nov 16, 2021
c7665b1
Merge remote-tracking branch 'origin/develop' into develop
randomairborne Nov 16, 2021
c986182
fix evil indents
randomairborne Nov 16, 2021
66a78aa
remove an unwrap
randomairborne Nov 16, 2021
60da52d
add verified column to users table
tazz4843 Nov 16, 2021
8366ee8
convert u128 to BigDecimal
tazz4843 Nov 16, 2021
02537af
Update verify_user.rs
tazz4843 Nov 16, 2021
37839b8
fix mismatched types
tazz4843 Nov 16, 2021
8048054
fix a few bugs and make code slightly easier to read
tazz4843 Nov 16, 2021
3205b37
use relative path instead of absolute
tazz4843 Nov 16, 2021
7c1317f
fetch the db
tazz4843 Nov 16, 2021
1a38ce9
fetch db (v2)
tazz4843 Nov 16, 2021
3664db2
fix a handful more typos
tazz4843 Nov 16, 2021
e8c74dc
couple more typos
tazz4843 Nov 16, 2021
c887afb
as_str not as_string
tazz4843 Nov 16, 2021
d8d3b94
fix cannot find value in this scope
tazz4843 Nov 16, 2021
5ec4fe3
Update verify_user.rs
tazz4843 Nov 16, 2021
73c4048
fix formatting
randomairborne Nov 16, 2021
5032f74
Merge remote-tracking branch 'origin/develop' into develop
randomairborne Nov 16, 2021
489e4e0
fix a couple more "cannot find value" errors
tazz4843 Nov 16, 2021
7b9dd24
fix a few more errors
tazz4843 Nov 16, 2021
79885b8
fix moved value error
tazz4843 Nov 16, 2021
79bbfbb
just had to move some stuff
randomairborne Nov 16, 2021
4aa1640
perhaps fix borrow after move error
tazz4843 Nov 16, 2021
8e472a0
fix some warnings
tazz4843 Nov 16, 2021
856e3fa
fix a few more warnings
tazz4843 Nov 16, 2021
a82ccbf
async emails
randomairborne Nov 16, 2021
4fb78d9
last warning
tazz4843 Nov 16, 2021
e1c408d
add supported systems
randomairborne Nov 16, 2021
b019076
Merge remote-tracking branch 'origin/develop' into develop
randomairborne Nov 16, 2021
98c96ce
enable crate features
randomairborne Nov 16, 2021
9e6803a
disable other crate features
randomairborne Nov 16, 2021
46a7b30
enable crate features
randomairborne Nov 16, 2021
0093ee2
update cargo
randomairborne Nov 16, 2021
4729848
fix mismatched types
tazz4843 Nov 16, 2021
2db90df
one more dep
randomairborne Nov 16, 2021
3343964
Merge remote-tracking branch 'origin/develop' into develop
randomairborne Nov 16, 2021
da174b8
cargo fmt
randomairborne Nov 16, 2021
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
1,024 changes: 1,004 additions & 20 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ cat /proc/cpuinfo | grep pclmulqdq # This one is required for simd-json to compi
cat /proc/cpuinfo | grep avx2 # Either this one or...
cat /proc/cpuinfo | grep sse4_2 # this one are required as well as pclmulqdq
```
### What operating systems do you support?
For the client.... almost all of them. For the server, Linux and FreeBSD are supported.
2 changes: 2 additions & 0 deletions ferrischat_webserver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ nix = { git = "https://github.com/nix-rust/nix" }
http = "0.2"
bytes = "*"
tokio-tungstenite = "0.15"
lettre = { version = "0.10.0-rc.4", features = ["tokio1", "tokio1_rustls", "tokio1-rustls-tls", "builder", "pool", "hostname", "smtp-transport"], default-features = false }
check-if-email-exists = "0.8"
simd-json = { version = "0.4", features = ["128bit"] }

ferrischat_db = { path = "../ferrischat_db", version = "0.1" }
Expand Down
1 change: 1 addition & 0 deletions ferrischat_webserver/src/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ mod token_gen;

pub use auth_struct::Authorization;
pub use get_token::*;
pub use token_gen::*;
26 changes: 18 additions & 8 deletions ferrischat_webserver/src/entrypoint.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
use actix_web::http::StatusCode;
use actix_web::{web, App, HttpResponse, HttpServer};
use ring::rand::{SecureRandom, SystemRandom};

use ferrischat_auth::init_auth;
use ferrischat_db::load_db;
use ferrischat_macros::expand_version;
use ferrischat_redis::load_redis;
use ferrischat_ws::{init_ws_server, preload_ws};

use crate::auth::get_token;
use crate::channels::*;
use crate::guilds::*;
Expand All @@ -7,14 +17,6 @@ use crate::messages::*;
use crate::not_implemented::not_implemented;
use crate::users::*;
use crate::ws::*;
use actix_web::http::StatusCode;
use actix_web::{web, App, HttpResponse, HttpServer};
use ferrischat_auth::init_auth;
use ferrischat_db::load_db;
use ferrischat_macros::expand_version;
use ferrischat_redis::load_redis;
use ferrischat_ws::{init_ws_server, preload_ws};
use ring::rand::{SecureRandom, SystemRandom};

#[allow(clippy::expect_used)]
pub async fn entrypoint() {
Expand Down Expand Up @@ -180,6 +182,14 @@ pub async fn entrypoint() {
expand_version!("guilds/{guild_id}/members/{user_id}/role/{role_id}"),
web::delete().to(roles::remove_member_role),
)
.route(
expand_version!("verify"),
web::post().to(send_verification_email),
)
.route(
expand_version!("verify/{token}"),
web::get().to(verify_email),
)
.route(
expand_version!("teapot"),
web::get().to(async || HttpResponse::new(StatusCode::IM_A_TEAPOT)),
Expand Down
2 changes: 2 additions & 0 deletions ferrischat_webserver/src/users/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ mod create_user;
mod delete_user;
mod edit_user;
mod get_user;
mod verify_user;

pub use create_user::*;
pub use delete_user::*;
pub use edit_user::*;
pub use get_user::*;
pub use verify_user::*;
188 changes: 188 additions & 0 deletions ferrischat_webserver/src/users/verify_user.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
use actix_web::{web, HttpResponse, Responder};
use check_if_email_exists::{check_email, CheckEmailInput, Reachable};
use ferrischat_common::types::{InternalServerErrorJson, NotFoundJson};
use lettre::{
transport::smtp::authentication::Credentials, AsyncSmtpTransport, AsyncTransport, Message,
Tokio1Executor,
};

use ferrischat_redis::{redis::AsyncCommands, REDIS_MANAGER};
/// POST /v0/verify
pub async fn send_verification_email(auth: crate::Authorization) -> impl Responder {
let db = get_db_or_fail!();
let authorized_user = auth.0;
let user_email = match sqlx::query!(
"SELECT email FROM users WHERE id = $1",
u128_to_bigdecimal!(authorized_user)
)
.fetch_one(db)
.await
{
Ok(email) => email.email,
Err(e) => {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: format!("Database returned a error: {}", e),
})
}
};
let checker_input = CheckEmailInput::new(vec![user_email.clone().into()]);
let checked_email = check_email(&checker_input).await;
if checked_email[0].syntax.is_valid_syntax {
if checked_email[0].is_reachable == Reachable::Safe
|| checked_email[0].is_reachable == Reachable::Risky
|| checked_email[0].is_reachable == Reachable::Unknown
{
// get configs
let mut redis = REDIS_MANAGER
.get()
.expect("redis not initialized: call load_redis before this")
.clone();
let host = match redis
.get::<String, String>("config:email:host".to_string())
.await
{
Ok(r) => r,
Err(_) => {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: format!("No SMTP server host set."),
})
}
};
let username = match redis
.get::<String, String>("config:email:username".to_string())
.await
{
Ok(r) => r,
Err(_) => {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: format!("No SMTP server username set."),
})
}
};
let password = match redis
.get::<String, String>("config:email:password".to_string())
.await
{
Ok(r) => r,
Err(_) => {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: format!("No SMTP server password set."),
})
}
};
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(),
})
}
};
let message = match Message::builder()
.from("Ferris <[email protected]>".parse().unwrap())
.reply_to("Ferris <[email protected]>".parse().unwrap())
.to(user_email.parse().unwrap())
.subject("FerrisChat Email Verification")
.body(String::from(format!("Welcome to FerrisChat!<br><a href=\"https://api.ferris.chat/v0/verify/{}\">Click here to verify \
your email!</a> (expires in 1 hour) <br><br> If you don't know what this is, reset your token and change \
your password ASAP.", token))) {
Ok(m) => m,
Err(e) => return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: format!("This should not have happened. Submit a bug report on \
https://github.com/ferrischat/server/issues with the error `{}`", e),
}),
};

let mail_creds = Credentials::new(username.to_string(), password.to_string());

// Open a remote 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 {
reason: format!(
"Error creating SMTP transport! Please submit a bug report on \
https://github.com/ferrischat/server/issues with the error `{}`",
e
),
})
}
};

// Send the email
if let Err(e) = mailer.send(message).await {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: format!(
"Mailer failed to send correctly! Please submit a bug report \
on https://github.com/ferrischat/server/issues with the error `{}`",
e
),
});
}
// writes the token to redis
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),
});
}
HttpResponse::Ok().finish()
} else {
HttpResponse::Conflict().json(InternalServerErrorJson {
reason: format!("Email deemed unsafe to send to. Is it a real email?"),
})
}
} else {
HttpResponse::Conflict().json(InternalServerErrorJson {
reason: format!("Email {} is invalid.", user_email),
})
}
}
/// GET /v0/verify/{token}
pub async fn verify_email(path: web::Path<String>) -> impl Responder {
let token = path.into_inner();
let redis_key = format!("email:tokens:{}", token);

let mut redis = REDIS_MANAGER
.get()
.expect("redis not initialized: call load_redis before this")
.clone();
let email = match redis.get::<String, Option<String>>(redis_key.clone()).await {
Ok(Some(email)) => {
if let Err(e) = redis.del::<String, i32>(redis_key).await {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: format!("Redis returned a error: {}", e),
});
}
email
}
Ok(None) => {
return HttpResponse::NotFound().json(NotFoundJson {
message: format!("This token has expired or was not found."),
});
}
Err(e) => {
return HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: format!("Redis returned a error: {}", e),
});
}
};
let db = get_db_or_fail!();
if let Err(e) = sqlx::query!("UPDATE users SET verified = true WHERE email = $1", email)
.execute(db)
.await
{
HttpResponse::InternalServerError().json(InternalServerErrorJson {
reason: format!("Database returned a error: {}", e),
})
} else {
HttpResponse::Ok().body("Verified email. You can close this page.")
}
}
2 changes: 2 additions & 0 deletions migrations/20211116070235_add_verified_column.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- Add migration script here
ALTER TABLE users ADD COLUMN verified BOOLEAN DEFAULT false;