working example
This commit is contained in:
parent
aaf9781fe8
commit
e69bcfc23d
18 changed files with 1290 additions and 252 deletions
|
|
@ -1,11 +1,7 @@
|
|||
use clap::{Args, ArgGroup};
|
||||
use restson::{RestClient, RestPath, Response};
|
||||
use restson::RestClient;
|
||||
use anyhow::{Result, Error};
|
||||
use serde::{
|
||||
ser::{Serialize, Serializer, SerializeMap},
|
||||
Deserialize,
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
use std::str::{FromStr};
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
#[command(group(
|
||||
|
|
@ -14,10 +10,10 @@ use chrono::{DateTime, Utc};
|
|||
))]
|
||||
pub struct Authentication {
|
||||
#[arg(long)]
|
||||
access: Option<Token>,
|
||||
access: Option<Access>,
|
||||
|
||||
#[arg(long)]
|
||||
refresh: Option<Token>,
|
||||
refresh: Option<Refresh>,
|
||||
|
||||
#[arg(long)]
|
||||
email: Option<Email>,
|
||||
|
|
@ -26,13 +22,18 @@ pub struct Authentication {
|
|||
phone: Option<Phone>,
|
||||
}
|
||||
|
||||
fn bearer(mut client: RestClient, token: &str) -> Result<RestClient> {
|
||||
client.set_header("Authorization", &format!("Bearer {}", token))?;
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
impl Authentication {
|
||||
pub async fn apply(self, client: RestClient) -> Result<RestClient> {
|
||||
let client = match (self.access, self.refresh, self.email, self.phone) {
|
||||
(Some(v), None, None, None) => v.apply(client)?,
|
||||
(None, Some(v), None, None) => Tokens::authenticate(client, v).await?,
|
||||
(None, None, Some(v), None) => Tokens::authenticate(client, v).await?,
|
||||
(None, None, None, Some(v)) => Tokens::authenticate(client, v).await?,
|
||||
pub async fn apply(&self, client: RestClient) -> Result<RestClient> {
|
||||
let client = match (self.access.as_ref(), self.refresh.as_ref(), self.email.as_ref(), self.phone.as_ref()) {
|
||||
(Some(ref v), None, None, None) => v.apply(client)?,
|
||||
(None, Some(ref v), None, None) => v.apply(client).await?,
|
||||
(None, None, Some(ref v), None) => v.apply(client).await?,
|
||||
(None, None, None, Some(ref v)) => v.apply(client).await?,
|
||||
(None, None, None, None) => client,
|
||||
(a, b, c, d) => anyhow::bail!("invalid authentication: {} + {} + {} + {}", a.is_some(), b.is_some(), c.is_some(), d.is_some()),
|
||||
};
|
||||
|
|
@ -40,126 +41,87 @@ impl Authentication {
|
|||
}
|
||||
}
|
||||
|
||||
mod identifier {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Email;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Phone;
|
||||
}
|
||||
|
||||
trait Identifier: Clone {
|
||||
const NAME: &'static str;
|
||||
type Value: std::str::FromStr<Err=Self::Error> + std::fmt::Debug + Clone + serde::Serialize;
|
||||
type Error: std::error::Error + Send + Sync + 'static;
|
||||
}
|
||||
|
||||
impl Identifier for identifier::Email {
|
||||
const NAME: &'static str = "email";
|
||||
type Value = String;
|
||||
type Error = <String as std::str::FromStr>::Err;
|
||||
}
|
||||
|
||||
impl Identifier for identifier::Phone {
|
||||
const NAME: &'static str = "phone";
|
||||
type Value = String;
|
||||
type Error = <String as std::str::FromStr>::Err;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct WithPassword<I: Identifier> {
|
||||
value: I::Value,
|
||||
struct WithPassword {
|
||||
value: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
impl<I: Identifier> Serialize for WithPassword<I> {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
{
|
||||
let mut map = serializer.serialize_map(Some(2))?;
|
||||
map.serialize_entry(I::NAME, &self.value)?;
|
||||
map.serialize_entry("password", &self.password)?;
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Identifier> RestPath<()> for WithPassword<I> {
|
||||
fn get_path(_: ()) -> std::result::Result<String, restson::Error> {
|
||||
Ok(String::from("auth2/login"))
|
||||
}
|
||||
}
|
||||
|
||||
type Email = WithPassword<identifier::Email>;
|
||||
type Phone = WithPassword<identifier::Phone>;
|
||||
|
||||
impl<I: Identifier> std::str::FromStr for WithPassword<I> {
|
||||
type Err= Error;
|
||||
impl FromStr for WithPassword {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let password = match std::env::var("SPOND_PASSWORD") {
|
||||
Ok(password) => password,
|
||||
Err(_) => rpassword::prompt_password("Password: ")?,
|
||||
};
|
||||
let value = I::Value::from_str(s)?;
|
||||
let value = String::from_str(s)?;
|
||||
Ok(Self { value, password })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Tokens {
|
||||
#[serde(rename = "accessToken")]
|
||||
access: TokenWithExpiration,
|
||||
#[serde(rename = "refreshToken")]
|
||||
refresh: TokenWithExpiration,
|
||||
}
|
||||
|
||||
impl Tokens {
|
||||
async fn authenticate<R: serde::Serialize + RestPath<()>>(client: RestClient, request: R) -> Result<RestClient> {
|
||||
let tokens: Response<Self> = client.post_capture((), &request).await?;
|
||||
tokens.into_inner().apply(client)
|
||||
}
|
||||
|
||||
fn apply(self, client: RestClient) -> Result<RestClient> {
|
||||
println!("refresh: {self:?}");
|
||||
self.access.token.apply(client)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct TokenWithExpiration {
|
||||
token: Token,
|
||||
#[allow(unused)]
|
||||
expiration: DateTime<Utc>
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct Token(String);
|
||||
|
||||
impl Serialize for Token {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
{
|
||||
let mut map = serializer.serialize_map(Some(1))?;
|
||||
map.serialize_entry("token", &self.0)?;
|
||||
map.end()
|
||||
#[derive(Debug, Clone)]
|
||||
struct Email(WithPassword);
|
||||
impl Email {
|
||||
async fn apply(&self, client: RestClient) -> Result<RestClient> {
|
||||
let tokens = spond_api::authentication::email(&client, &self.0.value, &self.0.password).await?;
|
||||
bearer(client, tokens.access.token.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl Token {
|
||||
fn apply(self, mut client: RestClient) -> Result<RestClient> {
|
||||
client.set_header("Authorization", &format!("Bearer {}", self.0))?;
|
||||
Ok(client)
|
||||
}
|
||||
}
|
||||
|
||||
impl RestPath<()> for Token {
|
||||
fn get_path(_: ()) -> std::result::Result<String, restson::Error> {
|
||||
Ok(String::from("auth2/login/refresh"))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for Token {
|
||||
type Err = std::convert::Infallible;
|
||||
impl FromStr for Email {
|
||||
type Err= <WithPassword as FromStr>::Err;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(s.to_string()))
|
||||
Ok(Self(WithPassword::from_str(s)?))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Phone(WithPassword);
|
||||
impl Phone {
|
||||
async fn apply(&self, client: RestClient) -> Result<RestClient> {
|
||||
let tokens = spond_api::authentication::phone(&client, &self.0.value, &self.0.password).await?;
|
||||
bearer(client, tokens.access.token.as_ref())
|
||||
}
|
||||
}
|
||||
impl FromStr for Phone {
|
||||
type Err= <WithPassword as FromStr>::Err;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(WithPassword::from_str(s)?))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Access(String);
|
||||
|
||||
impl Access {
|
||||
fn apply(&self, client: RestClient) -> Result<RestClient> {
|
||||
bearer(client, &self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Access {
|
||||
type Err = std::convert::Infallible; // parsing a String never fails
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Access(s.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Refresh(String);
|
||||
|
||||
impl Refresh {
|
||||
async fn apply(&self, client: RestClient) -> Result<RestClient> {
|
||||
let tokens = spond_api::authentication::token(&client, &self.0).await?;
|
||||
bearer(client, tokens.access.token.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Refresh {
|
||||
type Err = std::convert::Infallible; // parsing a String never fails
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Refresh(s.to_string()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue