implemented storing of credentials and tokens in keyring
parent
04e818663f
commit
7c1cc34ff0
@ -1 +1,51 @@
|
||||
pub mod login;
|
||||
use crate::{
|
||||
auth::{self, AuthOptions},
|
||||
error::Result,
|
||||
publication::{self, Publication},
|
||||
secrets,
|
||||
};
|
||||
|
||||
pub async fn login<U: AsRef<str>, P: AsRef<str>>(
|
||||
username: U,
|
||||
password: P,
|
||||
options: AuthOptions,
|
||||
) -> Result<()> {
|
||||
auth::password_login(username.as_ref(), password.as_ref(), options).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list(limit: usize) -> Result<()> {
|
||||
let data = publication::list_publications(limit).await?;
|
||||
|
||||
let mut table = prettytable::Table::new();
|
||||
|
||||
table.add_row(prettytable::Row::new(
|
||||
Publication::row_labels()
|
||||
.iter()
|
||||
.map(|label| prettytable::Cell::new(label))
|
||||
.collect(),
|
||||
));
|
||||
|
||||
for publication in data {
|
||||
table.add_row(prettytable::Row::new(
|
||||
publication
|
||||
.as_row()
|
||||
.iter()
|
||||
.map(|label| prettytable::Cell::new(label))
|
||||
.collect(),
|
||||
));
|
||||
}
|
||||
|
||||
table.printstd();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn reply<I: AsRef<str>>(id: I) -> Result<()> {
|
||||
let mut client = secrets::get_client().await?;
|
||||
|
||||
client.reply_to_publication(id.as_ref()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -0,0 +1,61 @@
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
pub async fn list_publications(limit: usize) -> Result<Vec<Publication>> {
|
||||
let client = sshn_lib::Client::new(None);
|
||||
|
||||
let publications = client.get_publications_list(limit as i64).await?;
|
||||
|
||||
Ok(publications
|
||||
.housing_publications
|
||||
.ok_or(Error::MissingPublications)?
|
||||
.nodes
|
||||
.ok_or(Error::MissingPublications)?
|
||||
.edges
|
||||
.ok_or(Error::MissingPublications)?
|
||||
.into_iter()
|
||||
.filter_map(|publication| {
|
||||
let publication = publication?.node?;
|
||||
|
||||
// let city = publication.unit?.location?.city?.name.as_ref()?.to_string();
|
||||
let rent = publication.unit?.gross_rent.as_ref()?.exact;
|
||||
|
||||
Some(Publication {
|
||||
id: publication.id,
|
||||
name: String::new(),
|
||||
city: String::new(),
|
||||
nr_of_applicants: publication.total_number_of_applications,
|
||||
rent,
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub struct Publication {
|
||||
id: String,
|
||||
name: String,
|
||||
city: String,
|
||||
nr_of_applicants: i64,
|
||||
rent: f64,
|
||||
}
|
||||
|
||||
impl Publication {
|
||||
pub fn as_row(self) -> Vec<String> {
|
||||
vec![
|
||||
self.name,
|
||||
self.city,
|
||||
self.nr_of_applicants.to_string(),
|
||||
self.rent.to_string(),
|
||||
self.id,
|
||||
]
|
||||
}
|
||||
|
||||
pub fn row_labels() -> Vec<String> {
|
||||
vec![
|
||||
String::from("Name"),
|
||||
String::from("City"),
|
||||
String::from("Number of applicants"),
|
||||
String::from("Gross rent"),
|
||||
String::from("ID"),
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
use keyring::Entry;
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use sshn_lib::{AuthenticatedClient, Tokens};
|
||||
|
||||
pub use crate::error::Result;
|
||||
use crate::{auth, error::Error};
|
||||
|
||||
const SERVICE_NAME: &str = "SSHN-cli";
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Credentials {
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
impl Credentials {
|
||||
pub fn new<U: Into<String>, P: Into<String>>(username: U, password: P) -> Self {
|
||||
Self {
|
||||
username: username.into(),
|
||||
password: password.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set<I: AsRef<str>, T: Serialize>(identifier: I, data: &T) -> Result<()> {
|
||||
let user = whoami::username();
|
||||
|
||||
let entry_name = format!("{}-{}", identifier.as_ref(), SERVICE_NAME);
|
||||
|
||||
let entry = Entry::new(&entry_name, &user)?;
|
||||
|
||||
let data = serde_json::to_string(data)?;
|
||||
|
||||
entry.set_password(&data)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get<I: AsRef<str>, T: DeserializeOwned>(identifier: I) -> Result<T> {
|
||||
let user = whoami::username();
|
||||
|
||||
let entry_name = format!("{}-{}", identifier.as_ref(), SERVICE_NAME);
|
||||
|
||||
let entry = Entry::new(&entry_name, &user)?;
|
||||
|
||||
let data = entry.get_password()?;
|
||||
|
||||
let data = serde_json::from_str(&data)?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub async fn get_client() -> Result<AuthenticatedClient> {
|
||||
let client = sshn_lib::Client::new(None);
|
||||
|
||||
if let Ok(tokens) = get::<_, Tokens>("tokens") {
|
||||
if !tokens.access_token().has_expired() {
|
||||
return Ok(AuthenticatedClient::new(None, tokens));
|
||||
} else {
|
||||
if !tokens.refresh_token().has_expired() {
|
||||
return Ok(client
|
||||
.login(sshn_lib::LoginType::RefreshToken {
|
||||
token: tokens.refresh_token().content().to_string(),
|
||||
})
|
||||
.await?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("Tokens expired, logging in using credentials");
|
||||
|
||||
if let Ok(credentials) = get::<_, Credentials>("credentials") {
|
||||
return auth::password_login(
|
||||
credentials.username,
|
||||
credentials.password,
|
||||
Default::default(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
Err(Error::MissingCredentials)
|
||||
}
|
Loading…
Reference in new issue