Formatting
Signed-off-by: Jacob Kiers <code@kiers.eu>
This commit is contained in:
parent
07f08bd253
commit
7b8c9453d6
287
src/config.rs
287
src/config.rs
@ -1,138 +1,149 @@
|
|||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use isahc::{ReadResponseExt, RequestExt};
|
use isahc::http::StatusCode;
|
||||||
use isahc::http::StatusCode;
|
use isahc::{ReadResponseExt, RequestExt};
|
||||||
use rsa::pkcs1::{DecodeRsaPrivateKey, EncodeRsaPrivateKey, LineEnding};
|
use rsa::pkcs1::{DecodeRsaPrivateKey, EncodeRsaPrivateKey, LineEnding};
|
||||||
use rsa::pkcs8::EncodePublicKey;
|
use rsa::pkcs8::EncodePublicKey;
|
||||||
use rsa::RsaPrivateKey;
|
use rsa::RsaPrivateKey;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{BASE_URL, BunqClient, deserialize_retarded_response, sign};
|
use crate::{deserialize_retarded_response, sign, BunqClient, BASE_URL};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default)]
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
struct AppState {
|
struct AppState {
|
||||||
token: String,
|
token: String,
|
||||||
pem_private: String,
|
pem_private: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default)]
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
pub struct BunqConfig {
|
pub struct BunqConfig {
|
||||||
api_key: String,
|
api_key: String,
|
||||||
state: Option<AppState>,
|
state: Option<AppState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BunqConfig {
|
impl BunqConfig {
|
||||||
pub fn load() -> anyhow::Result<BunqConfig> {
|
pub fn load() -> anyhow::Result<BunqConfig> {
|
||||||
println!("Loading config file from {}", confy::get_configuration_file_path("bunq-rs", "bunq-rs")?.to_string_lossy());
|
println!(
|
||||||
Ok(confy::load("bunq-rs", "bunq-rs")?)
|
"Loading config file from {}",
|
||||||
}
|
confy::get_configuration_file_path("bunq-rs", "bunq-rs")?.to_string_lossy()
|
||||||
pub fn save(&self) -> anyhow::Result<()> {
|
);
|
||||||
println!("Storing config file in {}", confy::get_configuration_file_path("bunq-rs", None)?.to_string_lossy());
|
Ok(confy::load("bunq-rs", "bunq-rs")?)
|
||||||
confy::store("bunq-rs", "bunq-rs", self)?;
|
}
|
||||||
Ok(())
|
pub fn save(&self) -> anyhow::Result<()> {
|
||||||
}
|
println!(
|
||||||
pub fn install(mut self) -> anyhow::Result<BunqClient> {
|
"Storing config file in {}",
|
||||||
let api_key = &self.api_key;
|
confy::get_configuration_file_path("bunq-rs", None)?.to_string_lossy()
|
||||||
|
);
|
||||||
let keypair = if let Some(state) = &self.state {
|
confy::store("bunq-rs", "bunq-rs", self)?;
|
||||||
RsaPrivateKey::from_pkcs1_pem(&state.pem_private)?
|
Ok(())
|
||||||
} else {
|
}
|
||||||
let mut rng = rand::thread_rng();
|
pub fn install(mut self) -> anyhow::Result<BunqClient> {
|
||||||
|
let api_key = &self.api_key;
|
||||||
let bits = 2048;
|
|
||||||
let keypair = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
|
let keypair = if let Some(state) = &self.state {
|
||||||
|
RsaPrivateKey::from_pkcs1_pem(&state.pem_private)?
|
||||||
let pem_public = keypair.to_public_key().to_public_key_pem(LineEnding::CRLF)?;
|
} else {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
let body = Installation {
|
|
||||||
client_public_key: &pem_public,
|
let bits = 2048;
|
||||||
};
|
let keypair = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
|
||||||
let response = isahc::post(
|
|
||||||
format!("{}/v1/installation", BASE_URL),
|
let pem_public = keypair
|
||||||
serde_json::to_string(&body)?,
|
.to_public_key()
|
||||||
)?
|
.to_public_key_pem(LineEnding::CRLF)?;
|
||||||
.text()?;
|
|
||||||
let response: InstallationResponse = deserialize_retarded_response(&response)?.response;
|
let body = Installation {
|
||||||
let token = response.token.token;
|
client_public_key: &pem_public,
|
||||||
|
};
|
||||||
let body = DeviceServer {
|
let response = isahc::post(
|
||||||
description: "JK Test Server",
|
format!("{}/v1/installation", BASE_URL),
|
||||||
secret: api_key,
|
serde_json::to_string(&body)?,
|
||||||
permitted_ips: &["77.175.86.236", "*"],
|
)?
|
||||||
};
|
.text()?;
|
||||||
let body = serde_json::to_string(&body)?;
|
let response: InstallationResponse = deserialize_retarded_response(&response)?.response;
|
||||||
let mut response = isahc::http::Request::post(format!("{}/v1/device-server", BASE_URL))
|
let token = response.token.token;
|
||||||
.header("X-Bunq-Client-Authentication", &token)
|
|
||||||
.body(body)?
|
let body = DeviceServer {
|
||||||
.send()?;
|
description: "JK Test Server",
|
||||||
|
secret: api_key,
|
||||||
let response_text = response.text()?;
|
permitted_ips: &["77.175.86.236", "*"],
|
||||||
println!("{}", response_text);
|
};
|
||||||
|
let body = serde_json::to_string(&body)?;
|
||||||
if response.status() != StatusCode::OK {
|
let mut response = isahc::http::Request::post(format!("{}/v1/device-server", BASE_URL))
|
||||||
return Err(anyhow!(response_text));
|
.header("X-Bunq-Client-Authentication", &token)
|
||||||
}
|
.body(body)?
|
||||||
|
.send()?;
|
||||||
self.state = Some(AppState { pem_private: keypair.to_pkcs1_pem(LineEnding::CRLF)?.to_string(), token });
|
|
||||||
self.save()?;
|
let response_text = response.text()?;
|
||||||
|
println!("{}", response_text);
|
||||||
keypair
|
|
||||||
};
|
if response.status() != StatusCode::OK {
|
||||||
let token = self.state.unwrap().token;
|
return Err(anyhow!(response_text));
|
||||||
let body = SessionServer { secret: api_key };
|
}
|
||||||
let body = serde_json::to_string(&body)?;
|
|
||||||
let sig = sign(&body, &keypair)?;
|
self.state = Some(AppState {
|
||||||
let response = isahc::http::Request::post(format!("{}/v1/session-server", BASE_URL))
|
pem_private: keypair.to_pkcs1_pem(LineEnding::CRLF)?.to_string(),
|
||||||
.header("X-Bunq-Client-Authentication", &token)
|
token,
|
||||||
.header("X-Bunq-Client-Signature", &sig)
|
});
|
||||||
.body(body)?
|
self.save()?;
|
||||||
.send()?
|
|
||||||
.text()?;
|
keypair
|
||||||
let r: SessionServerResponse = deserialize_retarded_response(&response)?.response;
|
};
|
||||||
|
let token = self.state.unwrap().token;
|
||||||
Ok(BunqClient {
|
let body = SessionServer { secret: api_key };
|
||||||
keypair,
|
let body = serde_json::to_string(&body)?;
|
||||||
token: r.token.token,
|
let sig = sign(&body, &keypair)?;
|
||||||
user_id: r.user_person.id,
|
let response = isahc::http::Request::post(format!("{}/v1/session-server", BASE_URL))
|
||||||
})
|
.header("X-Bunq-Client-Authentication", &token)
|
||||||
}
|
.header("X-Bunq-Client-Signature", &sig)
|
||||||
}
|
.body(body)?
|
||||||
|
.send()?
|
||||||
#[derive(Serialize)]
|
.text()?;
|
||||||
pub(super) struct Installation<'a> {
|
let r: SessionServerResponse = deserialize_retarded_response(&response)?.response;
|
||||||
pub(super) client_public_key: &'a str,
|
|
||||||
}
|
Ok(BunqClient {
|
||||||
|
keypair,
|
||||||
#[derive(Deserialize)]
|
token: r.token.token,
|
||||||
pub(super) struct Token {
|
user_id: r.user_person.id,
|
||||||
pub(super) token: String,
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[derive(Serialize)]
|
||||||
pub(super) struct InstallationResponse {
|
pub(super) struct Installation<'a> {
|
||||||
pub(super) token: Token,
|
pub(super) client_public_key: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Deserialize)]
|
||||||
pub(super) struct DeviceServer<'a> {
|
pub(super) struct Token {
|
||||||
pub(super) description: &'a str,
|
pub(super) token: String,
|
||||||
pub(super) secret: &'a str,
|
}
|
||||||
pub(super) permitted_ips: &'a [&'a str],
|
|
||||||
}
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
#[derive(Serialize)]
|
pub(super) struct InstallationResponse {
|
||||||
pub(super) struct SessionServer<'a> {
|
pub(super) token: Token,
|
||||||
pub(super) secret: &'a str,
|
}
|
||||||
}
|
|
||||||
|
#[derive(Serialize)]
|
||||||
#[derive(Deserialize)]
|
pub(super) struct DeviceServer<'a> {
|
||||||
#[serde(rename_all = "PascalCase")]
|
pub(super) description: &'a str,
|
||||||
pub(super) struct SessionServerResponse {
|
pub(super) secret: &'a str,
|
||||||
pub(super) token: Token,
|
pub(super) permitted_ips: &'a [&'a str],
|
||||||
pub(super) user_person: UserPerson,
|
}
|
||||||
}
|
|
||||||
|
#[derive(Serialize)]
|
||||||
#[derive(Deserialize)]
|
pub(super) struct SessionServer<'a> {
|
||||||
pub(super) struct UserPerson {
|
pub(super) secret: &'a str,
|
||||||
pub(super) id: i64,
|
}
|
||||||
}
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub(super) struct SessionServerResponse {
|
||||||
|
pub(super) token: Token,
|
||||||
|
pub(super) user_person: UserPerson,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub(super) struct UserPerson {
|
||||||
|
pub(super) id: i64,
|
||||||
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use rsa::pkcs1v15::SigningKey;
|
use rsa::pkcs1v15::SigningKey;
|
||||||
use rsa::RsaPrivateKey;
|
|
||||||
use rsa::sha2::Sha256;
|
use rsa::sha2::Sha256;
|
||||||
use rsa::signature::RandomizedSigner;
|
use rsa::signature::RandomizedSigner;
|
||||||
|
use rsa::RsaPrivateKey;
|
||||||
use serde::{de::DeserializeOwned, Deserialize};
|
use serde::{de::DeserializeOwned, Deserialize};
|
||||||
|
|
||||||
pub use config::BunqConfig;
|
pub use config::BunqConfig;
|
||||||
@ -94,7 +94,7 @@ fn deserialize_normal_response<T: DeserializeOwned>(r: &str) -> Result<Response<
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn sign(body: &str, key: &RsaPrivateKey) -> Result<String> {
|
fn sign(body: &str, key: &RsaPrivateKey) -> Result<String> {
|
||||||
use base64::prelude::{BASE64_STANDARD, Engine};
|
use base64::prelude::{Engine, BASE64_STANDARD};
|
||||||
|
|
||||||
let signing_key = SigningKey::<Sha256>::new(key.clone());
|
let signing_key = SigningKey::<Sha256>::new(key.clone());
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
@ -1,38 +1,38 @@
|
|||||||
use isahc::{ReadResponseExt, RequestExt};
|
use isahc::{ReadResponseExt, RequestExt};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::{BASE_URL, BunqClient};
|
use crate::{BunqClient, BASE_URL};
|
||||||
|
|
||||||
pub(super) fn get(client: &BunqClient) -> anyhow::Result<Vec<MonetaryAccount>> {
|
pub(super) fn get(client: &BunqClient) -> anyhow::Result<Vec<MonetaryAccount>> {
|
||||||
let response = isahc::http::Request::get(format!(
|
let response = isahc::http::Request::get(format!(
|
||||||
"{}/v1/user/{}/monetary-account",
|
"{}/v1/user/{}/monetary-account",
|
||||||
BASE_URL, client.user_id
|
BASE_URL, client.user_id
|
||||||
))
|
))
|
||||||
.header("X-Bunq-Client-Authentication", &client.token)
|
.header("X-Bunq-Client-Authentication", &client.token)
|
||||||
.body(())?
|
.body(())?
|
||||||
.send()?
|
.send()?
|
||||||
.text()?;
|
.text()?;
|
||||||
|
|
||||||
let accounts = crate::deserialize_normal_response::<Vec<MonetaryAccount>>(&response)?;
|
let accounts = crate::deserialize_normal_response::<Vec<MonetaryAccount>>(&response)?;
|
||||||
|
|
||||||
Ok(accounts.response)
|
Ok(accounts.response)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
pub enum MonetaryAccount {
|
pub enum MonetaryAccount {
|
||||||
MonetaryAccountBank(MonetaryAccountBank),
|
MonetaryAccountBank(MonetaryAccountBank),
|
||||||
MonetaryAccountSavings(MonetaryAccountSavings),
|
MonetaryAccountSavings(MonetaryAccountSavings),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct MonetaryAccountBank {
|
pub struct MonetaryAccountBank {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct MonetaryAccountSavings {
|
pub struct MonetaryAccountSavings {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
}
|
}
|
||||||
|
201
src/payment.rs
201
src/payment.rs
@ -1,100 +1,101 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use isahc::{ReadResponseExt, RequestExt};
|
use isahc::{ReadResponseExt, RequestExt};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::{BASE_URL, BunqClient, MonetaryAccount, Pagination, Response};
|
use crate::{BunqClient, MonetaryAccount, Pagination, Response, BASE_URL};
|
||||||
|
|
||||||
pub(super) fn get_from_to(client: &BunqClient,
|
pub(super) fn get_from_to(
|
||||||
acc: &MonetaryAccount,
|
client: &BunqClient,
|
||||||
from: Option<i64>,
|
acc: &MonetaryAccount,
|
||||||
to: Option<i64>) -> Result<Vec<Payment>>
|
from: Option<i64>,
|
||||||
{
|
to: Option<i64>,
|
||||||
let next_page = |url: &str| -> Result<(_, _)> {
|
) -> Result<Vec<Payment>> {
|
||||||
let response = isahc::http::Request::get(url)
|
let next_page = |url: &str| -> Result<(_, _)> {
|
||||||
.header("X-Bunq-Client-Authentication", &client.token)
|
let response = isahc::http::Request::get(url)
|
||||||
.body(())?
|
.header("X-Bunq-Client-Authentication", &client.token)
|
||||||
.send()?
|
.body(())?
|
||||||
.text()?;
|
.send()?
|
||||||
let Response {
|
.text()?;
|
||||||
response,
|
let Response {
|
||||||
pagination,
|
response,
|
||||||
} = crate::deserialize_normal_response::<Vec<PaymentPayment>>(&response)?;
|
pagination,
|
||||||
Ok((
|
} = crate::deserialize_normal_response::<Vec<PaymentPayment>>(&response)?;
|
||||||
response.into_iter().map(|p| p.payment).collect(),
|
Ok((
|
||||||
pagination,
|
response.into_iter().map(|p| p.payment).collect(),
|
||||||
))
|
pagination,
|
||||||
};
|
))
|
||||||
|
};
|
||||||
let account_id = match acc {
|
|
||||||
MonetaryAccount::MonetaryAccountBank(bank) => bank.id,
|
let account_id = match acc {
|
||||||
MonetaryAccount::MonetaryAccountSavings(savings) => savings.id,
|
MonetaryAccount::MonetaryAccountBank(bank) => bank.id,
|
||||||
};
|
MonetaryAccount::MonetaryAccountSavings(savings) => savings.id,
|
||||||
|
};
|
||||||
let mut url = format!(
|
|
||||||
"/v1/user/{}/monetary-account/{}/payment",
|
let mut url = format!(
|
||||||
client.user_id, account_id
|
"/v1/user/{}/monetary-account/{}/payment",
|
||||||
);
|
client.user_id, account_id
|
||||||
|
);
|
||||||
if let Some(to) = to {
|
|
||||||
url = format!("{}?newer_id={}", url, to);
|
if let Some(to) = to {
|
||||||
}
|
url = format!("{}?newer_id={}", url, to);
|
||||||
|
}
|
||||||
let mut all = Vec::new();
|
|
||||||
loop {
|
let mut all = Vec::new();
|
||||||
let (mut payments, pag) = next_page(&format!("{}{}", BASE_URL, url))?;
|
loop {
|
||||||
dbg!(&payments);
|
let (mut payments, pag) = next_page(&format!("{}{}", BASE_URL, url))?;
|
||||||
all.append(&mut payments);
|
dbg!(&payments);
|
||||||
dbg!(&pag);
|
all.append(&mut payments);
|
||||||
if let Some(Pagination {
|
dbg!(&pag);
|
||||||
older_url: Some(older_url),
|
if let Some(Pagination {
|
||||||
..
|
older_url: Some(older_url),
|
||||||
}) = pag
|
..
|
||||||
{
|
}) = pag
|
||||||
if let (Some(latest), Some(from)) = (all.last(), from) {
|
{
|
||||||
if latest.id <= from {
|
if let (Some(latest), Some(from)) = (all.last(), from) {
|
||||||
all.retain(|p| p.id >= from);
|
if latest.id <= from {
|
||||||
break;
|
all.retain(|p| p.id >= from);
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
url = older_url;
|
}
|
||||||
} else {
|
url = older_url;
|
||||||
break;
|
} else {
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
Ok(all)
|
}
|
||||||
}
|
Ok(all)
|
||||||
|
}
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct LabelMonetaryAccount {
|
#[derive(Deserialize, Debug)]
|
||||||
pub iban: Option<String>,
|
pub struct LabelMonetaryAccount {
|
||||||
pub display_name: String,
|
pub iban: Option<String>,
|
||||||
pub merchant_category_code: Option<String>,
|
pub display_name: String,
|
||||||
}
|
pub merchant_category_code: Option<String>,
|
||||||
|
}
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct Amount {
|
#[derive(Deserialize, Debug)]
|
||||||
pub value: String,
|
pub struct Amount {
|
||||||
pub currency: String,
|
pub value: String,
|
||||||
}
|
pub currency: String,
|
||||||
|
}
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[derive(Deserialize, Debug)]
|
||||||
struct PaymentPayment {
|
#[serde(rename_all = "PascalCase")]
|
||||||
payment: Payment,
|
struct PaymentPayment {
|
||||||
}
|
payment: Payment,
|
||||||
|
}
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct Payment {
|
#[derive(Deserialize, Debug)]
|
||||||
pub alias: LabelMonetaryAccount,
|
pub struct Payment {
|
||||||
pub counterparty_alias: LabelMonetaryAccount,
|
pub alias: LabelMonetaryAccount,
|
||||||
pub amount: Amount,
|
pub counterparty_alias: LabelMonetaryAccount,
|
||||||
pub balance_after_mutation: Amount,
|
pub amount: Amount,
|
||||||
pub created: String,
|
pub balance_after_mutation: Amount,
|
||||||
pub updated: String,
|
pub created: String,
|
||||||
pub description: String,
|
pub updated: String,
|
||||||
pub id: i64,
|
pub description: String,
|
||||||
pub monetary_account_id: i64,
|
pub id: i64,
|
||||||
#[serde(rename = "type")]
|
pub monetary_account_id: i64,
|
||||||
pub type_: String,
|
#[serde(rename = "type")]
|
||||||
pub sub_type: String,
|
pub type_: String,
|
||||||
}
|
pub sub_type: String,
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user