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