use std::sync::Arc;
use jaws::key::SerializeJWK;
use crate::protocol::{request::Key, AcmeError, Request, Response, Url};
use crate::schema::{
self,
account::{Contacts, CreateAccount, ExternalAccountBindingRequest},
directory::Directory,
};
use signature::Keypair;
use super::{
order::{Order, OrderBuilder},
Provider,
};
#[derive(Debug)]
pub struct Account<K> {
provider: Provider,
key: Arc<K>,
data: schema::Account,
url: Url,
}
impl<K> Account<K> {
fn new(provider: Provider, key: Arc<K>, data: schema::Account, url: Url) -> Self {
Self {
provider,
key,
data,
url,
}
}
#[inline]
pub(crate) fn client(&self) -> &super::client::Client {
self.provider.client()
}
#[inline]
pub(crate) fn directory(&self) -> &Directory {
self.provider.directory()
}
pub async fn refresh(&mut self) -> Result<(), AcmeError>
where
K: jaws::algorithms::TokenSigner<jaws::SignatureBytes>,
{
let response: Response<schema::Account> = self
.client()
.execute(Request::get(self.url().clone(), self.request_key()))
.await?;
self.data = response.into_inner();
Ok(())
}
pub fn update(&mut self) -> UpdateAccount<'_, K> {
UpdateAccount::new(self)
}
pub fn data(&self) -> &schema::Account {
&self.data
}
pub fn key(&self) -> Arc<K> {
self.key.clone()
}
pub fn url(&self) -> &Url {
&self.url
}
pub(crate) fn request_key(&self) -> impl Into<Key<K>> {
(self.key(), self.url.clone())
}
pub fn order(&self) -> OrderBuilder<'_, K> {
OrderBuilder::new(self)
}
pub async fn orders(&self, limit: Option<usize>) -> Result<Vec<Order<'_, K>>, AcmeError>
where
K: jaws::algorithms::TokenSigner<jaws::SignatureBytes>,
{
let orders = super::order::list(self, limit).await?;
Ok(orders)
}
}
pub struct AccountBuilder<K> {
contact: Contacts,
terms_of_service_agreed: Option<bool>,
only_return_existing: Option<bool>,
external_account_binding: Option<ExternalAccountBindingRequest>,
key: Arc<K>,
provider: Provider,
}
impl<K> AccountBuilder<K>
where
K: Clone,
{
pub(crate) fn new(provider: Provider, key: Arc<K>) -> Self {
AccountBuilder {
contact: Default::default(),
terms_of_service_agreed: None,
only_return_existing: None,
external_account_binding: None,
key,
provider,
}
}
pub fn external_account(mut self, binding: ExternalAccountBindingRequest) -> AccountBuilder<K> {
self.external_account_binding = Some(binding);
self
}
pub fn agree_to_terms_of_service(mut self) -> Self {
self.terms_of_service_agreed = Some(true);
self
}
pub fn add_contact_url(mut self, url: Url) -> Self {
self.contact.add_contact_url(url);
self
}
pub fn add_contact_email(
mut self,
email: &str,
) -> Result<Self, <reqwest::Url as std::str::FromStr>::Err> {
self.contact.add_contact_email(email)?;
Ok(self)
}
pub fn must_exist(mut self) -> Self {
self.only_return_existing = Some(true);
self
}
pub async fn create(self) -> Result<Account<K>, AcmeError>
where
K: Keypair,
K::VerifyingKey: SerializeJWK,
K: jaws::algorithms::TokenSigner<jaws::SignatureBytes>,
{
let url = self.provider.directory().new_account.clone();
let public_key = self.key.verifying_key();
let payload = CreateAccount {
contact: self.contact,
terms_of_service_agreed: self.terms_of_service_agreed,
only_return_existing: self.only_return_existing,
external_account_binding: self
.external_account_binding
.map(|binding| binding.token(&public_key, url.clone())),
};
let account: Response<crate::schema::Account> = self
.provider
.client()
.execute(Request::post(payload, url, self.key.clone()))
.await?;
let account_url = account
.location()
.ok_or_else(|| AcmeError::MissingData("account id URL"))?;
Ok(Account::new(
self.provider,
self.key,
account.into_inner(),
account_url,
))
}
pub async fn get(mut self) -> Result<Account<K>, AcmeError>
where
K: Keypair,
K::VerifyingKey: SerializeJWK,
K: jaws::algorithms::TokenSigner<jaws::SignatureBytes>,
{
self.only_return_existing = Some(true);
self.create().await
}
}
#[derive(Debug)]
pub struct UpdateAccount<'a, K> {
contact: Contacts,
account: &'a mut Account<K>,
}
impl<'a, K> UpdateAccount<'a, K> {
fn new(account: &'a mut Account<K>) -> Self {
UpdateAccount {
contact: account.data().contact.clone(),
account,
}
}
pub fn contacts(&mut self) -> &mut Contacts {
&mut self.contact
}
pub async fn update(self) -> Result<(), AcmeError>
where
K: jaws::algorithms::TokenSigner<jaws::SignatureBytes>,
{
let url = self.account.url().clone();
let key = self.account.key.clone();
let request = crate::schema::account::UpdateAccount::new(self.contact);
let account: Response<crate::schema::Account> = self
.account
.client()
.execute(Request::post(request, url, key))
.await?;
let account_url = account
.location()
.ok_or_else(|| AcmeError::MissingData("account id URL"))?;
assert_eq!(account_url, self.account.url);
self.account.data = account.into_inner();
Ok(())
}
}