Merge branch 'release/v0.2.4'

This commit is contained in:
Henri Bourcereau 2026-05-11 21:25:07 +02:00
commit dac2645d01
5 changed files with 224 additions and 91 deletions

168
Cargo.lock generated
View file

@ -738,7 +738,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
@ -2038,9 +2038,12 @@ dependencies = [
"nom",
"percent-encoding",
"quoted_printable",
"rustls",
"socket2",
"tokio",
"tokio-rustls",
"url",
"webpki-roots",
]
[[package]]
@ -2214,7 +2217,7 @@ checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
dependencies = [
"libc",
"wasi 0.11.1+wasi-snapshot-preview1",
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
@ -2238,7 +2241,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
@ -2904,6 +2907,20 @@ dependencies = [
"tracing-subscriber",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.17",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rstml"
version = "0.12.1"
@ -2934,6 +2951,41 @@ dependencies = [
"semver",
]
[[package]]
name = "rustls"
version = "0.23.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
dependencies = [
"log",
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pki-types"
version = "1.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.22"
@ -3230,7 +3282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
@ -3500,7 +3552,7 @@ dependencies = [
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
@ -3540,6 +3592,16 @@ dependencies = [
"whoami",
]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "tokio-tungstenite"
version = "0.29.0"
@ -3984,6 +4046,12 @@ version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.8"
@ -4262,6 +4330,15 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webpki-roots"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "whoami"
version = "2.1.2"
@ -4281,7 +4358,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
@ -4290,6 +4367,15 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
@ -4299,6 +4385,70 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.7.15"
@ -4532,6 +4682,12 @@ dependencies = [
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]]
name = "zerotrie"
version = "0.1.3"

View file

@ -16,62 +16,6 @@
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1767039857,
"owner": "NixOS",
"repo": "flake-compat",
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "flake-compat",
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1776796298,
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "3cfd774b0a530725a077e17354fbdb87ea1c4aad",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1762808025,
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1776734388,
@ -105,12 +49,8 @@
"root": {
"inputs": {
"devenv": "devenv",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs",
"nixpkgs-cmake3": "nixpkgs-cmake3",
"pre-commit-hooks": [
"git-hooks"
]
"nixpkgs-cmake3": "nixpkgs-cmake3"
}
}
},

View file

@ -48,9 +48,14 @@ in
description = "SMTP server hostname.";
};
port = mkOption {
type = types.port;
default = 1025;
description = "SMTP server port.";
type = types.nullOr types.port;
default = null;
description = "SMTP server port. Defaults to 465 when tls = true, 1025 otherwise.";
};
tls = mkOption {
type = types.bool;
default = false;
description = "Use TLS (port 465). Required for Resend and other cloud SMTP providers.";
};
from = mkOption {
type = types.str;
@ -60,7 +65,7 @@ in
user = mkOption {
type = types.str;
default = "";
description = "SMTP username (leave empty to skip authentication).";
description = "SMTP username (leave empty to skip authentication). Use \"resend\" for Resend.";
};
passwordFile = mkOption {
type = types.nullOr types.path;
@ -181,8 +186,11 @@ in
DATABASE_URL = "postgresql://${cfg.user}@127.0.0.1/${cfg.user}";
APP_URL = "${cfg.protocol}://${cfg.hostname}";
SMTP_HOST = cfg.smtp.host;
SMTP_PORT = toString cfg.smtp.port;
SMTP_PORT = toString (if cfg.smtp.port != null then cfg.smtp.port
else if cfg.smtp.tls then 465 else 1025);
SMTP_FROM = cfg.smtp.from;
} // optionalAttrs cfg.smtp.tls {
SMTP_TLS = "true";
} // optionalAttrs (cfg.smtp.user != "") {
SMTP_USER = cfg.smtp.user;
};

View file

@ -25,4 +25,4 @@ axum-login = "0.18"
argon2 = "0.5"
time = "0.3"
thiserror = "1"
lettre = { version = "0.11", default-features = false, features = ["smtp-transport", "tokio1", "builder", "hostname"] }
lettre = { version = "0.11", default-features = false, features = ["smtp-transport", "tokio1", "builder", "hostname", "tokio1-rustls-tls"] }

View file

@ -1,15 +1,18 @@
//! SMTP mailer (plain SMTP, no TLS).
//! SMTP mailer.
//!
//! Configured via environment variables:
//! SMTP_HOST — default: 127.0.0.1 (mailpit in dev)
//! SMTP_PORT — default: 1025 (mailpit default)
//! SMTP_HOST — default: 127.0.0.1 (mailpit in dev)
//! SMTP_PORT — default: 1025 (mailpit) / 465 when SMTP_TLS=true
//! SMTP_TLS — set to "true" to use TLS (required for Resend and other cloud SMTP)
//! SMTP_FROM — default: noreply@trictrac.local
//! SMTP_USER — optional SMTP credentials
//! SMTP_PASSWORD — optional SMTP credentials
//! SMTP_USER — optional SMTP credentials (use "resend" for Resend)
//! SMTP_PASSWORD — optional SMTP credentials (use Resend API key)
//! APP_URL — default: http://localhost:9091 (frontend base URL for email links)
//!
//! For production with TLS, run a local relay (e.g. Postfix) or use an SMTP
//! service that accepts plain connections on a local port and handles TLS externally.
//! Production (Resend):
//! SMTP_HOST=smtp.resend.com SMTP_TLS=true
//! SMTP_USER=resend SMTP_PASSWORD=re_xxxx
//! SMTP_FROM=noreply@yourdomain.com
use lettre::{
AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor,
@ -26,21 +29,47 @@ pub struct Mailer {
impl Mailer {
pub fn from_env() -> Self {
let host = std::env::var("SMTP_HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
let port: u16 = std::env::var("SMTP_PORT")
.ok()
.and_then(|p| p.parse().ok())
.unwrap_or(1025);
let tls = std::env::var("SMTP_TLS").map(|v| v == "true").unwrap_or(false);
let from_str = std::env::var("SMTP_FROM")
.unwrap_or_else(|_| "noreply@trictrac.local".to_string());
let app_url = std::env::var("APP_URL")
.unwrap_or_else(|_| "http://localhost:9091".to_string());
let mut builder =
AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(&host).port(port);
if let (Ok(user), Ok(pass)) = (std::env::var("SMTP_USER"), std::env::var("SMTP_PASSWORD")) {
builder = builder.credentials(SmtpCredentials::new(user, pass));
}
let transport = builder.build();
let credentials = if let (Ok(user), Ok(pass)) =
(std::env::var("SMTP_USER"), std::env::var("SMTP_PASSWORD"))
{
Some(SmtpCredentials::new(user, pass))
} else {
None
};
let transport = if tls {
// TLS on port 465 (Resend, SendGrid, etc.)
let default_port = 465u16;
let port: u16 = std::env::var("SMTP_PORT")
.ok()
.and_then(|p| p.parse().ok())
.unwrap_or(default_port);
let mut builder = AsyncSmtpTransport::<Tokio1Executor>::relay(&host)
.expect("invalid SMTP_HOST for TLS relay")
.port(port);
if let Some(creds) = credentials {
builder = builder.credentials(creds);
}
builder.build()
} else {
// Plain SMTP (Mailpit dev, or local relay)
let port: u16 = std::env::var("SMTP_PORT")
.ok()
.and_then(|p| p.parse().ok())
.unwrap_or(1025);
let mut builder =
AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(&host).port(port);
if let Some(creds) = credentials {
builder = builder.credentials(creds);
}
builder.build()
};
let from = from_str
.parse()