1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-29 22:01:14 +03:00

WIP: email sending crate

This commit is contained in:
Quentin Gliech
2022-01-19 12:10:03 +01:00
parent 29b2fc2e43
commit 2e12f43c0e
7 changed files with 259 additions and 5 deletions

18
crates/email/Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "mas-email"
version = "0.1.0"
authors = ["Quentin Gliech <quenting@element.io>"]
edition = "2021"
license = "Apache-2.0"
[dependencies]
tokio = { version = "1.15.0", features = ["macros"] }
mas-templates = { path = "../templates" }
anyhow = "1.0.52"
async-trait = "0.1.52"
[dependencies.lettre]
version = "0.10.0-rc.4"
default-features = false
features = ["tokio1-rustls-tls", "hostname", "builder", "tracing", "pool"]

78
crates/email/src/lib.rs Normal file
View File

@ -0,0 +1,78 @@
// Copyright 2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use lettre::{
message::{Mailbox, MessageBuilder, MultiPart},
AsyncTransport, Message,
};
use mas_templates::{EmailVerificationContext, Templates};
pub struct Mailer {
templates: Templates,
from: Mailbox,
reply_to: Mailbox,
}
impl Mailer {
pub fn new<T>(templates: &Templates, from: Mailbox, reply_to: Mailbox) -> Self {
Self {
templates: templates.clone(),
from,
reply_to,
}
}
fn base_message(&self) -> MessageBuilder {
Message::builder()
.from(self.from.clone())
.reply_to(self.reply_to.clone())
}
async fn prepare_verification_email(
&self,
to: Mailbox,
context: &EmailVerificationContext,
) -> anyhow::Result<Message> {
let plain = self
.templates
.render_email_verification_txt(context)
.await?;
let html = self
.templates
.render_email_verification_html(context)
.await?;
let multipart = MultiPart::alternative_plain_html(plain, html);
let message = self.base_message().to(to).multipart(multipart)?;
Ok(message)
}
pub async fn send_verification_email<T>(
&self,
transport: &T,
to: Mailbox,
context: &EmailVerificationContext,
) -> anyhow::Result<()>
where
T: AsyncTransport + Send + Sync,
T::Error: std::error::Error + Send + Sync + 'static,
{
let message = self.prepare_verification_email(to, context).await?;
transport.send(message).await?;
Ok(())
}
}

View File

@ -17,7 +17,7 @@
#![allow(clippy::trait_duplication_in_bounds)]
use mas_data_model::{
errors::ErroredForm, AuthorizationGrant, BrowserSession, StorageBackend, UserEmail,
errors::ErroredForm, AuthorizationGrant, BrowserSession, StorageBackend, User, UserEmail,
};
use oauth2_types::errors::OAuth2Error;
use serde::{ser::SerializeStruct, Serialize};
@ -76,6 +76,15 @@ pub trait TemplateContext: Serialize {
Self: Sized;
}
impl TemplateContext for () {
fn sample() -> Vec<Self>
where
Self: Sized,
{
Vec::new()
}
}
/// Context with a CSRF token in it
#[derive(Serialize)]
pub struct WithCsrf<T> {
@ -440,6 +449,40 @@ impl<T: StorageBackend> TemplateContext for AccountEmailsContext<T> {
}
}
/// Context used by the `emails/verification.{txt,html}` templates
#[derive(Serialize)]
pub struct EmailVerificationContext {
user: User<()>,
verification_link: Url,
}
impl EmailVerificationContext {
#[must_use]
pub fn new(user: User<()>, verification_link: Url) -> Self {
Self {
user,
verification_link,
}
}
}
impl TemplateContext for EmailVerificationContext {
fn sample() -> Vec<Self>
where
Self: Sized,
{
User::samples()
.into_iter()
.map(|u| {
Self::new(
u,
Url::parse("https://example.com/emails/verify?code=2134").unwrap(),
)
})
.collect()
}
}
/// Context used by the `form_post.html` template
#[derive(Serialize)]
pub struct FormPostContext<T> {

View File

@ -48,10 +48,10 @@ mod functions;
mod macros;
pub use self::context::{
AccountContext, AccountEmailsContext, EmptyContext, ErrorContext, FormPostContext,
IndexContext, LoginContext, LoginFormField, PostAuthContext, ReauthContext, ReauthFormField,
RegisterContext, RegisterFormField, TemplateContext, WithCsrf, WithOptionalSession,
WithSession,
AccountContext, AccountEmailsContext, EmailVerificationContext, EmptyContext, ErrorContext,
FormPostContext, IndexContext, LoginContext, LoginFormField, PostAuthContext, ReauthContext,
ReauthFormField, RegisterContext, RegisterFormField, TemplateContext, WithCsrf,
WithOptionalSession, WithSession,
};
/// Wrapper around [`tera::Tera`] helping rendering the various templates
@ -312,6 +312,12 @@ register_templates! {
/// Render the HTML error page
pub fn render_error(ErrorContext) { "pages/error.html" }
/// Render the email verification email (plain text variant)
pub fn render_email_verification_txt(EmailVerificationContext) { "emails/verification.txt" }
/// Render the email verification email (plain text variant)
pub fn render_email_verification_html(EmailVerificationContext) { "emails/verification.html" }
}
impl Templates {
@ -323,9 +329,12 @@ impl Templates {
check::render_index(self).await?;
check::render_account_index(self).await?;
check::render_account_password(self).await?;
check::render_account_emails::<()>(self).await?;
check::render_reauth(self).await?;
check::render_form_post::<EmptyContext>(self).await?;
check::render_error(self).await?;
check::render_email_verification_txt(self).await?;
check::render_email_verification_html(self).await?;
Ok(())
}
}

View File

@ -0,0 +1,23 @@
{#
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
#}
Hi <b>{{ user.username }}</b>,<br />
<br />
click this link to verify your account:<br />
<br />
<a href="{{ verification_link }}">{{ verification_link }}</a><br />
<br />
kthxbye

View File

@ -0,0 +1,23 @@
{#
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
#}
Hi {{ user.username }},
click this link to verify your account:
<{{ verification_link }}>
kthxbye