Compare commits

...

3 Commits

Author SHA1 Message Date
ff29bd0b19 Merge pull request 'feat: Autoactivate user if SMTP is unconfigured' (#8) from autoactivate into main
Reviewed-on: #8
2025-09-06 22:39:46 +00:00
BenjaminBrienen
9b68fe514c warn 2025-09-07 00:36:04 +02:00
BenjaminBrienen
49bf11514b feat: Autoactivate user if SMTP is unconfigured 2025-09-07 00:32:42 +02:00
6 changed files with 70 additions and 21 deletions

View File

@@ -25,7 +25,7 @@ pub mod ssr {
.ok_or_else(|| ServerFnError::ServerError("Pool missing.".into()))
}
pub fn get_lettre() -> Result<DangerousLettre, ServerFnError> {
pub fn get_lettre() -> Result<Option<DangerousLettre>, ServerFnError> {
with_context::<AppState, _>(|state| state.lettre.clone())
.ok_or_else(|| ServerFnError::ServerError("Lettre missing".into()))
}

View File

@@ -348,13 +348,25 @@ pub async fn signup(
.await
.ok_or_else(|| ServerFnError::new("Signup failed: Unable to get user id from token."))?;
// Send email with token url
lettre.user_created(&user, &verify_token).await;
if let Some(lettre) = lettre {
// Send email with token url
lettre.user_created(&user, &verify_token).await;
// leptos_axum::redirect("/");
Ok(SignupResponse {
msg: "Je account is aangemaakt. Controleer je email om je account te activeren.".into(),
})
// leptos_axum::redirect("/");
Ok(SignupResponse {
msg: "Je account is aangemaakt. Controleer je email om je account te activeren.".into(),
})
} else {
// TODO: just do this when creating the user
let user = User::activate(&verify_token, &pool).await.unwrap();
let auth = auth().await?;
auth.login_user(user.id);
auth.remember_user(true);
leptos_axum::redirect("/");
Ok(SignupResponse {
msg: "Je account is aangemaakt.".into(),
})
}
}
use crate::auth::server_fn::codec::GetUrl;

View File

@@ -1,3 +1,4 @@
use anyhow::Context as _;
use lettre::message::{Mailbox, MultiPart, SinglePart, header};
use lettre::AsyncSmtpTransport;
@@ -16,24 +17,45 @@ pub struct DangerousLettre {
tera: Tera,
}
#[derive(Debug, thiserror::Error)]
pub enum NewDangerousLettreServiceError {
StartRelayError(#[from] lettre::transport::smtp::Error),
TeraError(#[from] tera::Error),
InvalidMailbox(String),
}
impl std::fmt::Display for NewDangerousLettreServiceError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::TeraError(inner) => {
f.write_fmt(format_args!("error constructing template engine: {inner}"))
}
Self::InvalidMailbox(from) => {
f.write_fmt(format_args!("'{from}' is not a valid mailbox"))
}
Self::StartRelayError(error) => {
f.write_fmt(format_args!("failed to start tls relay: {error}"))
}
}
}
}
impl DangerousLettre {
pub fn new(
host: &str,
username: &str,
password: &str,
from: &str,
) -> Result<Self, anyhow::Error> {
) -> Result<Self, NewDangerousLettreServiceError> {
let creds = Credentials::new(username.to_owned(), password.to_owned());
let mailer: AsyncSmtpTransport<Tokio1Executor> =
AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(host)?
.credentials(creds)
.build();
let tera = Tera::new("templates/**/*.email.*")?;
let from = from.parse::<Mailbox>()?;
let from = from
.parse::<Mailbox>()
.map_err(|_e| NewDangerousLettreServiceError::InvalidMailbox(from.to_owned()))?;
Ok(Self { mailer, from, tera })
}

View File

@@ -10,7 +10,7 @@ pub enum AppError {
}
impl AppError {
#[must_use]
#[must_use]
pub const fn status_code(&self) -> StatusCode {
match self {
Self::NotFound => StatusCode::NOT_FOUND,

View File

@@ -1,6 +1,11 @@
use std::{fmt::Display, net::IpAddr, str::FromStr, time::Duration};
use airsoftfotos::{app::*, auth::User, dangerous_lettre::DangerousLettre, state::AppState};
use airsoftfotos::{
app::*,
auth::User,
dangerous_lettre::{DangerousLettre, NewDangerousLettreServiceError},
state::AppState,
};
use anyhow::Context as _;
use axum::{Router, body::Body, extract::Request, middleware::Next, response::Response};
use axum_session::{SessionConfig, SessionLayer, SessionStore};
@@ -9,6 +14,7 @@ use axum_session_sqlx::SessionSqlitePool;
use http::{HeaderValue, Method, Uri};
use leptos::{config::get_configuration, logging::log};
use leptos_axum::{LeptosRoutes as _, generate_route_list};
use log::warn;
use sqlx::{SqlitePool, sqlite::SqlitePoolOptions};
use tower_http::{classify::ServerErrorsFailureClass, trace::TraceLayer};
use tracing::Span;
@@ -65,23 +71,32 @@ async fn uri_middleware(request: Request<Body>, next: Next) -> Response {
#[tokio::main]
async fn main() -> anyhow::Result<()> {
dotenvy::dotenv().unwrap();
dotenvy::dotenv().context("failed to read .env file")?;
simple_logger::init_with_level(log::Level::Info).expect("couldn't initialize logging");
simple_logger::init_with_level(log::Level::Info).context("failed to initialize logging")?;
let db_url = load_env("DATABASE_URL")?;
let pool = SqlitePoolOptions::new()
.connect(&db_url)
.await
.expect("Could not make pool.");
.context("failed to make pool")?;
let dangerous_lettre = DangerousLettre::new(
let dangerous_lettre = match DangerousLettre::new(
&load_env("SMTP_HOST")?,
&load_env("SMTP_USERNAME")?,
&load_env("SMTP_PASSWORD")?,
&load_env("SMTP_FROM")?,
)?;
) {
Err(NewDangerousLettreServiceError::InvalidMailbox(_)) => {
warn!(
"SMTP is disabled - new accounts will be automatically activated without a verification step"
);
None
}
Err(e) => return Err(e).context("failed to create DangerousLettre service"),
Ok(service) => Some(service),
};
// Auth section
let session_config = SessionConfig::default().with_table_name("axum_sessions");

View File

@@ -15,6 +15,6 @@ pub struct AppState {
pub leptos_options: LeptosOptions,
pub pool: SqlitePool,
pub routes: Vec<AxumRouteListing>,
pub lettre: DangerousLettre,
pub lettre: Option<DangerousLettre>,
pub aws_shared_config: aws_config::SdkConfig,
}