use alibi_core::AuthResponse;
use async_trait::async_trait;
use rand::RngExt;
use rand::distr::Alphanumeric;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::sync::Arc;
#[derive(Debug)]
pub enum SiweCallbackError {
Api(AuthResponse),
Failed(String),
Unknown,
}
impl fmt::Display for SiweCallbackError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Api(response) => {
write!(f, "SIWE callback rejected with HTTP {}", response.status)
}
Self::Failed(message) => f.write_str(message),
Self::Unknown => f.write_str("Unknown error"),
}
}
}
impl std::error::Error for SiweCallbackError {}
pub type SiweCallbackResult<T> = Result<T, SiweCallbackError>;
#[async_trait]
pub trait SiweNonceProvider: Send + Sync {
async fn get_nonce(&self) -> SiweCallbackResult<String>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct RandomSiweNonce;
#[async_trait]
impl SiweNonceProvider for RandomSiweNonce {
async fn get_nonce(&self) -> SiweCallbackResult<String> {
Ok(rand::rng()
.sample_iter(&Alphanumeric)
.take(32)
.map(char::from)
.collect())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CacaoHeader {
pub t: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CacaoPayload {
pub domain: String,
pub aud: String,
pub nonce: String,
pub iss: String,
pub version: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CacaoSignature {
pub t: String,
pub s: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Cacao {
pub h: CacaoHeader,
pub p: CacaoPayload,
pub s: CacaoSignature,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SiweVerification {
pub message: String,
pub signature: String,
pub address: String,
pub chain_id: f64,
pub cacao: Cacao,
}
#[async_trait]
pub trait SiweVerifier: Send + Sync {
async fn verify_message(&self, input: SiweVerification) -> SiweCallbackResult<bool>;
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct EnsProfile {
pub name: Option<String>,
pub avatar: Option<String>,
}
#[async_trait]
pub trait EnsLookup: Send + Sync {
async fn lookup(&self, wallet_address: &str) -> SiweCallbackResult<EnsProfile>;
}
#[derive(Clone)]
pub struct SiweConfig {
pub domain: String,
pub nonce_provider: Arc<dyn SiweNonceProvider>,
pub verifier: Arc<dyn SiweVerifier>,
pub email_domain_name: Option<String>,
pub anonymous: bool,
pub ens_lookup: Option<Arc<dyn EnsLookup>>,
}
impl SiweConfig {
#[must_use]
pub fn new(
domain: impl Into<String>,
nonce_provider: Arc<dyn SiweNonceProvider>,
verifier: Arc<dyn SiweVerifier>,
) -> Self {
Self {
domain: domain.into(),
nonce_provider,
verifier,
email_domain_name: None,
anonymous: true,
ens_lookup: None,
}
}
}
impl fmt::Debug for SiweConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SiweConfig")
.field("domain", &self.domain)
.field("email_domain_name", &self.email_domain_name)
.field("anonymous", &self.anonymous)
.field("has_ens_lookup", &self.ens_lookup.is_some())
.finish_non_exhaustive()
}
}