use secrecy::SecretString;
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::prelude::*;
pub type DecryptedEmailSettings = EmailSettings<SecretString>;
pub type EncryptedEmailSettings = EmailSettings<EncryptedAppPassword>;
#[derive(
derive_more::Debug,
Clone,
PartialEq,
Eq,
Builder,
Getters,
WithSetters,
Serialize,
Deserialize,
Zeroize,
ZeroizeOnDrop,
)]
pub struct EmailSettings<AppPassword: Zeroize> {
#[getset(get = "pub")]
#[debug("omitted")]
smtp_app_password: AppPassword,
#[getset(get = "pub")]
#[debug("omitted")]
salt: Salt,
#[getset(get = "pub")]
#[zeroize(skip)]
template: Template,
#[getset(get = "pub")]
#[zeroize(skip)]
reply_to: Option<EmailAccount>,
#[getset(get = "pub")]
#[zeroize(skip)]
smtp_server: SmtpServer,
#[getset(get = "pub", set_with = "pub")]
#[zeroize(skip)]
sender: EmailAccount,
#[getset(get = "pub")]
#[zeroize(skip)]
recipients: IndexSet<EmailAddress>,
#[getset(get = "pub")]
#[zeroize(skip)]
cc_recipients: IndexSet<EmailAddress>,
#[getset(get = "pub")]
#[zeroize(skip)]
bcc_recipients: IndexSet<EmailAddress>,
}
impl DecryptedEmailSettings {
pub fn compose(&self, pdf: &NamedPdf) -> (Email, EmailCredentials) {
let email = Email::from((self.clone(), pdf.clone()));
let credentials = EmailCredentials::from(self.clone());
(email, credentials)
}
}
impl<T: Zeroize + HasSample> HasSample for EmailSettings<T> {
fn sample() -> Self {
Self::builder()
.smtp_app_password(T::sample())
.salt(Salt::sample())
.template(Template::default())
.smtp_server(SmtpServer::default())
.sender(EmailAccount::sample())
.recipients(IndexSet::from([
EmailAddress::sample_alice(),
EmailAddress::sample_bob(),
]))
.cc_recipients(IndexSet::from([EmailAddress::sample_carol()]))
.bcc_recipients(IndexSet::from([
EmailAddress::sample_dave(),
EmailAddress::sample_erin(),
]))
.build()
}
fn sample_other() -> Self {
Self::builder()
.smtp_app_password(T::sample_other())
.salt(Salt::sample())
.template(Template::default())
.smtp_server(SmtpServer::default())
.sender(EmailAccount::sample_other())
.recipients(IndexSet::from([
EmailAddress::sample_bob(),
EmailAddress::sample_carol(),
]))
.cc_recipients(IndexSet::from([EmailAddress::sample_dave()]))
.bcc_recipients(IndexSet::from([
EmailAddress::sample_erin(),
EmailAddress::sample_alice(),
]))
.build()
}
}
impl EncryptedEmailSettings {
fn derive_and_decrypt_smtp_app_password(
&self,
encryption_key: EncryptionKey,
) -> Result<DecryptedEmailSettings> {
let decrypted = self.smtp_app_password.decrypt(encryption_key)?;
Ok(DecryptedEmailSettings::builder()
.smtp_app_password(decrypted)
.maybe_reply_to(self.reply_to.clone())
.smtp_server(self.smtp_server.clone())
.sender(self.sender.clone())
.recipients(self.recipients.clone())
.cc_recipients(self.cc_recipients.clone())
.bcc_recipients(self.bcc_recipients.clone())
.template(self.template.clone())
.salt(self.salt().clone())
.build())
}
pub fn decrypt_smtp_app_password(
&self,
encryption_password: SecretString,
) -> Result<DecryptedEmailSettings> {
let encryption_key = PbHkdfSha256::derive_key_from(encryption_password, self.salt());
self.derive_and_decrypt_smtp_app_password(encryption_key)
}
}
#[cfg(test)]
mod tests {
use super::*;
type Sut = EncryptedEmailSettings;
#[test]
fn equality() {
assert_eq!(Sut::sample(), Sut::sample());
assert_eq!(Sut::sample_other(), Sut::sample_other());
}
#[test]
fn inequality() {
assert_ne!(Sut::sample(), Sut::sample_other());
}
}