You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-31 09:24:31 +03:00
Remove the config dependency from the email, templates & handlers crates
This commit is contained in:
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -2420,6 +2420,7 @@ dependencies = [
|
||||
"figment",
|
||||
"indoc",
|
||||
"lettre",
|
||||
"mas-email",
|
||||
"mas-iana",
|
||||
"mas-jose",
|
||||
"mas-keystore",
|
||||
@ -2461,7 +2462,6 @@ dependencies = [
|
||||
"aws-sdk-sesv2",
|
||||
"aws-types",
|
||||
"lettre",
|
||||
"mas-config",
|
||||
"mas-templates",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@ -2483,7 +2483,6 @@ dependencies = [
|
||||
"indoc",
|
||||
"lettre",
|
||||
"mas-axum-utils",
|
||||
"mas-config",
|
||||
"mas-data-model",
|
||||
"mas-email",
|
||||
"mas-http",
|
||||
@ -2707,7 +2706,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"mas-config",
|
||||
"mas-data-model",
|
||||
"mas-router",
|
||||
"oauth2-types",
|
||||
|
@ -23,7 +23,8 @@ use clap::Parser;
|
||||
use futures::stream::{StreamExt, TryStreamExt};
|
||||
use hyper::Server;
|
||||
use mas_config::RootConfig;
|
||||
use mas_email::{MailTransport, Mailer};
|
||||
use mas_email::Mailer;
|
||||
use mas_handlers::MatrixHomeserver;
|
||||
use mas_http::ServerLayer;
|
||||
use mas_policy::PolicyFactory;
|
||||
use mas_router::UrlBuilder;
|
||||
@ -148,7 +149,7 @@ impl Options {
|
||||
let listener = TcpListener::bind(addr).context("could not bind address")?;
|
||||
|
||||
// Connect to the mail server
|
||||
let mail_transport = MailTransport::from_config(&config.email.transport).await?;
|
||||
let mail_transport = config.email.transport.to_transport().await?;
|
||||
mail_transport.test_connection().await?;
|
||||
|
||||
// Connect to the database
|
||||
@ -203,7 +204,7 @@ impl Options {
|
||||
let policy_factory = Arc::new(policy_factory);
|
||||
|
||||
// Load and compile the templates
|
||||
let templates = Templates::load_from_config(&config.templates)
|
||||
let templates = Templates::load(config.templates.path.clone(), config.templates.builtin)
|
||||
.await
|
||||
.context("could not load templates")?;
|
||||
|
||||
@ -218,7 +219,7 @@ impl Options {
|
||||
|
||||
let static_files = mas_static_files::service(&config.http.web_root);
|
||||
|
||||
let matrix_config = config.matrix.clone();
|
||||
let homeserver = MatrixHomeserver::new(config.matrix.homeserver.clone());
|
||||
|
||||
// Explicitely the config to properly zeroize secret keys
|
||||
drop(config);
|
||||
@ -242,7 +243,7 @@ impl Options {
|
||||
&encrypter,
|
||||
&mailer,
|
||||
&url_builder,
|
||||
&matrix_config,
|
||||
&homeserver,
|
||||
&policy_factory,
|
||||
)
|
||||
.fallback(static_files)
|
||||
|
@ -62,7 +62,7 @@ impl Options {
|
||||
path: Some(path.to_string()),
|
||||
builtin: !skip_builtin,
|
||||
};
|
||||
let templates = Templates::load_from_config(&config).await?;
|
||||
let templates = Templates::load(config.path.clone(), config.builtin).await?;
|
||||
templates.check_render().await?;
|
||||
|
||||
Ok(())
|
||||
|
@ -32,3 +32,4 @@ indoc = "1.0.7"
|
||||
mas-jose = { path = "../jose" }
|
||||
mas-keystore = { path = "../keystore" }
|
||||
mas-iana = { path = "../iana" }
|
||||
mas-email = { path = "../email" }
|
||||
|
@ -14,8 +14,10 @@
|
||||
|
||||
use std::num::NonZeroU16;
|
||||
|
||||
use anyhow::Context;
|
||||
use async_trait::async_trait;
|
||||
use lettre::{message::Mailbox, Address};
|
||||
use mas_email::MailTransport;
|
||||
use schemars::{
|
||||
gen::SchemaGenerator,
|
||||
schema::{InstanceType, Schema, SchemaObject},
|
||||
@ -51,7 +53,7 @@ pub struct Credentials {
|
||||
}
|
||||
|
||||
/// Encryption mode to use
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum EmailSmtpMode {
|
||||
/// Plain text
|
||||
@ -62,6 +64,16 @@ pub enum EmailSmtpMode {
|
||||
Tls,
|
||||
}
|
||||
|
||||
impl From<&EmailSmtpMode> for mas_email::SmtpMode {
|
||||
fn from(value: &EmailSmtpMode) -> Self {
|
||||
match value {
|
||||
EmailSmtpMode::Plain => Self::Plain,
|
||||
EmailSmtpMode::StartTls => Self::StartTls,
|
||||
EmailSmtpMode::Tls => Self::Tls,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// What backend should be used when sending emails
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(tag = "transport", rename_all = "snake_case")]
|
||||
@ -156,3 +168,30 @@ impl ConfigurationSection<'_> for EmailConfig {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl EmailTransportConfig {
|
||||
/// Create a [`lettre::Transport`] out of this config
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the transport could not be created
|
||||
pub async fn to_transport(&self) -> Result<MailTransport, anyhow::Error> {
|
||||
match self {
|
||||
Self::Blackhole => Ok(MailTransport::blackhole()),
|
||||
Self::Smtp {
|
||||
mode,
|
||||
hostname,
|
||||
credentials,
|
||||
port,
|
||||
} => {
|
||||
let credentials = credentials
|
||||
.clone()
|
||||
.map(|c| mas_email::SmtpCredentials::new(c.username, c.password));
|
||||
MailTransport::smtp(mode.into(), hostname, port.as_ref().copied(), credentials)
|
||||
.context("failed to build SMTP transport")
|
||||
}
|
||||
EmailTransportConfig::Sendmail { command } => Ok(MailTransport::sendmail(command)),
|
||||
EmailTransportConfig::AwsSes => Ok(MailTransport::aws_ses().await),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ aws-config = "0.47.0"
|
||||
aws-types = "0.47.0"
|
||||
|
||||
mas-templates = { path = "../templates" }
|
||||
mas-config = { path = "../config" }
|
||||
|
||||
[dependencies.lettre]
|
||||
version = "0.10.1"
|
||||
|
@ -26,7 +26,9 @@
|
||||
mod mailer;
|
||||
mod transport;
|
||||
|
||||
pub use lettre::transport::smtp::authentication::Credentials as SmtpCredentials;
|
||||
|
||||
pub use self::{
|
||||
mailer::Mailer,
|
||||
transport::{aws_ses::Transport as AwsSesTransport, Transport as MailTransport},
|
||||
transport::{aws_ses::Transport as AwsSesTransport, SmtpMode, Transport as MailTransport},
|
||||
};
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
//! Email transport backends
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::{ffi::OsString, num::NonZeroU16, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use lettre::{
|
||||
@ -25,10 +25,20 @@ use lettre::{
|
||||
},
|
||||
AsyncTransport, Tokio1Executor,
|
||||
};
|
||||
use mas_config::{EmailSmtpMode, EmailTransportConfig};
|
||||
|
||||
pub mod aws_ses;
|
||||
|
||||
/// Encryption mode to use
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum SmtpMode {
|
||||
/// Plain text
|
||||
Plain,
|
||||
/// StartTLS (starts as plain text then upgrade to TLS)
|
||||
StartTls,
|
||||
/// TLS
|
||||
Tls,
|
||||
}
|
||||
|
||||
/// A wrapper around many [`AsyncTransport`]s
|
||||
#[derive(Default, Clone)]
|
||||
pub struct Transport {
|
||||
@ -43,52 +53,56 @@ enum TransportInner {
|
||||
}
|
||||
|
||||
impl Transport {
|
||||
/// Construct a transport from a user configration
|
||||
fn new(inner: TransportInner) -> Self {
|
||||
let inner = Arc::new(inner);
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
/// Construct a blackhole transport
|
||||
#[must_use]
|
||||
pub fn blackhole() -> Self {
|
||||
Self::new(TransportInner::Blackhole)
|
||||
}
|
||||
|
||||
/// Construct a SMTP transport
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Will return `Err` on invalid confiuration
|
||||
pub async fn from_config(config: &EmailTransportConfig) -> Result<Self, anyhow::Error> {
|
||||
let inner = match config {
|
||||
EmailTransportConfig::Blackhole => TransportInner::Blackhole,
|
||||
EmailTransportConfig::Smtp {
|
||||
mode,
|
||||
hostname,
|
||||
credentials,
|
||||
port,
|
||||
} => {
|
||||
let mut t = match mode {
|
||||
EmailSmtpMode::Plain => {
|
||||
AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(hostname)
|
||||
}
|
||||
EmailSmtpMode::StartTls => {
|
||||
AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(hostname)?
|
||||
}
|
||||
EmailSmtpMode::Tls => AsyncSmtpTransport::<Tokio1Executor>::relay(hostname)?,
|
||||
};
|
||||
|
||||
if let Some(credentials) = credentials {
|
||||
t = t.credentials(Credentials::new(
|
||||
credentials.username.clone(),
|
||||
credentials.password.clone(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(port) = port {
|
||||
t = t.port((*port).into());
|
||||
}
|
||||
|
||||
TransportInner::Smtp(t.build())
|
||||
}
|
||||
EmailTransportConfig::Sendmail { command } => {
|
||||
TransportInner::Sendmail(AsyncSendmailTransport::new_with_command(command))
|
||||
}
|
||||
EmailTransportConfig::AwsSes => {
|
||||
TransportInner::AwsSes(aws_ses::Transport::from_env().await)
|
||||
}
|
||||
/// Returns an error if the underlying SMTP transport could not be built
|
||||
pub fn smtp(
|
||||
mode: SmtpMode,
|
||||
hostname: &str,
|
||||
port: Option<NonZeroU16>,
|
||||
credentials: Option<Credentials>,
|
||||
) -> Result<Self, lettre::transport::smtp::Error> {
|
||||
let mut t = match mode {
|
||||
SmtpMode::Plain => AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(hostname),
|
||||
SmtpMode::StartTls => AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(hostname)?,
|
||||
SmtpMode::Tls => AsyncSmtpTransport::<Tokio1Executor>::relay(hostname)?,
|
||||
};
|
||||
let inner = Arc::new(inner);
|
||||
Ok(Self { inner })
|
||||
|
||||
if let Some(credentials) = credentials {
|
||||
t = t.credentials(credentials);
|
||||
}
|
||||
|
||||
if let Some(port) = port {
|
||||
t = t.port(port.into());
|
||||
}
|
||||
|
||||
Ok(Self::new(TransportInner::Smtp(t.build())))
|
||||
}
|
||||
|
||||
/// Construct a Sendmail transport
|
||||
#[must_use]
|
||||
pub fn sendmail(command: impl Into<OsString>) -> Self {
|
||||
Self::new(TransportInner::Sendmail(
|
||||
AsyncSendmailTransport::new_with_command(command),
|
||||
))
|
||||
}
|
||||
|
||||
/// Construct a AWS SES transport
|
||||
pub async fn aws_ses() -> Self {
|
||||
Self::new(TransportInner::AwsSes(aws_ses::Transport::from_env().await))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,6 @@ headers = "0.3.7"
|
||||
|
||||
oauth2-types = { path = "../oauth2-types" }
|
||||
mas-axum-utils = { path = "../axum-utils" }
|
||||
mas-config = { path = "../config" }
|
||||
mas-data-model = { path = "../data-model" }
|
||||
mas-email = { path = "../email" }
|
||||
mas-http = { path = "../http" }
|
||||
|
@ -15,7 +15,6 @@
|
||||
use axum::{response::IntoResponse, Extension, Json};
|
||||
use chrono::{Duration, Utc};
|
||||
use hyper::StatusCode;
|
||||
use mas_config::MatrixConfig;
|
||||
use mas_data_model::{CompatSession, CompatSsoLoginState, Device, TokenType};
|
||||
use mas_storage::{
|
||||
compat::{
|
||||
@ -31,7 +30,7 @@ use serde_with::{serde_as, skip_serializing_none, DurationMilliSeconds};
|
||||
use sqlx::{PgPool, Postgres, Transaction};
|
||||
use thiserror::Error;
|
||||
|
||||
use super::MatrixError;
|
||||
use super::{MatrixError, MatrixHomeserver};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
@ -199,7 +198,7 @@ impl IntoResponse for RouteError {
|
||||
#[tracing::instrument(skip_all, err)]
|
||||
pub(crate) async fn post(
|
||||
Extension(pool): Extension<PgPool>,
|
||||
Extension(config): Extension<MatrixConfig>,
|
||||
Extension(homeserver): Extension<MatrixHomeserver>,
|
||||
Json(input): Json<RequestBody>,
|
||||
) -> Result<impl IntoResponse, RouteError> {
|
||||
let mut txn = pool.begin().await?;
|
||||
@ -216,7 +215,7 @@ pub(crate) async fn post(
|
||||
}
|
||||
};
|
||||
|
||||
let user_id = format!("@{}:{}", session.user.username, config.homeserver);
|
||||
let user_id = format!("@{}:{}", session.user.username, homeserver);
|
||||
|
||||
// If the client asked for a refreshable token, make it expire
|
||||
let expires_in = if input.refresh_token {
|
||||
|
@ -22,6 +22,22 @@ pub(crate) mod login_sso_redirect;
|
||||
pub(crate) mod logout;
|
||||
pub(crate) mod refresh;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MatrixHomeserver(String);
|
||||
|
||||
impl MatrixHomeserver {
|
||||
#[must_use]
|
||||
pub const fn new(hs: String) -> Self {
|
||||
Self(hs)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for MatrixHomeserver {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct MatrixError {
|
||||
errcode: &'static str,
|
||||
|
@ -30,7 +30,6 @@ use axum::{
|
||||
};
|
||||
use headers::HeaderName;
|
||||
use hyper::header::{ACCEPT, ACCEPT_LANGUAGE, AUTHORIZATION, CONTENT_LANGUAGE, CONTENT_TYPE};
|
||||
use mas_config::MatrixConfig;
|
||||
use mas_email::Mailer;
|
||||
use mas_http::CorsLayerExt;
|
||||
use mas_keystore::{Encrypter, Keystore};
|
||||
@ -46,6 +45,8 @@ mod health;
|
||||
mod oauth2;
|
||||
mod views;
|
||||
|
||||
pub use compat::MatrixHomeserver;
|
||||
|
||||
#[must_use]
|
||||
#[allow(
|
||||
clippy::too_many_lines,
|
||||
@ -60,7 +61,7 @@ pub fn router<B>(
|
||||
encrypter: &Encrypter,
|
||||
mailer: &Mailer,
|
||||
url_builder: &UrlBuilder,
|
||||
matrix_config: &MatrixConfig,
|
||||
homeserver: &MatrixHomeserver,
|
||||
policy_factory: &Arc<PolicyFactory>,
|
||||
) -> Router<B>
|
||||
where
|
||||
@ -239,33 +240,28 @@ where
|
||||
.layer(Extension(encrypter.clone()))
|
||||
.layer(Extension(url_builder.clone()))
|
||||
.layer(Extension(mailer.clone()))
|
||||
.layer(Extension(matrix_config.clone()))
|
||||
.layer(Extension(homeserver.clone()))
|
||||
.layer(Extension(policy_factory.clone()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
async fn test_router(pool: &PgPool) -> Result<Router, anyhow::Error> {
|
||||
use mas_config::TemplatesConfig;
|
||||
use mas_email::MailTransport;
|
||||
|
||||
let templates_config = TemplatesConfig::default();
|
||||
let templates = Templates::load_from_config(&templates_config).await?;
|
||||
let templates = Templates::load(None, true).await?;
|
||||
|
||||
// TODO: add test keys to the store
|
||||
let key_store = Keystore::default();
|
||||
|
||||
let encrypter = Encrypter::new(&[0x42; 32]);
|
||||
|
||||
let transport = MailTransport::default();
|
||||
let transport = MailTransport::blackhole();
|
||||
let mailbox = "server@example.com".parse()?;
|
||||
let mailer = Mailer::new(&templates, &transport, &mailbox, &mailbox);
|
||||
|
||||
let url_builder = UrlBuilder::new("https://example.com/".parse()?);
|
||||
|
||||
let matrix_config = MatrixConfig {
|
||||
homeserver: "example.com".to_owned(),
|
||||
};
|
||||
|
||||
let homeserver = MatrixHomeserver::new("example.com".to_owned());
|
||||
let policy_factory = PolicyFactory::load_default(serde_json::json!({})).await?;
|
||||
let policy_factory = Arc::new(policy_factory);
|
||||
|
||||
@ -276,7 +272,7 @@ async fn test_router(pool: &PgPool) -> Result<Router, anyhow::Error> {
|
||||
&encrypter,
|
||||
&mailer,
|
||||
&url_builder,
|
||||
&matrix_config,
|
||||
&homeserver,
|
||||
&policy_factory,
|
||||
))
|
||||
}
|
||||
|
@ -25,5 +25,4 @@ url = "2.2.2"
|
||||
|
||||
oauth2-types = { path = "../oauth2-types" }
|
||||
mas-data-model = { path = "../data-model" }
|
||||
mas-config = { path = "../config" }
|
||||
mas-router = { path = "../router" }
|
||||
|
@ -33,7 +33,6 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::{bail, Context as _};
|
||||
use mas_config::TemplatesConfig;
|
||||
use mas_data_model::StorageBackend;
|
||||
use serde::Serialize;
|
||||
use tera::{Context, Error as TeraError, Tera};
|
||||
@ -63,7 +62,8 @@ pub use self::{
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Templates {
|
||||
tera: Arc<RwLock<Tera>>,
|
||||
config: TemplatesConfig,
|
||||
path: Option<String>,
|
||||
builtin: bool,
|
||||
}
|
||||
|
||||
/// There was an issue while loading the templates
|
||||
@ -90,7 +90,7 @@ pub enum TemplateLoadingError {
|
||||
impl Templates {
|
||||
/// List directories to watch
|
||||
pub async fn watch_roots(&self) -> Vec<PathBuf> {
|
||||
Self::roots(self.config.path.as_deref(), self.config.builtin)
|
||||
Self::roots(self.path.as_deref(), self.builtin)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(Result::ok)
|
||||
@ -133,17 +133,17 @@ impl Templates {
|
||||
Ok(tera)
|
||||
}
|
||||
|
||||
/// Load the templates from [the config][`TemplatesConfig`]
|
||||
pub async fn load_from_config(config: &TemplatesConfig) -> Result<Self, TemplateLoadingError> {
|
||||
let tera = Self::load(config.path.as_deref(), config.builtin).await?;
|
||||
|
||||
/// Load the templates from the given config
|
||||
pub async fn load(path: Option<String>, builtin: bool) -> Result<Self, TemplateLoadingError> {
|
||||
let tera = Self::load_(path.as_deref(), builtin).await?;
|
||||
Ok(Self {
|
||||
tera: Arc::new(RwLock::new(tera)),
|
||||
config: config.clone(),
|
||||
path,
|
||||
builtin,
|
||||
})
|
||||
}
|
||||
|
||||
async fn load(path: Option<&str>, builtin: bool) -> Result<Tera, TemplateLoadingError> {
|
||||
async fn load_(path: Option<&str>, builtin: bool) -> Result<Tera, TemplateLoadingError> {
|
||||
let mut teras = Vec::new();
|
||||
|
||||
let roots = Self::roots(path, builtin).await;
|
||||
@ -202,7 +202,7 @@ impl Templates {
|
||||
/// Reload the templates on disk
|
||||
pub async fn reload(&self) -> anyhow::Result<()> {
|
||||
// Prepare the new Tera instance
|
||||
let new_tera = Self::load(self.config.path.as_deref(), self.config.builtin).await?;
|
||||
let new_tera = Self::load_(self.path.as_deref(), self.builtin).await?;
|
||||
|
||||
// Swap it
|
||||
*self.tera.write().await = new_tera;
|
||||
@ -378,12 +378,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn check_builtin_templates() {
|
||||
let config = TemplatesConfig {
|
||||
path: None,
|
||||
builtin: true,
|
||||
};
|
||||
|
||||
let templates = Templates::load_from_config(&config).await.unwrap();
|
||||
let templates = Templates::load(None, true).await.unwrap();
|
||||
templates.check_render().await.unwrap();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user