2023-05-16 17:07:56 +00:00
|
|
|
use anyhow::Result;
|
|
|
|
use dialoguer::{Confirm, Input, Select};
|
2023-08-28 07:04:13 +00:00
|
|
|
use email::{
|
2023-12-04 21:26:49 +00:00
|
|
|
account::config::{
|
|
|
|
oauth2::{OAuth2Config, OAuth2Method, OAuth2Scopes},
|
|
|
|
passwd::PasswdConfig,
|
|
|
|
},
|
2023-12-30 21:38:25 +00:00
|
|
|
smtp::config::{SmtpAuthConfig, SmtpConfig, SmtpEncryptionKind},
|
2023-05-16 17:07:56 +00:00
|
|
|
};
|
2023-08-28 07:04:13 +00:00
|
|
|
use oauth::v2_0::{AuthorizationCodeGrant, Client};
|
|
|
|
use secret::Secret;
|
2023-05-16 17:07:56 +00:00
|
|
|
|
|
|
|
use crate::{
|
2023-12-03 21:31:43 +00:00
|
|
|
backend::config::BackendConfig,
|
2024-01-09 08:28:45 +00:00
|
|
|
ui::{prompt, THEME},
|
2023-05-16 17:07:56 +00:00
|
|
|
wizard_log, wizard_prompt,
|
|
|
|
};
|
|
|
|
|
2023-12-30 21:38:25 +00:00
|
|
|
const PROTOCOLS: &[SmtpEncryptionKind] = &[
|
|
|
|
SmtpEncryptionKind::Tls,
|
|
|
|
SmtpEncryptionKind::StartTls,
|
|
|
|
SmtpEncryptionKind::None,
|
|
|
|
];
|
2023-05-16 17:07:56 +00:00
|
|
|
|
|
|
|
const PASSWD: &str = "Password";
|
|
|
|
const OAUTH2: &str = "OAuth 2.0";
|
|
|
|
const AUTH_MECHANISMS: &[&str] = &[PASSWD, OAUTH2];
|
|
|
|
|
|
|
|
const XOAUTH2: &str = "XOAUTH2";
|
|
|
|
const OAUTHBEARER: &str = "OAUTHBEARER";
|
|
|
|
const OAUTH2_MECHANISMS: &[&str] = &[XOAUTH2, OAUTHBEARER];
|
|
|
|
|
|
|
|
const SECRETS: &[&str] = &[KEYRING, RAW, CMD];
|
|
|
|
const KEYRING: &str = "Ask the password, then save it in my system's global keyring";
|
|
|
|
const RAW: &str = "Ask the password, then save it in the configuration file (not safe)";
|
|
|
|
const CMD: &str = "Use a shell command that exposes the password";
|
|
|
|
|
2023-12-03 21:31:43 +00:00
|
|
|
pub(crate) async fn configure(account_name: &str, email: &str) -> Result<BackendConfig> {
|
2023-05-16 17:07:56 +00:00
|
|
|
let mut config = SmtpConfig::default();
|
|
|
|
|
|
|
|
config.host = Input::with_theme(&*THEME)
|
|
|
|
.with_prompt("SMTP host")
|
|
|
|
.default(format!("smtp.{}", email.rsplit_once('@').unwrap().1))
|
|
|
|
.interact()?;
|
|
|
|
|
|
|
|
let protocol = Select::with_theme(&*THEME)
|
|
|
|
.with_prompt("SMTP security protocol")
|
|
|
|
.items(PROTOCOLS)
|
|
|
|
.default(0)
|
|
|
|
.interact_opt()?;
|
|
|
|
|
|
|
|
let default_port = match protocol {
|
2023-12-30 21:38:25 +00:00
|
|
|
Some(idx) if PROTOCOLS[idx] == SmtpEncryptionKind::Tls => {
|
|
|
|
config.encryption = Some(SmtpEncryptionKind::Tls);
|
2023-05-16 17:07:56 +00:00
|
|
|
465
|
|
|
|
}
|
2023-12-30 21:38:25 +00:00
|
|
|
Some(idx) if PROTOCOLS[idx] == SmtpEncryptionKind::StartTls => {
|
|
|
|
config.encryption = Some(SmtpEncryptionKind::StartTls);
|
2023-05-16 17:07:56 +00:00
|
|
|
587
|
|
|
|
}
|
2023-12-11 21:01:48 +00:00
|
|
|
_ => {
|
2023-12-30 21:38:25 +00:00
|
|
|
config.encryption = Some(SmtpEncryptionKind::None);
|
2023-12-11 21:01:48 +00:00
|
|
|
25
|
|
|
|
}
|
2023-05-16 17:07:56 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
config.port = Input::with_theme(&*THEME)
|
|
|
|
.with_prompt("SMTP port")
|
|
|
|
.validate_with(|input: &String| input.parse::<u16>().map(|_| ()))
|
|
|
|
.default(default_port.to_string())
|
|
|
|
.interact()
|
|
|
|
.map(|input| input.parse::<u16>().unwrap())?;
|
|
|
|
|
|
|
|
config.login = Input::with_theme(&*THEME)
|
|
|
|
.with_prompt("SMTP login")
|
|
|
|
.default(email.to_owned())
|
|
|
|
.interact()?;
|
|
|
|
|
|
|
|
let auth = Select::with_theme(&*THEME)
|
|
|
|
.with_prompt("SMTP authentication mechanism")
|
|
|
|
.items(AUTH_MECHANISMS)
|
|
|
|
.default(0)
|
|
|
|
.interact_opt()?;
|
|
|
|
|
|
|
|
config.auth = match auth {
|
|
|
|
Some(idx) if AUTH_MECHANISMS[idx] == PASSWD => {
|
|
|
|
let secret = Select::with_theme(&*THEME)
|
|
|
|
.with_prompt("SMTP authentication strategy")
|
|
|
|
.items(SECRETS)
|
|
|
|
.default(0)
|
|
|
|
.interact_opt()?;
|
|
|
|
|
|
|
|
let config = match secret {
|
|
|
|
Some(idx) if SECRETS[idx] == KEYRING => {
|
2023-06-08 21:35:36 +00:00
|
|
|
Secret::new_keyring_entry(format!("{account_name}-smtp-passwd"))
|
2024-01-09 08:28:45 +00:00
|
|
|
.set_keyring_entry_secret(prompt::passwd("SMTP password")?)
|
2023-12-11 17:38:00 +00:00
|
|
|
.await?;
|
2023-05-16 17:07:56 +00:00
|
|
|
PasswdConfig::default()
|
|
|
|
}
|
|
|
|
Some(idx) if SECRETS[idx] == RAW => PasswdConfig {
|
2024-01-09 08:28:45 +00:00
|
|
|
passwd: Secret::Raw(prompt::passwd("SMTP password")?),
|
2023-05-16 17:07:56 +00:00
|
|
|
},
|
|
|
|
Some(idx) if SECRETS[idx] == CMD => PasswdConfig {
|
|
|
|
passwd: Secret::new_cmd(
|
|
|
|
Input::with_theme(&*THEME)
|
|
|
|
.with_prompt("Shell command")
|
|
|
|
.default(format!("pass show {account_name}-smtp-passwd"))
|
|
|
|
.interact()?,
|
|
|
|
),
|
|
|
|
},
|
|
|
|
_ => PasswdConfig::default(),
|
|
|
|
};
|
|
|
|
SmtpAuthConfig::Passwd(config)
|
|
|
|
}
|
|
|
|
Some(idx) if AUTH_MECHANISMS[idx] == OAUTH2 => {
|
|
|
|
let mut config = OAuth2Config::default();
|
|
|
|
|
|
|
|
let method = Select::with_theme(&*THEME)
|
|
|
|
.with_prompt("SMTP OAuth 2.0 mechanism")
|
|
|
|
.items(OAUTH2_MECHANISMS)
|
|
|
|
.default(0)
|
|
|
|
.interact_opt()?;
|
|
|
|
|
|
|
|
config.method = match method {
|
|
|
|
Some(idx) if OAUTH2_MECHANISMS[idx] == XOAUTH2 => OAuth2Method::XOAuth2,
|
|
|
|
Some(idx) if OAUTH2_MECHANISMS[idx] == OAUTHBEARER => OAuth2Method::OAuthBearer,
|
|
|
|
_ => OAuth2Method::XOAuth2,
|
|
|
|
};
|
|
|
|
|
|
|
|
config.client_id = Input::with_theme(&*THEME)
|
|
|
|
.with_prompt("SMTP OAuth 2.0 client id")
|
|
|
|
.interact()?;
|
|
|
|
|
|
|
|
let client_secret: String = Input::with_theme(&*THEME)
|
|
|
|
.with_prompt("SMTP OAuth 2.0 client secret")
|
|
|
|
.interact()?;
|
2023-06-08 21:35:36 +00:00
|
|
|
Secret::new_keyring_entry(format!("{account_name}-smtp-oauth2-client-secret"))
|
2023-12-11 17:38:00 +00:00
|
|
|
.set_keyring_entry_secret(&client_secret)
|
|
|
|
.await?;
|
2023-05-16 17:07:56 +00:00
|
|
|
|
|
|
|
config.auth_url = Input::with_theme(&*THEME)
|
|
|
|
.with_prompt("SMTP OAuth 2.0 authorization URL")
|
|
|
|
.interact()?;
|
|
|
|
|
|
|
|
config.token_url = Input::with_theme(&*THEME)
|
|
|
|
.with_prompt("SMTP OAuth 2.0 token URL")
|
|
|
|
.interact()?;
|
|
|
|
|
|
|
|
config.scopes = OAuth2Scopes::Scope(
|
|
|
|
Input::with_theme(&*THEME)
|
|
|
|
.with_prompt("SMTP OAuth 2.0 main scope")
|
|
|
|
.interact()?,
|
|
|
|
);
|
|
|
|
|
|
|
|
while Confirm::new()
|
|
|
|
.with_prompt(wizard_prompt!(
|
|
|
|
"Would you like to add more SMTP OAuth 2.0 scopes?"
|
|
|
|
))
|
|
|
|
.default(false)
|
|
|
|
.interact_opt()?
|
|
|
|
.unwrap_or_default()
|
|
|
|
{
|
|
|
|
let mut scopes = match config.scopes {
|
|
|
|
OAuth2Scopes::Scope(scope) => vec![scope],
|
|
|
|
OAuth2Scopes::Scopes(scopes) => scopes,
|
|
|
|
};
|
|
|
|
|
|
|
|
scopes.push(
|
|
|
|
Input::with_theme(&*THEME)
|
|
|
|
.with_prompt("Additional SMTP OAuth 2.0 scope")
|
|
|
|
.interact()?,
|
|
|
|
);
|
|
|
|
|
|
|
|
config.scopes = OAuth2Scopes::Scopes(scopes);
|
|
|
|
}
|
|
|
|
|
|
|
|
config.pkce = Confirm::new()
|
|
|
|
.with_prompt(wizard_prompt!(
|
|
|
|
"Would you like to enable PKCE verification?"
|
|
|
|
))
|
|
|
|
.default(true)
|
|
|
|
.interact_opt()?
|
|
|
|
.unwrap_or(true);
|
|
|
|
|
|
|
|
wizard_log!("To complete your OAuth 2.0 setup, click on the following link:");
|
|
|
|
|
2023-06-08 21:35:36 +00:00
|
|
|
let client = Client::new(
|
2023-05-16 17:07:56 +00:00
|
|
|
config.client_id.clone(),
|
|
|
|
client_secret,
|
|
|
|
config.auth_url.clone(),
|
|
|
|
config.token_url.clone(),
|
2023-06-08 21:35:36 +00:00
|
|
|
)?
|
|
|
|
.with_redirect_host(config.redirect_host.clone())
|
|
|
|
.with_redirect_port(config.redirect_port)
|
|
|
|
.build()?;
|
|
|
|
|
|
|
|
let mut auth_code_grant = AuthorizationCodeGrant::new()
|
|
|
|
.with_redirect_host(config.redirect_host.clone())
|
|
|
|
.with_redirect_port(config.redirect_port);
|
2023-05-16 17:07:56 +00:00
|
|
|
|
|
|
|
if config.pkce {
|
2023-06-08 21:35:36 +00:00
|
|
|
auth_code_grant = auth_code_grant.with_pkce();
|
2023-05-16 17:07:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for scope in config.scopes.clone() {
|
2023-06-08 21:35:36 +00:00
|
|
|
auth_code_grant = auth_code_grant.with_scope(scope);
|
2023-05-16 17:07:56 +00:00
|
|
|
}
|
|
|
|
|
2023-06-08 21:35:36 +00:00
|
|
|
let (redirect_url, csrf_token) = auth_code_grant.get_redirect_url(&client);
|
2023-05-16 17:07:56 +00:00
|
|
|
|
|
|
|
println!("{}", redirect_url.to_string());
|
2024-01-08 18:00:17 +00:00
|
|
|
println!();
|
2023-05-16 17:07:56 +00:00
|
|
|
|
2023-07-20 09:43:28 +00:00
|
|
|
let (access_token, refresh_token) = auth_code_grant
|
|
|
|
.wait_for_redirection(&client, csrf_token)
|
|
|
|
.await?;
|
2023-05-16 17:07:56 +00:00
|
|
|
|
2023-06-08 21:35:36 +00:00
|
|
|
Secret::new_keyring_entry(format!("{account_name}-smtp-oauth2-access-token"))
|
2023-12-11 17:38:00 +00:00
|
|
|
.set_keyring_entry_secret(access_token)
|
|
|
|
.await?;
|
2023-05-16 17:07:56 +00:00
|
|
|
|
|
|
|
if let Some(refresh_token) = &refresh_token {
|
2023-06-08 21:35:36 +00:00
|
|
|
Secret::new_keyring_entry(format!("{account_name}-smtp-oauth2-refresh-token"))
|
2023-12-11 17:38:00 +00:00
|
|
|
.set_keyring_entry_secret(refresh_token)
|
|
|
|
.await?;
|
2023-05-16 17:07:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SmtpAuthConfig::OAuth2(config)
|
|
|
|
}
|
|
|
|
_ => SmtpAuthConfig::default(),
|
|
|
|
};
|
|
|
|
|
2023-12-03 21:31:43 +00:00
|
|
|
Ok(BackendConfig::Smtp(config))
|
2023-05-16 17:07:56 +00:00
|
|
|
}
|