mirror of
https://github.com/element-hq/matrix-authentication-service.git
synced 2026-08-23 20:29:58 +00:00
Add password_file config variant for email and database
This commit is contained in:
@@ -177,7 +177,7 @@ impl Options {
|
||||
homeserver_connection_from_config(&config.matrix, http_client.clone()).await?;
|
||||
|
||||
if !self.no_worker {
|
||||
let mailer = mailer_from_config(&config.email, &templates)?;
|
||||
let mailer = mailer_from_config(&config.email, &templates).await?;
|
||||
test_mailer_in_background(&mailer, Duration::from_secs(30));
|
||||
|
||||
info!("Starting task worker");
|
||||
|
||||
@@ -64,7 +64,7 @@ impl Options {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mailer = mailer_from_config(&config.email, &templates)?;
|
||||
let mailer = mailer_from_config(&config.email, &templates).await?;
|
||||
test_mailer_in_background(&mailer, Duration::from_secs(30));
|
||||
|
||||
let http_client = mas_http::reqwest_client();
|
||||
|
||||
+24
-7
@@ -59,7 +59,7 @@ pub async fn password_manager_from_config(
|
||||
PasswordManager::new(config.minimum_complexity(), schemes)
|
||||
}
|
||||
|
||||
pub fn mailer_from_config(
|
||||
pub async fn mailer_from_config(
|
||||
config: &EmailConfig,
|
||||
templates: &Templates,
|
||||
) -> Result<Mailer, anyhow::Error> {
|
||||
@@ -83,10 +83,15 @@ pub fn mailer_from_config(
|
||||
.mode()
|
||||
.context("invalid email configuration: missing mode")?;
|
||||
|
||||
let credentials = match (config.username(), config.password()) {
|
||||
let password = config
|
||||
.password()
|
||||
.await
|
||||
.context("invalid email configuration: unable to read password file")?;
|
||||
|
||||
let credentials = match (config.username(), password) {
|
||||
(Some(username), Some(password)) => Some(mas_email::SmtpCredentials::new(
|
||||
username.to_owned(),
|
||||
password.to_owned(),
|
||||
password.clone(),
|
||||
)),
|
||||
(None, None) => None,
|
||||
_ => {
|
||||
@@ -287,7 +292,7 @@ pub async fn templates_from_config(
|
||||
.with_context(|| format!("Failed to load the templates at {}", config.path))
|
||||
}
|
||||
|
||||
fn database_connect_options_from_config(
|
||||
async fn database_connect_options_from_config(
|
||||
config: &DatabaseConfig,
|
||||
opts: &DatabaseConnectOptions,
|
||||
) -> Result<PgConnectOptions, anyhow::Error> {
|
||||
@@ -317,6 +322,15 @@ fn database_connect_options_from_config(
|
||||
opts = opts.password(password);
|
||||
}
|
||||
|
||||
if let Some(password_file) = config.password_file.as_deref() {
|
||||
opts = opts.password(
|
||||
tokio::fs::read_to_string(password_file)
|
||||
.await
|
||||
.context("could not read database password file")?
|
||||
.trim(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(database) = config.database.as_deref() {
|
||||
opts = opts.database(database);
|
||||
}
|
||||
@@ -386,7 +400,8 @@ fn database_connect_options_from_config(
|
||||
/// Create a database connection pool from the configuration
|
||||
#[tracing::instrument(name = "db.connect", skip_all)]
|
||||
pub async fn database_pool_from_config(config: &DatabaseConfig) -> Result<PgPool, anyhow::Error> {
|
||||
let options = database_connect_options_from_config(config, &DatabaseConnectOptions::default())?;
|
||||
let options =
|
||||
database_connect_options_from_config(config, &DatabaseConnectOptions::default()).await?;
|
||||
PgPoolOptions::new()
|
||||
.max_connections(config.max_connections.into())
|
||||
.min_connections(config.min_connections)
|
||||
@@ -424,7 +439,8 @@ impl Default for DatabaseConnectOptions {
|
||||
pub async fn database_connection_from_config(
|
||||
config: &DatabaseConfig,
|
||||
) -> Result<PgConnection, anyhow::Error> {
|
||||
database_connect_options_from_config(config, &DatabaseConnectOptions::default())?
|
||||
database_connect_options_from_config(config, &DatabaseConnectOptions::default())
|
||||
.await?
|
||||
.connect()
|
||||
.await
|
||||
.context("could not connect to the database")
|
||||
@@ -437,7 +453,8 @@ pub async fn database_connection_from_config_with_options(
|
||||
config: &DatabaseConfig,
|
||||
options: &DatabaseConnectOptions,
|
||||
) -> Result<PgConnection, anyhow::Error> {
|
||||
database_connect_options_from_config(config, options)?
|
||||
database_connect_options_from_config(config, options)
|
||||
.await?
|
||||
.connect()
|
||||
.await
|
||||
.context("could not connect to the database")
|
||||
|
||||
@@ -47,6 +47,7 @@ impl Default for DatabaseConfig {
|
||||
socket: None,
|
||||
username: None,
|
||||
password: None,
|
||||
password_file: None,
|
||||
database: None,
|
||||
ssl_mode: None,
|
||||
ssl_ca: None,
|
||||
@@ -137,6 +138,15 @@ pub struct DatabaseConfig {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub password: Option<String>,
|
||||
|
||||
/// Path to the password to be used if the server demands password
|
||||
/// authentication
|
||||
///
|
||||
/// This must not be specified if the `password` or `uri` option is
|
||||
/// specified.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[schemars(with = "Option<String>")]
|
||||
pub password_file: Option<Utf8PathBuf>,
|
||||
|
||||
/// The database name
|
||||
///
|
||||
/// This must not be specified if `uri` is specified.
|
||||
@@ -242,14 +252,22 @@ impl ConfigurationSection for DatabaseConfig {
|
||||
|| self.socket.is_some()
|
||||
|| self.username.is_some()
|
||||
|| self.password.is_some()
|
||||
|| self.password_file.is_some()
|
||||
|| self.database.is_some();
|
||||
|
||||
if self.uri.is_some() && has_split_options {
|
||||
return Err(annotate(figment::error::Error::from(
|
||||
"uri must not be specified if host, port, socket, username, password, or database are specified".to_owned(),
|
||||
"uri must not be specified if host, port, socket, username, password, password_file, or database are specified".to_owned(),
|
||||
)).into());
|
||||
}
|
||||
|
||||
if self.password.is_some() && self.password_file.is_some() {
|
||||
return Err(annotate(figment::error::Error::from(
|
||||
"password must not be specified if password_file is specified".to_owned(),
|
||||
))
|
||||
.into());
|
||||
}
|
||||
|
||||
if self.ssl_ca.is_some() && self.ssl_ca_file.is_some() {
|
||||
return Err(annotate(figment::error::Error::from(
|
||||
"ssl_ca must not be specified if ssl_ca_file is specified".to_owned(),
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
use std::{num::NonZeroU16, str::FromStr};
|
||||
|
||||
use camino::Utf8PathBuf;
|
||||
use lettre::message::Mailbox;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize, de::Error};
|
||||
@@ -86,17 +87,25 @@ pub struct EmailConfig {
|
||||
/// SMTP transport: Username for use to authenticate when connecting to the
|
||||
/// SMTP server
|
||||
///
|
||||
/// Must be set if the `password` field is set
|
||||
/// Must be set if the `password` or `password_file` field is set
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
username: Option<String>,
|
||||
|
||||
/// SMTP transport: Password for use to authenticate when connecting to the
|
||||
/// SMTP server
|
||||
///
|
||||
/// Must be set if the `username` field is set
|
||||
/// Must be set if the `username` but not `password_file` field is set
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
password: Option<String>,
|
||||
|
||||
/// SMTP transport: Path to the password for use to authenticate when
|
||||
/// connecting to the SMTP server
|
||||
///
|
||||
/// Must be set if the `username` but not `password` field is set
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[schemars(with = "Option<String>")]
|
||||
password_file: Option<Utf8PathBuf>,
|
||||
|
||||
/// Sendmail transport: Command to use to send emails
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[schemars(default = "default_sendmail_command")]
|
||||
@@ -135,9 +144,21 @@ impl EmailConfig {
|
||||
}
|
||||
|
||||
/// Password for use to authenticate when connecting to the SMTP server
|
||||
#[must_use]
|
||||
pub fn password(&self) -> Option<&str> {
|
||||
self.password.as_deref()
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the password is a file and it could not be read.
|
||||
pub async fn password(&self) -> Result<Option<String>, anyhow::Error> {
|
||||
if let Some(password_file) = &self.password_file {
|
||||
return Ok(Some(
|
||||
tokio::fs::read_to_string(password_file)
|
||||
.await?
|
||||
.trim()
|
||||
.to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(self.password.clone())
|
||||
}
|
||||
|
||||
/// Command to use to send emails
|
||||
@@ -158,6 +179,7 @@ impl Default for EmailConfig {
|
||||
port: None,
|
||||
username: None,
|
||||
password: None,
|
||||
password_file: None,
|
||||
command: None,
|
||||
}
|
||||
}
|
||||
@@ -183,6 +205,10 @@ impl ConfigurationSection for EmailConfig {
|
||||
error_on_field(figment::error::Error::missing_field(field), field)
|
||||
};
|
||||
|
||||
let duplicate_field = |field: &'static str, duplicate: &'static str| {
|
||||
error_on_field(figment::error::Error::duplicate_field(duplicate), field)
|
||||
};
|
||||
|
||||
let unexpected_field = |field: &'static str, expected_fields: &'static [&'static str]| {
|
||||
error_on_field(
|
||||
figment::error::Error::unknown_field(field, expected_fields),
|
||||
@@ -202,7 +228,10 @@ impl ConfigurationSection for EmailConfig {
|
||||
return Err(error_on_field(figment::error::Error::custom(e), "reply_to").into());
|
||||
}
|
||||
|
||||
match (self.username.is_some(), self.password.is_some()) {
|
||||
match (
|
||||
self.username.is_some(),
|
||||
self.password.is_some() || self.password_file.is_some(),
|
||||
) {
|
||||
(true, true) | (false, false) => {}
|
||||
(true, false) => {
|
||||
return Err(missing_field("password").into());
|
||||
@@ -212,6 +241,10 @@ impl ConfigurationSection for EmailConfig {
|
||||
}
|
||||
}
|
||||
|
||||
if self.password.is_some() && self.password_file.is_some() {
|
||||
return Err(duplicate_field("password", "password_file").into());
|
||||
}
|
||||
|
||||
if self.mode.is_none() {
|
||||
return Err(missing_field("mode").into());
|
||||
}
|
||||
|
||||
+16
-2
@@ -1135,6 +1135,13 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"password_file": {
|
||||
"description": "Path to the password to be used if the server demands password\nauthentication\n\nThis must not be specified if the `password` or `uri` option is\nspecified.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"database": {
|
||||
"description": "The database name\n\nThis must not be specified if `uri` is specified.",
|
||||
"type": [
|
||||
@@ -1575,14 +1582,21 @@
|
||||
"maximum": 65535
|
||||
},
|
||||
"username": {
|
||||
"description": "SMTP transport: Username for use to authenticate when connecting to the\nSMTP server\n\nMust be set if the `password` field is set",
|
||||
"description": "SMTP transport: Username for use to authenticate when connecting to the\nSMTP server\n\nMust be set if the `password` or `password_file` field is set",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"password": {
|
||||
"description": "SMTP transport: Password for use to authenticate when connecting to the\nSMTP server\n\nMust be set if the `username` field is set",
|
||||
"description": "SMTP transport: Password for use to authenticate when connecting to the\nSMTP server\n\nMust be set if the `username` but not `password_file` field is set",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"password_file": {
|
||||
"description": "SMTP transport: Path to the password for use to authenticate when\nconnecting to the SMTP server\n\nMust be set if the `username` but not `password` field is set",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
|
||||
Reference in New Issue
Block a user