WIP email sending for registration
This commit is contained in:
parent
8d1bffc899
commit
f938a6b46b
77
Cargo.lock
generated
77
Cargo.lock
generated
@ -746,6 +746,7 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"headers",
|
"headers",
|
||||||
"http",
|
"http",
|
||||||
|
"lettre",
|
||||||
"maud",
|
"maud",
|
||||||
"notify",
|
"notify",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@ -947,6 +948,22 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "email-encoding"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbfb21b9878cf7a348dcb8559109aabc0ec40d69924bd706fa5149846c4fef75"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.4",
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "email_address"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2153bd83ebc09db15bcbdc3e2194d901804952e3dc96967e1cd3b0c5c32d112"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.33"
|
version = "0.8.33"
|
||||||
@ -1428,6 +1445,17 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hostname"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"match_cfg",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "html5ever"
|
name = "html5ever"
|
||||||
version = "0.25.2"
|
version = "0.25.2"
|
||||||
@ -1562,6 +1590,16 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-bidi",
|
||||||
|
"unicode-normalization",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -1720,6 +1758,29 @@ dependencies = [
|
|||||||
"spin 0.5.2",
|
"spin 0.5.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lettre"
|
||||||
|
version = "0.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76bd09637ae3ec7bd605b8e135e757980b3968430ff2b1a4a94fb7769e50166d"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.4",
|
||||||
|
"email-encoding",
|
||||||
|
"email_address",
|
||||||
|
"fastrand 1.9.0",
|
||||||
|
"futures-util",
|
||||||
|
"hostname",
|
||||||
|
"httpdate",
|
||||||
|
"idna 0.3.0",
|
||||||
|
"mime",
|
||||||
|
"native-tls",
|
||||||
|
"nom",
|
||||||
|
"once_cell",
|
||||||
|
"quoted_printable",
|
||||||
|
"socket2 0.4.9",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.148"
|
version = "0.2.148"
|
||||||
@ -1826,6 +1887,12 @@ dependencies = [
|
|||||||
"xml5ever",
|
"xml5ever",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "match_cfg"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchers"
|
name = "matchers"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -2432,6 +2499,12 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quoted_printable"
|
||||||
|
version = "0.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a3866219251662ec3b26fc217e3e05bf9c4f84325234dfb96bf0bf840889e49"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
@ -3734,7 +3807,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
|
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"idna",
|
"idna 0.4.0",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3766,7 +3839,7 @@ version = "0.16.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b92f40481c04ff1f4f61f304d61793c7b56ff76ac1469f1beb199b1445b253bd"
|
checksum = "b92f40481c04ff1f4f61f304d61793c7b56ff76ac1469f1beb199b1445b253bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"idna",
|
"idna 0.4.0",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -28,6 +28,7 @@ feed-rs = "1.3"
|
|||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
headers = "0.3"
|
headers = "0.3"
|
||||||
http = "0.2.9"
|
http = "0.2.9"
|
||||||
|
lettre = { version = "0.10", features = ["builder"] }
|
||||||
maud = { version = "0.25", features = ["axum"] }
|
maud = { version = "0.25", features = ["axum"] }
|
||||||
notify = "6"
|
notify = "6"
|
||||||
once_cell = "1.18"
|
once_cell = "1.18"
|
||||||
|
@ -21,6 +21,9 @@ Install these requirements to get started developing crawlnicle.
|
|||||||
|
|
||||||
* [just](https://github.com/casey/just#installation)
|
* [just](https://github.com/casey/just#installation)
|
||||||
* [bun](https://bun.sh)
|
* [bun](https://bun.sh)
|
||||||
|
* An [SMTP server for sending
|
||||||
|
emails](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol) (put
|
||||||
|
configuration in the `.env` file)
|
||||||
* (optional) [cargo-watch](https://github.com/watchexec/cargo-watch#install) for
|
* (optional) [cargo-watch](https://github.com/watchexec/cargo-watch#install) for
|
||||||
auto-recompiling the server in development
|
auto-recompiling the server in development
|
||||||
* (optional) [mold](https://github.com/rui314/mold#installation) for faster
|
* (optional) [mold](https://github.com/rui314/mold#installation) for faster
|
||||||
@ -59,6 +62,9 @@ builds
|
|||||||
TITLE=crawlnicle
|
TITLE=crawlnicle
|
||||||
MAX_MEM_LOG_SIZE=1000000
|
MAX_MEM_LOG_SIZE=1000000
|
||||||
CONTENT_DIR=./content
|
CONTENT_DIR=./content
|
||||||
|
SMTP_SERVER=smtp.gmail.com
|
||||||
|
SMTP_USER=user
|
||||||
|
SMTP_PASSWORD=password
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Run `just migrate` (or `sqlx migrate run`) which will run all the database
|
1. Run `just migrate` (or `sqlx migrate run`) which will run all the database
|
||||||
|
2
justfile
2
justfile
@ -41,7 +41,7 @@ watch-frontend: install-frontend
|
|||||||
-s 'just build-dev-frontend'
|
-s 'just build-dev-frontend'
|
||||||
|
|
||||||
watch-backend:
|
watch-backend:
|
||||||
mold -run cargo watch \
|
cargo watch \
|
||||||
--ignore 'logs/*' \
|
--ignore 'logs/*' \
|
||||||
--ignore 'static/*' \
|
--ignore 'static/*' \
|
||||||
--ignore 'frontend/*' \
|
--ignore 'frontend/*' \
|
||||||
|
@ -18,4 +18,10 @@ pub struct Config {
|
|||||||
pub max_mem_log_size: usize,
|
pub max_mem_log_size: usize,
|
||||||
#[clap(long, env)]
|
#[clap(long, env)]
|
||||||
pub content_dir: String,
|
pub content_dir: String,
|
||||||
|
#[clap(long, env)]
|
||||||
|
pub smtp_server: String,
|
||||||
|
#[clap(long, env)]
|
||||||
|
pub smtp_user: String,
|
||||||
|
#[clap(long, env)]
|
||||||
|
pub smtp_password: String,
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
use axum::response::{IntoResponse, Response};
|
use axum::response::{IntoResponse, Response};
|
||||||
use axum::TypedHeader;
|
use axum::TypedHeader;
|
||||||
use axum::{extract::State, Form};
|
use axum::{extract::State, Form};
|
||||||
|
use lettre::message::header::ContentType;
|
||||||
|
use lettre::message::{Mailbox, Message};
|
||||||
|
use lettre::{SmtpTransport, Transport};
|
||||||
use maud::html;
|
use maud::html;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_with::{serde_as, NoneAsEmptyString};
|
use serde_with::{serde_as, NoneAsEmptyString};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
use crate::htmx::{HXTarget, HXRedirect};
|
use crate::htmx::{HXRedirect, HXTarget};
|
||||||
use crate::models::user::{AuthContext, CreateUser, User};
|
use crate::models::user::{AuthContext, CreateUser, User};
|
||||||
use crate::partials::layout::Layout;
|
use crate::partials::layout::Layout;
|
||||||
use crate::partials::register_form::{register_form, RegisterFormProps};
|
use crate::partials::register_form::{register_form, RegisterFormProps};
|
||||||
@ -38,6 +41,7 @@ pub async fn get(hx_target: Option<TypedHeader<HXTarget>>, layout: Layout) -> Re
|
|||||||
|
|
||||||
pub async fn post(
|
pub async fn post(
|
||||||
State(pool): State<PgPool>,
|
State(pool): State<PgPool>,
|
||||||
|
State(mailer): State<SmtpTransport>,
|
||||||
mut auth: AuthContext,
|
mut auth: AuthContext,
|
||||||
Form(register): Form<Register>,
|
Form(register): Form<Register>,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
@ -111,6 +115,28 @@ pub async fn post(
|
|||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: don't 500 error on email send failure, render form with error message instead
|
||||||
|
let mailbox = Mailbox::new(
|
||||||
|
user.name.clone(),
|
||||||
|
user.email.parse().map_err(|_| Error::InternalServerError)?,
|
||||||
|
);
|
||||||
|
let email = Message::builder()
|
||||||
|
// TODO: make from address configurable and store in config already parsed
|
||||||
|
.from("crawlnicle <accounts@mail.crawlnicle.com>".parse().unwrap())
|
||||||
|
.to(mailbox)
|
||||||
|
.subject("Welcome to crawlnicle, please confirm your email address")
|
||||||
|
.header(ContentType::TEXT_PLAIN)
|
||||||
|
// TODO: fill in email body, use maud to create HTML body
|
||||||
|
.body(String::from("TODO"))
|
||||||
|
.map_err(|_| Error::InternalServerError)?;
|
||||||
|
|
||||||
|
// TODO: do email sending in a background async task
|
||||||
|
// TODO: notify the user that email has been sent somehow
|
||||||
|
mailer
|
||||||
|
.send(&email)
|
||||||
|
.map_err(|_| Error::InternalServerError)?;
|
||||||
|
|
||||||
auth.login(&user)
|
auth.login(&user)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| Error::InternalServerError)?;
|
.map_err(|_| Error::InternalServerError)?;
|
||||||
|
11
src/main.rs
11
src/main.rs
@ -14,6 +14,8 @@ use axum_login::{
|
|||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use dotenvy::dotenv;
|
use dotenvy::dotenv;
|
||||||
|
use lettre::transport::smtp::authentication::Credentials;
|
||||||
|
use lettre::SmtpTransport;
|
||||||
use notify::Watcher;
|
use notify::Watcher;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
@ -78,6 +80,14 @@ async fn main() -> Result<()> {
|
|||||||
.with_query("select * from users where user_id = $1");
|
.with_query("select * from users where user_id = $1");
|
||||||
let auth_layer = AuthLayer::new(user_store, &secret);
|
let auth_layer = AuthLayer::new(user_store, &secret);
|
||||||
|
|
||||||
|
let creds = Credentials::new(config.smtp_user.clone(), config.smtp_password.clone());
|
||||||
|
|
||||||
|
// Open a remote connection to gmail
|
||||||
|
let mailer = SmtpTransport::relay(&config.smtp_server)
|
||||||
|
.unwrap()
|
||||||
|
.credentials(creds)
|
||||||
|
.build();
|
||||||
|
|
||||||
sqlx::migrate!().run(&pool).await?;
|
sqlx::migrate!().run(&pool).await?;
|
||||||
|
|
||||||
let crawl_scheduler = CrawlSchedulerHandle::new(
|
let crawl_scheduler = CrawlSchedulerHandle::new(
|
||||||
@ -128,6 +138,7 @@ async fn main() -> Result<()> {
|
|||||||
crawl_scheduler,
|
crawl_scheduler,
|
||||||
importer,
|
importer,
|
||||||
imports,
|
imports,
|
||||||
|
mailer,
|
||||||
})
|
})
|
||||||
.layer(ServiceBuilder::new().layer(TraceLayer::new_for_http()))
|
.layer(ServiceBuilder::new().layer(TraceLayer::new_for_http()))
|
||||||
.layer(auth_layer)
|
.layer(auth_layer)
|
||||||
|
@ -3,6 +3,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use axum::extract::FromRef;
|
use axum::extract::FromRef;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
use lettre::SmtpTransport;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use tokio::sync::{broadcast, watch, Mutex};
|
use tokio::sync::{broadcast, watch, Mutex};
|
||||||
@ -47,6 +48,7 @@ pub struct AppState {
|
|||||||
pub crawl_scheduler: CrawlSchedulerHandle,
|
pub crawl_scheduler: CrawlSchedulerHandle,
|
||||||
pub importer: ImporterHandle,
|
pub importer: ImporterHandle,
|
||||||
pub imports: Imports,
|
pub imports: Imports,
|
||||||
|
pub mailer: SmtpTransport,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromRef<AppState> for PgPool {
|
impl FromRef<AppState> for PgPool {
|
||||||
@ -102,3 +104,9 @@ impl FromRef<AppState> for Imports {
|
|||||||
state.imports.clone()
|
state.imports.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromRef<AppState> for SmtpTransport {
|
||||||
|
fn from_ref(state: &AppState) -> Self {
|
||||||
|
state.mailer.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user