make secrets have default implem

Secrets use by default the keyring, and the entry is based on the name
of the current account to avoid conflicts.
This commit is contained in:
Clément DOUIN 2023-05-07 20:52:13 +02:00
parent 728f2555d7
commit 441ce40e09
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
7 changed files with 202 additions and 188 deletions

10
Cargo.lock generated
View file

@ -2101,7 +2101,7 @@ dependencies = [
[[package]]
name = "pimalaya-email"
version = "0.7.1"
source = "git+https://git.sr.ht/~soywod/pimalaya#5474a733194623c74665c8d37066974cf3fe4477"
source = "git+https://git.sr.ht/~soywod/pimalaya#524f8049566845369f55300a20843c48dfe7a620"
dependencies = [
"ammonia",
"chrono",
@ -2142,7 +2142,7 @@ dependencies = [
[[package]]
name = "pimalaya-keyring"
version = "0.0.1"
source = "git+https://git.sr.ht/~soywod/pimalaya#5474a733194623c74665c8d37066974cf3fe4477"
source = "git+https://git.sr.ht/~soywod/pimalaya#524f8049566845369f55300a20843c48dfe7a620"
dependencies = [
"keyring",
"log",
@ -2152,7 +2152,7 @@ dependencies = [
[[package]]
name = "pimalaya-oauth2"
version = "0.0.1"
source = "git+https://git.sr.ht/~soywod/pimalaya#5474a733194623c74665c8d37066974cf3fe4477"
source = "git+https://git.sr.ht/~soywod/pimalaya#524f8049566845369f55300a20843c48dfe7a620"
dependencies = [
"log",
"oauth2",
@ -2164,7 +2164,7 @@ dependencies = [
[[package]]
name = "pimalaya-process"
version = "0.0.1"
source = "git+https://git.sr.ht/~soywod/pimalaya#5474a733194623c74665c8d37066974cf3fe4477"
source = "git+https://git.sr.ht/~soywod/pimalaya#524f8049566845369f55300a20843c48dfe7a620"
dependencies = [
"log",
"thiserror",
@ -2173,7 +2173,7 @@ dependencies = [
[[package]]
name = "pimalaya-secret"
version = "0.0.1"
source = "git+https://git.sr.ht/~soywod/pimalaya#5474a733194623c74665c8d37066974cf3fe4477"
source = "git+https://git.sr.ht/~soywod/pimalaya#524f8049566845369f55300a20843c48dfe7a620"
dependencies = [
"log",
"pimalaya-keyring",

View file

@ -55,6 +55,10 @@ pimalaya-email = { git = "https://git.sr.ht/~soywod/pimalaya" }
pimalaya-keyring = { git = "https://git.sr.ht/~soywod/pimalaya" }
pimalaya-process = { git = "https://git.sr.ht/~soywod/pimalaya" }
pimalaya-secret = { git = "https://git.sr.ht/~soywod/pimalaya" }
# pimalaya-email = { path = "/home/soywod/sourcehut/pimalaya/email" }
# pimalaya-keyring = { path = "/home/soywod/sourcehut/pimalaya/keyring" }
# pimalaya-process = { path = "/home/soywod/sourcehut/pimalaya/process" }
# pimalaya-secret = { path = "/home/soywod/sourcehut/pimalaya/secret" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
shellexpand = "2.1"

View file

@ -1,7 +1,7 @@
use pimalaya_email::{
folder::sync::Strategy as SyncFoldersStrategy, EmailHooks, EmailSender, EmailTextPlainFormat,
ImapAuthConfig, MaildirConfig, OAuth2Config, OAuth2Method, OAuth2Scopes, SendmailConfig,
SmtpAuthConfig, SmtpConfig,
ImapAuthConfig, MaildirConfig, OAuth2Config, OAuth2Method, OAuth2Scopes, PasswdConfig,
SendmailConfig, SmtpAuthConfig, SmtpConfig,
};
use pimalaya_keyring::Entry;
use pimalaya_process::Cmd;
@ -15,14 +15,50 @@ use pimalaya_email::ImapConfig;
#[cfg(feature = "notmuch-backend")]
use pimalaya_email::NotmuchConfig;
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "Cmd", from = "String")]
pub struct CmdDef;
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "Entry", from = "String")]
pub struct EntryDef;
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "Cmd", from = "String")]
pub struct SingleCmdDef;
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "Cmd", from = "Vec<String>")]
pub struct PipelineDef;
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "Cmd", from = "SingleCmdOrPipeline")]
pub struct SingleCmdOrPipelineDef;
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum SingleCmdOrPipeline {
#[serde(with = "SingleCmdDef")]
SingleCmd(Cmd),
#[serde(with = "PipelineDef")]
Pipeline(Cmd),
}
impl From<SingleCmdOrPipeline> for Cmd {
fn from(cmd: SingleCmdOrPipeline) -> Cmd {
match cmd {
SingleCmdOrPipeline::SingleCmd(cmd) => cmd,
SingleCmdOrPipeline::Pipeline(cmd) => cmd,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "Secret", rename_all = "kebab-case")]
pub enum SecretDef {
Raw(String),
#[serde(with = "SingleCmdOrPipelineDef")]
Cmd(Cmd),
#[serde(with = "EntryDef")]
Keyring(Entry),
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "OAuth2Method")]
pub enum OAuth2MethodDef {
@ -32,110 +68,6 @@ pub enum OAuth2MethodDef {
OAuthBearer,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "SmtpConfig")]
struct SmtpConfigDef {
#[serde(rename = "smtp-host")]
pub host: String,
#[serde(rename = "smtp-port")]
pub port: u16,
#[serde(rename = "smtp-ssl")]
pub ssl: Option<bool>,
#[serde(rename = "smtp-starttls")]
pub starttls: Option<bool>,
#[serde(rename = "smtp-insecure")]
pub insecure: Option<bool>,
#[serde(rename = "smtp-login")]
pub login: String,
#[serde(flatten, with = "SmtpAuthConfigDef")]
pub auth: SmtpAuthConfig,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "SmtpAuthConfig", tag = "smtp-auth")]
pub enum SmtpAuthConfigDef {
#[serde(rename = "passwd", alias = "password", with = "SmtpPasswdDef")]
Passwd(Secret),
#[serde(rename = "oauth2", with = "SmtpOAuth2ConfigDef")]
OAuth2(OAuth2Config),
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "Secret", rename_all = "kebab-case")]
pub enum SmtpPasswdDef {
#[serde(rename = "smtp-passwd")]
Raw(String),
#[serde(rename = "smtp-passwd-cmd", with = "CmdDef")]
Cmd(Cmd),
#[serde(rename = "smtp-passwd-keyring", with = "EntryDef")]
Keyring(Entry),
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "OAuth2Config")]
pub struct SmtpOAuth2ConfigDef {
#[serde(rename = "smtp-oauth2-method", with = "OAuth2MethodDef")]
pub method: OAuth2Method,
#[serde(rename = "smtp-oauth2-client-id")]
pub client_id: String,
#[serde(flatten, with = "SmtpOAuth2ClientSecretDef")]
pub client_secret: Secret,
#[serde(rename = "smtp-oauth2-auth-url")]
pub auth_url: String,
#[serde(rename = "smtp-oauth2-token-url")]
pub token_url: String,
#[serde(flatten, with = "SmtpOAuth2AccessTokenDef")]
pub access_token: Secret,
#[serde(flatten, with = "SmtpOAuth2RefreshTokenDef")]
pub refresh_token: Secret,
#[serde(flatten, with = "SmtpOAuth2ScopesDef")]
pub scopes: OAuth2Scopes,
#[serde(rename = "smtp-oauth2-pkce", default)]
pub pkce: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "Secret")]
pub enum SmtpOAuth2ClientSecretDef {
#[serde(rename = "smtp-oauth2-client-secret")]
Raw(String),
#[serde(rename = "smtp-oauth2-client-secret-cmd", with = "CmdDef")]
Cmd(Cmd),
#[serde(rename = "smtp-oauth2-client-secret-keyring", with = "EntryDef")]
Keyring(Entry),
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "Secret")]
pub enum SmtpOAuth2AccessTokenDef {
#[serde(rename = "smtp-oauth2-access-token")]
Raw(String),
#[serde(rename = "smtp-oauth2-access-token-cmd", with = "CmdDef")]
Cmd(Cmd),
#[serde(rename = "smtp-oauth2-access-token-keyring", with = "EntryDef")]
Keyring(Entry),
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "Secret")]
pub enum SmtpOAuth2RefreshTokenDef {
#[serde(rename = "smtp-oauth2-refresh-token")]
Raw(String),
#[serde(rename = "smtp-oauth2-refresh-token-cmd", with = "CmdDef")]
Cmd(Cmd),
#[serde(rename = "smtp-oauth2-refresh-token-keyring", with = "EntryDef")]
Keyring(Entry),
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "OAuth2Scopes")]
pub enum SmtpOAuth2ScopesDef {
#[serde(rename = "smtp-oauth2-scope")]
Scope(String),
#[serde(rename = "smtp-oauth2-scopes")]
Scopes(Vec<String>),
}
#[cfg(feature = "imap-backend")]
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "ImapConfig")]
@ -165,39 +97,35 @@ pub struct ImapConfigDef {
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "ImapAuthConfig", tag = "imap-auth")]
pub enum ImapAuthConfigDef {
#[serde(rename = "passwd", alias = "password", with = "ImapPasswdDef")]
Passwd(Secret),
#[serde(rename = "passwd", alias = "password", with = "ImapPasswdConfigDef")]
Passwd(#[serde(default)] PasswdConfig),
#[serde(rename = "oauth2", with = "ImapOAuth2ConfigDef")]
OAuth2(OAuth2Config),
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "Secret", rename_all = "kebab-case")]
pub enum ImapPasswdDef {
#[serde(rename = "imap-passwd")]
Raw(String),
#[serde(rename = "imap-passwd-cmd", with = "CmdDef")]
Cmd(Cmd),
#[serde(rename = "imap-passwd-keyring", with = "EntryDef")]
Keyring(Entry),
#[serde(remote = "PasswdConfig")]
pub struct ImapPasswdConfigDef {
#[serde(rename = "imap-passwd", with = "SecretDef", default)]
pub passwd: Secret,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "OAuth2Config")]
pub struct ImapOAuth2ConfigDef {
#[serde(rename = "imap-oauth2-method", with = "OAuth2MethodDef")]
#[serde(rename = "imap-oauth2-method", with = "OAuth2MethodDef", default)]
pub method: OAuth2Method,
#[serde(rename = "imap-oauth2-client-id")]
pub client_id: String,
#[serde(flatten, with = "ImapOAuth2ClientSecretDef")]
#[serde(rename = "imap-oauth2-client-secret", with = "SecretDef", default)]
pub client_secret: Secret,
#[serde(rename = "imap-oauth2-auth-url")]
pub auth_url: String,
#[serde(rename = "imap-oauth2-token-url")]
pub token_url: String,
#[serde(flatten, with = "ImapOAuth2AccessTokenDef")]
#[serde(rename = "imap-oauth2-access-token", with = "SecretDef", default)]
pub access_token: Secret,
#[serde(flatten, with = "ImapOAuth2RefreshTokenDef")]
#[serde(rename = "imap-oauth2-refresh-token", with = "SecretDef", default)]
pub refresh_token: Secret,
#[serde(flatten, with = "ImapOAuth2ScopesDef")]
pub scopes: OAuth2Scopes,
@ -205,39 +133,6 @@ pub struct ImapOAuth2ConfigDef {
pub pkce: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "Secret")]
pub enum ImapOAuth2ClientSecretDef {
#[serde(rename = "imap-oauth2-client-secret")]
Raw(String),
#[serde(rename = "imap-oauth2-client-secret-cmd", with = "CmdDef")]
Cmd(Cmd),
#[serde(rename = "imap-oauth2-client-secret-keyring", with = "EntryDef")]
Keyring(Entry),
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "Secret")]
pub enum ImapOAuth2AccessTokenDef {
#[serde(rename = "imap-oauth2-access-token")]
Raw(String),
#[serde(rename = "imap-oauth2-access-token-cmd", with = "CmdDef")]
Cmd(Cmd),
#[serde(rename = "imap-oauth2-access-token-keyring", with = "EntryDef")]
Keyring(Entry),
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "Secret")]
pub enum ImapOAuth2RefreshTokenDef {
#[serde(rename = "imap-oauth2-refresh-token")]
Raw(String),
#[serde(rename = "imap-oauth2-refresh-token-cmd", with = "CmdDef")]
Cmd(Cmd),
#[serde(rename = "imap-oauth2-refresh-token-keyring", with = "EntryDef")]
Keyring(Entry),
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "OAuth2Scopes")]
pub enum ImapOAuth2ScopesDef {
@ -287,6 +182,73 @@ pub enum EmailSenderDef {
Sendmail(SendmailConfig),
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "SmtpConfig")]
struct SmtpConfigDef {
#[serde(rename = "smtp-host")]
pub host: String,
#[serde(rename = "smtp-port")]
pub port: u16,
#[serde(rename = "smtp-ssl")]
pub ssl: Option<bool>,
#[serde(rename = "smtp-starttls")]
pub starttls: Option<bool>,
#[serde(rename = "smtp-insecure")]
pub insecure: Option<bool>,
#[serde(rename = "smtp-login")]
pub login: String,
#[serde(flatten, with = "SmtpAuthConfigDef")]
pub auth: SmtpAuthConfig,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "SmtpAuthConfig", tag = "smtp-auth")]
pub enum SmtpAuthConfigDef {
#[serde(rename = "passwd", alias = "password", with = "SmtpPasswdConfigDef")]
Passwd(#[serde(default)] PasswdConfig),
#[serde(rename = "oauth2", with = "SmtpOAuth2ConfigDef")]
OAuth2(OAuth2Config),
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "PasswdConfig", default)]
pub struct SmtpPasswdConfigDef {
#[serde(rename = "smtp-passwd", with = "SecretDef", default)]
pub passwd: Secret,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "OAuth2Config")]
pub struct SmtpOAuth2ConfigDef {
#[serde(rename = "smtp-oauth2-method", with = "OAuth2MethodDef", default)]
pub method: OAuth2Method,
#[serde(rename = "smtp-oauth2-client-id")]
pub client_id: String,
#[serde(rename = "smtp-oauth2-client-secret", with = "SecretDef", default)]
pub client_secret: Secret,
#[serde(rename = "smtp-oauth2-auth-url")]
pub auth_url: String,
#[serde(rename = "smtp-oauth2-token-url")]
pub token_url: String,
#[serde(rename = "smtp-oauth2-access-token", with = "SecretDef", default)]
pub access_token: Secret,
#[serde(rename = "smtp-oauth2-refresh-token", with = "SecretDef", default)]
pub refresh_token: Secret,
#[serde(flatten, with = "SmtpOAuth2ScopesDef")]
pub scopes: OAuth2Scopes,
#[serde(rename = "smtp-oauth2-pkce", default)]
pub pkce: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "OAuth2Scopes")]
pub enum SmtpOAuth2ScopesDef {
#[serde(rename = "smtp-oauth2-scope")]
Scope(String),
#[serde(rename = "smtp-oauth2-scopes")]
Scopes(Vec<String>),
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "SendmailConfig", rename_all = "kebab-case")]
pub struct SendmailConfigDef {

View file

@ -1,7 +1,6 @@
use anyhow::Result;
use dialoguer::{Input, Select};
use pimalaya_email::ImapConfig;
use std::io;
use crate::account::{
DeserializedAccountConfig, DeserializedBaseAccountConfig, DeserializedImapAccountConfig,
@ -59,11 +58,3 @@ pub(crate) fn configure(base: DeserializedBaseAccountConfig) -> Result<Deseriali
DeserializedImapAccountConfig { base, backend },
))
}
#[cfg(feature = "imap-backend")]
pub(crate) fn configure_oauth2_client_secret() -> io::Result<String> {
Input::with_theme(&*THEME)
.with_prompt("Enter your OAuth 2.0 client secret:")
.report(false)
.interact()
}

View file

@ -11,10 +11,10 @@ use super::DeserializedConfig;
use crate::account::{DeserializedAccountConfig, DeserializedBaseAccountConfig};
use anyhow::{anyhow, Result};
use console::style;
use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select};
use dialoguer::{theme::ColorfulTheme, Confirm, Input, Password, Select};
use log::trace;
use once_cell::sync::Lazy;
use std::{fs, process};
use std::{fs, io, process};
const BACKENDS: &[&str] = &[
"Maildir",
@ -163,3 +163,20 @@ fn configure_base() -> Result<DeserializedBaseAccountConfig> {
Ok(base_account_config)
}
pub(crate) fn prompt_passwd(prompt: &str) -> io::Result<String> {
Password::with_theme(&*THEME)
.with_prompt(prompt)
.with_confirmation(
"Confirm password:",
"Passwords do not match, please try again.",
)
.interact()
}
pub(crate) fn prompt_secret(prompt: &str) -> io::Result<String> {
Input::with_theme(&*THEME)
.with_prompt(prompt)
.report(false)
.interact()
}

View file

@ -5,7 +5,7 @@
use pimalaya_email::{
folder::sync::Strategy as SyncFoldersStrategy, AccountConfig, BackendConfig, EmailHooks,
EmailSender, EmailTextPlainFormat, MaildirConfig,
EmailSender, EmailTextPlainFormat, ImapAuthConfig, MaildirConfig,
};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, path::PathBuf};
@ -46,10 +46,29 @@ impl DeserializedAccountConfig {
BackendConfig::Maildir(config.backend.clone()),
),
#[cfg(feature = "imap-backend")]
DeserializedAccountConfig::Imap(config) => (
config.base.to_account_config(name, global_config),
BackendConfig::Imap(config.backend.clone()),
),
DeserializedAccountConfig::Imap(config) => {
let mut imap_config = config.backend.clone();
match &mut imap_config.auth {
ImapAuthConfig::Passwd(secret) => {
secret.replace_undefined_entry_with(format!("{name}-imap-passwd"));
}
ImapAuthConfig::OAuth2(config) => {
config.client_secret.replace_undefined_entry_with(format!(
"{name}-imap-oauth2-client-secret"
));
config.access_token.replace_undefined_entry_with(format!(
"{name}-imap-oauth2-access-token"
));
config.refresh_token.replace_undefined_entry_with(format!(
"{name}-imap-oauth2-refresh-token"
));
}
};
let account_config = config.base.to_account_config(name, global_config);
(account_config, BackendConfig::Imap(imap_config))
}
#[cfg(feature = "notmuch-backend")]
DeserializedAccountConfig::Notmuch(config) => (
config.base.to_account_config(name, global_config),

View file

@ -7,11 +7,14 @@ use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use log::{info, trace, warn};
use pimalaya_email::{
folder::sync::Strategy as SyncFoldersStrategy, AccountConfig, Backend, BackendConfig,
BackendSyncBuilder, BackendSyncProgressEvent, EmailSender,
BackendSyncBuilder, BackendSyncProgressEvent, EmailSender, ImapAuthConfig, SmtpAuthConfig,
};
use crate::{
config::{wizard::imap::configure_oauth2_client_secret, DeserializedConfig},
config::{
wizard::{prompt_passwd, prompt_secret},
DeserializedConfig,
},
printer::{PrintTableOpts, Printer},
Accounts,
};
@ -27,8 +30,11 @@ pub fn configure(
if reset {
#[cfg(feature = "imap-backend")]
if let BackendConfig::Imap(imap_config) = backend_config {
println!("Resetting IMAP secrets…");
if let Err(err) = imap_config.auth.reset() {
let reset = match &imap_config.auth {
ImapAuthConfig::Passwd(passwd) => passwd.reset(),
ImapAuthConfig::OAuth2(oauth2) => oauth2.reset(),
};
if let Err(err) = reset {
warn!("error while resetting imap secrets, skipping it");
warn!("{err}");
}
@ -36,8 +42,11 @@ pub fn configure(
#[cfg(feature = "smtp-sender")]
if let EmailSender::Smtp(smtp_config) = &account_config.email_sender {
println!("Resetting SMTP secrets…");
if let Err(err) = smtp_config.auth.reset() {
let reset = match &smtp_config.auth {
SmtpAuthConfig::Passwd(passwd) => passwd.reset(),
SmtpAuthConfig::OAuth2(oauth2) => oauth2.reset(),
};
if let Err(err) = reset {
warn!("error while resetting smtp secrets, skipping it");
warn!("{err}");
}
@ -46,14 +55,26 @@ pub fn configure(
#[cfg(feature = "imap-backend")]
if let BackendConfig::Imap(imap_config) = backend_config {
println!("Configuring IMAP secrets…");
imap_config.auth.configure(configure_oauth2_client_secret)?;
match &imap_config.auth {
ImapAuthConfig::Passwd(passwd) => {
passwd.configure(|| prompt_passwd("Enter your IMAP password:"))
}
ImapAuthConfig::OAuth2(oauth2) => {
oauth2.configure(|| prompt_secret("Enter your IMAP OAuth 2.0 client secret:"))
}
}?;
}
#[cfg(feature = "smtp-sender")]
if let EmailSender::Smtp(smtp_config) = &account_config.email_sender {
println!("Configuring SMTP secrets…");
smtp_config.auth.configure(configure_oauth2_client_secret)?;
match &smtp_config.auth {
SmtpAuthConfig::Passwd(passwd) => {
passwd.configure(|| prompt_passwd("Enter your SMTP password:"))
}
SmtpAuthConfig::OAuth2(oauth2) => {
oauth2.configure(|| prompt_secret("Enter your SMTP OAuth 2.0 client secret:"))
}
}?;
}
println!("Account successfully configured!");