use std::borrow::Borrow;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_util::codec::Framed;
use serde::Deserialize;
use blather::Telegram;
use crate::utils;
use crate::Error;
#[derive(Debug, Default)]
pub struct Builder {
name: Option<String>,
pass_file: Option<String>,
pass: Option<String>,
token_file: Option<String>,
token: Option<String>
}
impl Builder {
pub fn new() -> Self {
Self::default()
}
pub fn name<N>(mut self, nm: N) -> Self
where
N: ToString
{
self.name = Some(nm.to_string());
self
}
pub fn pass_file<P>(mut self, p: P) -> Self
where
P: ToString
{
self.pass_file = Some(p.to_string());
self
}
pub fn pass<P>(mut self, p: P) -> Self
where
P: ToString
{
self.pass = Some(p.to_string());
self
}
pub fn token_file<T>(mut self, t: T) -> Self
where
T: ToString
{
self.token_file = Some(t.to_string());
self
}
pub fn token<T>(mut self, t: T) -> Self
where
T: ToString
{
self.token = Some(t.to_string());
self
}
pub fn build(self) -> Result<Auth, Error> {
Ok(Auth {
name: self.name,
pass_file: self.pass_file,
pass: self.pass,
token_file: self.token_file,
token: self.token
})
}
}
#[derive(Clone, Debug, Default, Deserialize)]
pub struct Auth {
pub name: Option<String>,
#[serde(rename = "pass-file")]
pub pass_file: Option<String>,
pub pass: Option<String>,
#[serde(rename = "token-file")]
pub token_file: Option<String>,
pub token: Option<String>
}
impl Auth {
pub fn have_pass(&self) -> bool {
self.pass.is_some() || self.pass_file.is_some()
}
pub fn get_pass(&self) -> Result<String, Error> {
if let Some(pass) = &self.pass {
Ok(pass.clone())
} else if let Some(fname) = &self.pass_file {
if let Some(pass) = utils::read_single_line(fname) {
Ok(pass)
} else {
return Err(Error::invalid_cred(
"Unable to read passphrase from file"
));
}
} else {
Err(Error::invalid_cred("Missing passphrase"))
}
}
pub fn get_token(&self) -> Result<Option<String>, Error> {
if let Some(tkn) = &self.token {
Ok(Some(tkn.clone()))
} else if let Some(fname) = &self.token_file {
let fname = Path::new(&fname);
if fname.exists() {
if let Some(tkn) = utils::read_single_line(fname) {
Ok(Some(tkn))
} else {
Err(Error::invalid_cred("Unable to read token from file"))
}
} else if self.name.is_none() {
Err(Error::invalid_cred("Unable to read token from file"))
} else if self.pass.is_none() && self.pass_file.is_none() {
Err(Error::invalid_cred("Missing passphrase for token request"))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
pub async fn authenticate<C>(
&self,
conn: &mut Framed<C, blather::Codec>
) -> Result<Option<String>, Error>
where
C: AsyncRead + AsyncWrite + Unpin
{
let tkn = self.get_token()?;
if self.name.is_none() && tkn.is_none() {
return Ok(None);
}
if let Some(tkn) = tkn {
token(conn, CredStore::Buf(tkn)).await?;
return Ok(None);
}
if let Some(accname) = &self.name {
let pass = self.get_pass()?;
let opttkn = accpass(
conn,
accname,
CredStore::Buf(pass),
self.token_file.is_some()
)
.await?;
if let Some(tkn) = opttkn {
if let Some(fname) = &self.token_file {
let mut f = File::create(fname)?;
f.write_all(tkn.as_bytes())?;
}
return Ok(Some(tkn));
}
return Ok(None);
}
Err(Error::invalid_cred("Missing credentials"))
}
}
pub enum CredStore {
Buf(String),
File(PathBuf)
}
pub async fn token<T, O>(
conn: &mut Framed<T, blather::Codec>,
tkn: O
) -> Result<(), Error>
where
O: Borrow<CredStore>,
T: AsyncRead + AsyncWrite + Unpin
{
let tkn = match tkn.borrow() {
CredStore::Buf(s) => s.clone(),
CredStore::File(p) => {
if let Some(t) = utils::read_single_line(p) {
t
} else {
return Err(Error::invalid_cred("Unable to read token from file"));
}
}
};
let mut tg = Telegram::new_topic("Auth")?;
tg.add_param("Tkn", tkn)?;
crate::sendrecv(conn, &tg).await?;
Ok(())
}
pub async fn accpass<T, A, P>(
conn: &mut Framed<T, blather::Codec>,
accname: A,
pass: P,
reqtkn: bool
) -> Result<Option<String>, Error>
where
A: AsRef<str>,
P: Borrow<CredStore>,
T: AsyncRead + AsyncWrite + Unpin
{
let mut tg = Telegram::new_topic("Auth")?;
tg.add_param("AccName", accname.as_ref())?;
let pass = match pass.borrow() {
CredStore::Buf(s) => s.clone(),
CredStore::File(p) => {
if let Some(pass) = utils::read_single_line(p) {
pass
} else {
return Err(Error::invalid_cred(
"Unable to read passphrase from file"
));
}
}
};
tg.add_param("Pass", pass)?;
if reqtkn {
tg.add_param("ReqTkn", "True")?;
}
let params = crate::sendrecv(conn, &tg).await?;
if reqtkn {
let s = params.get_str("Tkn");
if let Some(s) = s {
Ok(Some(s.to_string()))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
pub async fn unauthenticate<T>(
conn: &mut Framed<T, blather::Codec>
) -> Result<(), Error>
where
T: AsyncRead + AsyncWrite + Unpin
{
let tg = Telegram::new_topic("Unauth")?;
crate::sendrecv(conn, &tg).await?;
Ok(())
}