use std::ops::Deref;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Error as SerdeError;
use crate::{account::{Address, EIP1271Signature, PersonalSignature, EphemeralPayload, PERSONAL_SIGNATURE_SIZE, DecodeHexError}};
static SIGNER: &str = "SIGNER";
static ECDSA_EPHEMERAL: &str = "ECDSA_EPHEMERAL";
static ECDSA_SIGNED_ENTITY: &str = "ECDSA_SIGNED_ENTITY";
static ECDSA_EIP_1654_EPHEMERAL: &str = "ECDSA_EIP_1654_EPHEMERAL";
static ECDSA_EIP_1654_SIGNED_ENTITY: &str = "ECDSA_EIP_1654_SIGNED_ENTITY";
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
#[serde(tag = "type")]
pub enum AuthLink {
#[serde(rename = "SIGNER")]
Signer{
payload: Address,
signature: String,
},
#[serde(rename = "ECDSA_EPHEMERAL")]
EcdsaPersonalEphemeral {
payload: EphemeralPayload,
signature: PersonalSignature,
},
#[serde(rename = "ECDSA_SIGNED_ENTITY")]
EcdsaPersonalSignedEntity {
payload: String,
signature: PersonalSignature,
},
#[serde(rename = "ECDSA_EIP_1654_EPHEMERAL")]
EcdsaEip1654Ephemeral {
payload: EphemeralPayload,
signature: EIP1271Signature,
},
#[serde(rename = "ECDSA_EIP_1654_SIGNED_ENTITY")]
EcdsaEip1654SignedEntity {
payload: String,
signature: EIP1271Signature,
},
}
impl AuthLink {
pub fn kind(&self) -> &str {
match self {
AuthLink::Signer{ .. } => SIGNER,
AuthLink::EcdsaPersonalEphemeral { .. } => ECDSA_EPHEMERAL,
AuthLink::EcdsaPersonalSignedEntity { .. } => ECDSA_SIGNED_ENTITY,
AuthLink::EcdsaEip1654Ephemeral { .. } => ECDSA_EIP_1654_EPHEMERAL,
AuthLink::EcdsaEip1654SignedEntity { .. } => ECDSA_EIP_1654_SIGNED_ENTITY,
}
}
pub fn parse(value: &str) -> Result<AuthLink, SerdeError> {
serde_json::from_str::<AuthLink>(value)
}
pub fn signer(payload: Address) -> Self {
AuthLink::Signer{ payload, signature: String::with_capacity(0) }
}
}
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
#[serde(transparent)]
pub struct AuthChain(Vec<AuthLink>);
impl Deref for AuthChain {
type Target = Vec<AuthLink>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<Vec<AuthLink>> for AuthChain {
fn from(links: Vec<AuthLink>) -> Self {
AuthChain(links)
}
}
impl AuthChain {
pub fn simple<P, T>(signer: Address, payload: P, signature: T) -> Result<Self, DecodeHexError> where P: AsRef<str>, T: AsRef<str> {
let signature = signature.as_ref();
let payload = payload.as_ref().to_string();
let entity = if signature.len() == (PERSONAL_SIGNATURE_SIZE * 2) + 2 {
let signature = PersonalSignature::try_from(signature)?;
AuthLink::EcdsaPersonalSignedEntity { payload, signature }
} else {
let signature = EIP1271Signature::try_from(signature)?;
AuthLink::EcdsaEip1654SignedEntity { payload, signature }
};
Ok(AuthChain::from(vec![
AuthLink::signer(signer),
entity
]))
}
pub fn from_json<V>(value: V) -> Result<AuthChain, SerdeError> where V: AsRef<str> {
serde_json::from_str::<AuthChain>(value.as_ref())
}
pub fn from_json_links(value: Vec<&str>) -> Result<AuthChain, SerdeError> {
let links = value
.iter()
.map(|link| {
let link = serde_json::from_str::<AuthLink>(link)?;
Ok(link)
})
.collect::<Result<Vec<AuthLink>, SerdeError>>()?;
Ok(AuthChain::from(links))
}
pub fn owner(&self) -> Option<&Address> {
match (*self).first() {
Some(AuthLink::Signer{ payload, .. }) => Some(payload),
_ => None,
}
}
pub fn is_expired(&self) -> bool {
let now = &Utc::now();
self.iter().any(|link| match link {
AuthLink::EcdsaPersonalEphemeral { payload, .. } => payload.is_expired_at(now),
AuthLink::EcdsaEip1654Ephemeral { payload, .. } => payload.is_expired_at(now),
_ => false,
})
}
pub fn is_expired_at(&self, time: &DateTime<Utc>) -> bool {
self.iter().any(|link| match link {
AuthLink::EcdsaPersonalEphemeral { payload, .. } => payload.is_expired_at(time),
AuthLink::EcdsaEip1654Ephemeral { payload, .. } => payload.is_expired_at(time),
_ => false,
})
}
}