set up imap oauth2 config

This commit is contained in:
Clément DOUIN 2023-05-03 16:54:39 +02:00
parent e271ca4293
commit 21f67bc7f5
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
9 changed files with 1244 additions and 61 deletions

1197
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -48,7 +48,8 @@ dialoguer = "0.10.2"
email_address = "0.2.4" email_address = "0.2.4"
env_logger = "0.8" env_logger = "0.8"
erased-serde = "0.3" erased-serde = "0.3"
pimalaya-email = "0.7.1" # pimalaya-email = "0.7.1"
pimalaya-email = { path = "/home/soywod/sourcehut/pimalaya/email" }
indicatif = "0.17" indicatif = "0.17"
log = "0.4" log = "0.4"
once_cell = "1.16.0" once_cell = "1.16.0"

View file

@ -21,6 +21,22 @@
"type": "github" "type": "github"
} }
}, },
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": { "flake-utils": {
"inputs": { "inputs": {
"systems": "systems" "systems": "systems"
@ -98,6 +114,7 @@
"root": { "root": {
"inputs": { "inputs": {
"fenix": "fenix", "fenix": "fenix",
"flake-compat": "flake-compat",
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
"gitignore": "gitignore", "gitignore": "gitignore",
"naersk": "naersk", "naersk": "naersk",

View file

@ -16,9 +16,13 @@
url = "github:nix-community/naersk"; url = "github:nix-community/naersk";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
flake-compat = {
url = "github:edolstra/flake-compat";
flake = false;
};
}; };
outputs = { self, nixpkgs, flake-utils, gitignore, fenix, naersk }: outputs = { self, nixpkgs, flake-utils, gitignore, fenix, naersk, ... }:
let let
inherit (gitignore.lib) gitignoreSource; inherit (gitignore.lib) gitignoreSource;

View file

@ -1,6 +1,7 @@
use pimalaya_email::{ use pimalaya_email::{
folder::sync::Strategy as SyncFoldersStrategy, EmailHooks, EmailSender, EmailTextPlainFormat, folder::sync::Strategy as SyncFoldersStrategy, EmailHooks, EmailSender, EmailTextPlainFormat,
MaildirConfig, SendmailConfig, SmtpConfig, ImapAuthConfig, MaildirConfig, OAuth2Config, OAuth2Method, OAuth2Scopes, SendmailConfig,
SmtpConfig,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{collections::HashSet, path::PathBuf}; use std::{collections::HashSet, path::PathBuf};
@ -46,8 +47,8 @@ pub struct ImapConfigDef {
pub insecure: Option<bool>, pub insecure: Option<bool>,
#[serde(rename = "imap-login")] #[serde(rename = "imap-login")]
pub login: String, pub login: String,
#[serde(rename = "imap-passwd-cmd")] #[serde(flatten, with = "ImapAuthConfigDef")]
pub passwd_cmd: String, pub auth: ImapAuthConfig,
#[serde(rename = "imap-notify-cmd")] #[serde(rename = "imap-notify-cmd")]
pub notify_cmd: Option<String>, pub notify_cmd: Option<String>,
#[serde(rename = "imap-notify-query")] #[serde(rename = "imap-notify-query")]
@ -56,6 +57,48 @@ pub struct ImapConfigDef {
pub watch_cmds: Option<Vec<String>>, pub watch_cmds: Option<Vec<String>>,
} }
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "ImapAuthConfig", rename_all = "kebab-case")]
pub enum ImapAuthConfigDef {
#[default]
None,
#[serde(rename = "imap-passwd")]
Passwd(String),
#[serde(rename = "imap-passwd-cmd")]
PasswdCmd(String),
#[serde(rename = "imap-oauth2", with = "OAuth2ConfigDef")]
OAuth2(OAuth2Config),
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "OAuth2Config", rename_all = "kebab-case")]
pub struct OAuth2ConfigDef {
#[serde(with = "OAuth2MethodDef")]
pub method: OAuth2Method,
pub client_id: String,
pub client_secret: String,
pub auth_url: String,
pub token_url: String,
#[serde(flatten, with = "OAuth2ScopesDef")]
pub scopes: OAuth2Scopes,
#[serde(default)]
pub pkce: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "OAuth2Method", rename_all = "lowercase")]
pub enum OAuth2MethodDef {
XOAuth2,
OAuthBearer,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "OAuth2Scopes", rename_all = "kebab-case")]
pub enum OAuth2ScopesDef {
Scope(String),
Scopes(Vec<String>),
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(remote = "MaildirConfig", rename_all = "kebab-case")] #[serde(remote = "MaildirConfig", rename_all = "kebab-case")]
pub struct MaildirConfigDef { pub struct MaildirConfigDef {

View file

@ -46,10 +46,11 @@ pub(crate) fn configure(base: DeserializedBaseAccountConfig) -> Result<Deseriali
.default(base.email.clone()) .default(base.email.clone())
.interact()?; .interact()?;
backend.passwd_cmd = Input::with_theme(&*THEME) // FIXME: add all variants: password, password command and oauth2
.with_prompt("What shell command should we run to get your password?") // backend.passwd_cmd = Input::with_theme(&*THEME)
.default(format!("pass show {}", &base.email)) // .with_prompt("What shell command should we run to get your password?")
.interact()?; // .default(format!("pass show {}", &base.email))
// .interact()?;
Ok(DeserializedAccountConfig::Imap( Ok(DeserializedAccountConfig::Imap(
DeserializedImapAccountConfig { base, backend }, DeserializedImapAccountConfig { base, backend },

View file

@ -11,6 +11,7 @@ use crate::{folder, ui::table};
const ARG_ACCOUNT: &str = "account"; const ARG_ACCOUNT: &str = "account";
const ARG_DRY_RUN: &str = "dry-run"; const ARG_DRY_RUN: &str = "dry-run";
const CMD_ACCOUNTS: &str = "accounts"; const CMD_ACCOUNTS: &str = "accounts";
const CMD_CONFIGURE: &str = "configure";
const CMD_LIST: &str = "list"; const CMD_LIST: &str = "list";
const CMD_SYNC: &str = "sync"; const CMD_SYNC: &str = "sync";
@ -23,6 +24,8 @@ pub enum Cmd {
List(table::args::MaxTableWidth), List(table::args::MaxTableWidth),
/// Represents the sync account command. /// Represents the sync account command.
Sync(Option<SyncFoldersStrategy>, DryRun), Sync(Option<SyncFoldersStrategy>, DryRun),
/// Configure the current selected account.
Configure,
} }
/// Represents the account command matcher. /// Represents the account command matcher.
@ -51,6 +54,9 @@ pub fn matches(m: &ArgMatches) -> Result<Option<Cmd>> {
info!("list accounts subcommand matched"); info!("list accounts subcommand matched");
let max_table_width = table::args::parse_max_width(m); let max_table_width = table::args::parse_max_width(m);
Some(Cmd::List(max_table_width)) Some(Cmd::List(max_table_width))
} else if let Some(_) = m.subcommand_matches(CMD_CONFIGURE) {
info!("configure account subcommand matched");
Some(Cmd::Configure)
} else { } else {
info!("no account subcommand matched, falling back to subcommand list"); info!("no account subcommand matched, falling back to subcommand list");
Some(Cmd::List(None)) Some(Cmd::List(None))
@ -80,6 +86,9 @@ pub fn subcmd() -> Command {
"Synchronize all folders except the given ones", "Synchronize all folders except the given ones",
)) ))
.arg(dry_run()), .arg(dry_run()),
Command::new(CMD_CONFIGURE)
.about("Configure the current selected account")
.aliases(["config", "conf", "cfg"]),
]) ])
} }

View file

@ -6,8 +6,8 @@ use anyhow::Result;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use log::{info, trace}; use log::{info, trace};
use pimalaya_email::{ use pimalaya_email::{
folder::sync::Strategy as SyncFoldersStrategy, AccountConfig, Backend, BackendSyncBuilder, folder::sync::Strategy as SyncFoldersStrategy, AccountConfig, Backend, BackendConfig,
BackendSyncProgressEvent, BackendSyncBuilder, BackendSyncProgressEvent,
}; };
use crate::{ use crate::{
@ -16,6 +16,14 @@ use crate::{
Accounts, Accounts,
}; };
/// Configure the current selected account
pub fn configure(account_config: &AccountConfig, backend_config: &BackendConfig) -> Result<()> {
info!("entering the configure account handler");
backend_config.configure(&account_config.name)?;
println!("Account {} configured!", account_config.name);
Ok(())
}
/// Lists all accounts. /// Lists all accounts.
pub fn list<'a, P: Printer>( pub fn list<'a, P: Printer>(
max_width: Option<usize>, max_width: Option<usize>,

View file

@ -137,6 +137,9 @@ fn main() -> Result<()> {
backend.close()?; backend.close()?;
return Ok(()); return Ok(());
} }
Some(account::args::Cmd::Configure) => {
return account::handlers::configure(&account_config, &backend_config);
}
_ => (), _ => (),
} }