Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 10 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/defguard_common/src/db/models/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -867,7 +867,7 @@ Sent by Defguard {{ defguard_version }}
Star us on GitHub! https://github.com/defguard/defguard\
";

pub static WELCOME_EMAIL_SUBJECT: &str = "[defguard] Welcome message after enrollment";
pub static WELCOME_EMAIL_SUBJECT: &str = "Defguard: Welcome message after enrollment";
}

#[cfg(test)]
Expand Down
7 changes: 5 additions & 2 deletions crates/defguard_common/src/db/models/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -585,14 +585,17 @@ impl User<Id> {

/// Select all users without sensitive data.
// FIXME: Remove it when Model macro will support SecretString
pub async fn all_without_sensitive_data(pool: &PgPool) -> sqlx::Result<Vec<UserDiagnostic>> {
pub async fn all_without_sensitive_data<'e, E>(executor: E) -> sqlx::Result<Vec<UserDiagnostic>>
where
E: PgExecutor<'e>,
{
let users = query!(
"SELECT id, mfa_enabled, totp_enabled, email_mfa_enabled, \
mfa_method \"mfa_method: MFAMethod\", password_hash, is_active, openid_sub, \
from_ldap, ldap_pass_randomized, ldap_rdn \
FROM \"user\""
)
.fetch_all(pool)
.fetch_all(executor)
.await?;
let res = users
.iter()
Expand Down
115 changes: 23 additions & 92 deletions crates/defguard_core/src/db/models/enrollment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@ use defguard_common::{
VERSION,
db::{
Id,
models::{Settings, settings::defaults::WELCOME_EMAIL_SUBJECT, user::User},
models::{Settings, user::User},
},
random::gen_alphanumeric,
types::UrlParseError,
};
use defguard_mail::{
Mail,
templates::{self, TemplateError, safe_tera},
};
use sqlx::{PgConnection, PgExecutor, PgPool, Transaction, query, query_as};
use defguard_mail::templates;
use sqlx::{PgConnection, PgExecutor, PgPool, query, query_as};
use tera::Context;
use thiserror::Error;
use tonic::{Code, Status};
Expand Down Expand Up @@ -49,7 +46,7 @@ pub enum TokenError {
#[error(transparent)]
TemplateErrorInternal(#[from] tera::Error),
#[error(transparent)]
TemplateError(#[from] TemplateError),
TemplateError(#[from] templates::TemplateError),
#[error(transparent)]
UrlParseError(#[from] UrlParseError),
}
Expand Down Expand Up @@ -84,7 +81,7 @@ impl From<TokenError> for Status {
pub struct Token {
pub id: String,
pub user_id: Id,
pub admin_id: Option<i64>,
pub admin_id: Option<Id>,
pub email: Option<String>,
pub created_at: NaiveDateTime,
pub expires_at: NaiveDateTime,
Expand Down Expand Up @@ -121,7 +118,8 @@ impl Token {
E: PgExecutor<'e>,
{
query!(
"INSERT INTO token (id, user_id, admin_id, email, created_at, expires_at, used_at, token_type, device_id) \
"INSERT INTO token (id, user_id, admin_id, email, created_at, expires_at, used_at, \
token_type, device_id) \
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
self.id,
self.user_id,
Expand Down Expand Up @@ -321,15 +319,15 @@ impl Token {
/// - admin_phone
pub(crate) async fn get_welcome_message_context(
&self,
transaction: &mut PgConnection,
conn: &mut PgConnection,
) -> Result<Context, TokenError> {
debug!(
"Preparing welcome message context for enrollment token {}",
self.id
);

let user = self.fetch_user(&mut *transaction).await?;
let admin = self.fetch_admin(&mut *transaction).await?;
let user = self.fetch_user(&mut *conn).await?;
let admin = self.fetch_admin(&mut *conn).await?;
let url = Settings::url()?;
let mut context = Context::new();
context.insert("first_name", &user.first_name);
Expand All @@ -352,107 +350,40 @@ impl Token {
// to be displayed on final enrollment page
pub async fn get_welcome_page_content(
&self,
transaction: &mut PgConnection,
conn: &mut PgConnection,
) -> Result<String, TokenError> {
let settings = Settings::get_current_settings();

// load configured content as template
let mut tera = safe_tera();
let mut tera = templates::safe_tera();
tera.add_raw_template("welcome_page", &enrollment_welcome_message(&settings)?)?;

let context = self.get_welcome_message_context(&mut *transaction).await?;
let context = self.get_welcome_message_context(&mut *conn).await?;

Ok(tera.render("welcome_page", &context)?)
}

// Render welcome email content
pub(crate) async fn get_welcome_email_content(
/// Send configured welcome email to a user after finishing enrollment.
pub async fn send_welcome_email(
&self,
transaction: &mut PgConnection,
conn: &mut PgConnection,
user: &User<Id>,
ip_address: &str,
device_info: Option<&str>,
) -> Result<String, TokenError> {
) -> Result<(), TokenError> {
debug!("Sending welcome mail to {}", user.username);
let settings = Settings::get_current_settings();

// load configured content as template
let mut tera = safe_tera();
let mut tera = templates::safe_tera();
tera.add_raw_template("welcome_email", &enrollment_welcome_email(&settings)?)?;

let context = self.get_welcome_message_context(&mut *transaction).await?;
let context = self.get_welcome_message_context(conn).await?;
let content = tera.render("welcome_email", &context)?;

Ok(templates::enrollment_welcome_mail(
&content,
Some(ip_address),
device_info,
)?)
}

// Send configured welcome email to user after finishing enrollment
pub async fn send_welcome_email(
&self,
transaction: &mut Transaction<'_, sqlx::Postgres>,
user: &User<Id>,
settings: &Settings,
ip_address: &str,
device_info: Option<&str>,
) -> Result<(), TokenError> {
debug!("Sending welcome mail to {}", user.username);
let mail = Mail::new(
&user.email,
settings
.enrollment_welcome_email_subject
.as_deref()
.unwrap_or(WELCOME_EMAIL_SUBJECT),
self.get_welcome_email_content(&mut *transaction, ip_address, device_info)
.await?,
);
match mail.send().await {
Ok(()) => {
info!("Sent enrollment welcome mail to {}", user.username);
Ok(())
}
Err(err) => {
error!("Error sending welcome mail: {err}");
Err(TokenError::NotificationError(err.to_string()))
}
}
}
templates::enrollment_welcome_mail(&user.email, &content, Some(ip_address), device_info)?;

// Notify admin that a user has completed enrollment
pub async fn send_admin_notification(
admin: &User<Id>,
user: &User<Id>,
ip_address: &str,
device_info: Option<&str>,
) -> Result<(), TokenError> {
debug!(
"Sending enrollment success notification for user {} to {}",
user.username, admin.username
);
let mail = Mail::new(
&admin.email,
"[defguard] User enrollment completed",
templates::enrollment_admin_notification(
&user.into(),
&admin.into(),
ip_address,
device_info,
)?,
);
match mail.send().await {
Ok(()) => {
info!(
"Sent enrollment success notification for user {} to {}",
user.username, admin.username
);
Ok(())
}
Err(err) => {
error!("Error sending welcome mail: {err}");
Err(TokenError::NotificationError(err.to_string()))
}
}
Ok(())
}
}

Expand Down
5 changes: 3 additions & 2 deletions crates/defguard_core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,11 @@ impl From<DeviceError> for WebError {
match error {
DeviceError::PubkeyConflict(..) => Self::PubkeyValidation(error.to_string()),
DeviceError::DatabaseError(_) => Self::DbError(error.to_string()),
DeviceError::NetworkIpAssignmentError(_) => Self::ModelError(error.to_string()),
DeviceError::Unexpected(_) => Self::Http(StatusCode::INTERNAL_SERVER_ERROR),
DeviceError::NetworkFull(_) => Self::NetworkFull(error.to_string()),
DeviceError::ModelError(_) => Self::ModelError(error.to_string()),
DeviceError::NetworkIpAssignmentError(_) | DeviceError::ModelError(_) => {
Self::ModelError(error.to_string())
}
}
}
}
Expand Down
Loading
Loading