alibi_plugins/siwe/
config.rs1use alibi_core::AuthResponse;
2use async_trait::async_trait;
3use rand::RngExt;
4use rand::distr::Alphanumeric;
5use serde::{Deserialize, Serialize};
6use std::fmt;
7use std::sync::Arc;
8
9#[derive(Debug)]
15pub enum SiweCallbackError {
16 Api(AuthResponse),
17 Failed(String),
18 Unknown,
19}
20
21impl fmt::Display for SiweCallbackError {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 match self {
24 Self::Api(response) => {
25 write!(f, "SIWE callback rejected with HTTP {}", response.status)
26 }
27 Self::Failed(message) => f.write_str(message),
28 Self::Unknown => f.write_str("Unknown error"),
29 }
30 }
31}
32
33impl std::error::Error for SiweCallbackError {}
34
35pub type SiweCallbackResult<T> = Result<T, SiweCallbackError>;
36
37#[async_trait]
38pub trait SiweNonceProvider: Send + Sync {
39 async fn get_nonce(&self) -> SiweCallbackResult<String>;
41}
42
43#[derive(Debug, Clone, Copy, Default)]
45pub struct RandomSiweNonce;
46
47#[async_trait]
48impl SiweNonceProvider for RandomSiweNonce {
49 async fn get_nonce(&self) -> SiweCallbackResult<String> {
50 Ok(rand::rng()
51 .sample_iter(&Alphanumeric)
52 .take(32)
53 .map(char::from)
54 .collect())
55 }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59pub struct CacaoHeader {
60 pub t: String,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64pub struct CacaoPayload {
65 pub domain: String,
66 pub aud: String,
67 pub nonce: String,
68 pub iss: String,
69 pub version: String,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73pub struct CacaoSignature {
74 pub t: String,
75 pub s: String,
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
81pub struct Cacao {
82 pub h: CacaoHeader,
83 pub p: CacaoPayload,
84 pub s: CacaoSignature,
85}
86
87#[derive(Debug, Clone, PartialEq)]
88pub struct SiweVerification {
89 pub message: String,
90 pub signature: String,
91 pub address: String,
93 pub chain_id: f64,
95 pub cacao: Cacao,
96}
97
98#[async_trait]
99pub trait SiweVerifier: Send + Sync {
100 async fn verify_message(&self, input: SiweVerification) -> SiweCallbackResult<bool>;
103}
104
105#[derive(Debug, Clone, Default, PartialEq, Eq)]
106pub struct EnsProfile {
107 pub name: Option<String>,
108 pub avatar: Option<String>,
109}
110
111#[async_trait]
112pub trait EnsLookup: Send + Sync {
113 async fn lookup(&self, wallet_address: &str) -> SiweCallbackResult<EnsProfile>;
114}
115
116#[derive(Clone)]
119pub struct SiweConfig {
120 pub domain: String,
121 pub nonce_provider: Arc<dyn SiweNonceProvider>,
122 pub verifier: Arc<dyn SiweVerifier>,
123 pub email_domain_name: Option<String>,
124 pub anonymous: bool,
127 pub ens_lookup: Option<Arc<dyn EnsLookup>>,
128}
129
130impl SiweConfig {
131 #[must_use]
132 pub fn new(
133 domain: impl Into<String>,
134 nonce_provider: Arc<dyn SiweNonceProvider>,
135 verifier: Arc<dyn SiweVerifier>,
136 ) -> Self {
137 Self {
138 domain: domain.into(),
139 nonce_provider,
140 verifier,
141 email_domain_name: None,
142 anonymous: true,
143 ens_lookup: None,
144 }
145 }
146}
147
148impl fmt::Debug for SiweConfig {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 f.debug_struct("SiweConfig")
151 .field("domain", &self.domain)
152 .field("email_domain_name", &self.email_domain_name)
153 .field("anonymous", &self.anonymous)
154 .field("has_ens_lookup", &self.ens_lookup.is_some())
155 .finish_non_exhaustive()
156 }
157}