Skip to main content

openauth_plugins/siwe/
types.rs

1use std::future::Future;
2use std::pin::Pin;
3use std::sync::Arc;
4
5use openauth_core::error::OpenAuthError;
6use serde::{Deserialize, Serialize};
7use time::OffsetDateTime;
8
9use super::schema::SiweSchemaOptions;
10
11type BoxFuture<T> = Pin<Box<dyn Future<Output = Result<T, OpenAuthError>> + Send>>;
12
13pub type GetNonce = Arc<dyn Fn() -> BoxFuture<String> + Send + Sync>;
14pub type VerifyMessage = Arc<dyn Fn(SiweVerifyMessageArgs) -> BoxFuture<bool> + Send + Sync>;
15pub type EnsLookup = Arc<dyn Fn(EnsLookupArgs) -> BoxFuture<Option<EnsLookupResult>> + Send + Sync>;
16
17#[derive(Clone)]
18pub struct SiweOptions {
19    pub(crate) domain: String,
20    pub(crate) email_domain_name: Option<String>,
21    pub(crate) anonymous: bool,
22    pub(crate) get_nonce: GetNonce,
23    pub(crate) verify_message: VerifyMessage,
24    pub(crate) ens_lookup: Option<EnsLookup>,
25    pub(crate) schema: SiweSchemaOptions,
26}
27
28impl SiweOptions {
29    pub fn new<G, GFut, V, VFut>(domain: impl Into<String>, get_nonce: G, verify_message: V) -> Self
30    where
31        G: Fn() -> GFut + Send + Sync + 'static,
32        GFut: Future<Output = Result<String, OpenAuthError>> + Send + 'static,
33        V: Fn(SiweVerifyMessageArgs) -> VFut + Send + Sync + 'static,
34        VFut: Future<Output = Result<bool, OpenAuthError>> + Send + 'static,
35    {
36        Self {
37            domain: domain.into(),
38            email_domain_name: None,
39            anonymous: true,
40            get_nonce: Arc::new(move || Box::pin(get_nonce())),
41            verify_message: Arc::new(move |args| Box::pin(verify_message(args))),
42            ens_lookup: None,
43            schema: SiweSchemaOptions::new(),
44        }
45    }
46
47    #[must_use]
48    pub fn email_domain_name(mut self, domain: impl Into<String>) -> Self {
49        self.email_domain_name = Some(domain.into());
50        self
51    }
52
53    #[must_use]
54    pub fn anonymous(mut self, anonymous: bool) -> Self {
55        self.anonymous = anonymous;
56        self
57    }
58
59    #[must_use]
60    pub fn ens_lookup<E, EFut>(mut self, ens_lookup: E) -> Self
61    where
62        E: Fn(EnsLookupArgs) -> EFut + Send + Sync + 'static,
63        EFut: Future<Output = Result<Option<EnsLookupResult>, OpenAuthError>> + Send + 'static,
64    {
65        self.ens_lookup = Some(Arc::new(move |args| Box::pin(ens_lookup(args))));
66        self
67    }
68
69    #[must_use]
70    pub fn schema(mut self, schema: SiweSchemaOptions) -> Self {
71        self.schema = schema;
72        self
73    }
74
75    pub(crate) fn schema_options(&self) -> &SiweSchemaOptions {
76        &self.schema
77    }
78
79    pub(crate) fn validate(&self) -> Result<(), OpenAuthError> {
80        if self.domain.trim().is_empty() {
81            return Err(OpenAuthError::InvalidConfig(
82                "siwe domain cannot be empty".to_owned(),
83            ));
84        }
85        Ok(())
86    }
87
88    pub(crate) fn metadata(&self) -> serde_json::Value {
89        let mut metadata = serde_json::json!({
90            "domain": self.domain,
91            "anonymous": self.anonymous,
92            "schema": self.schema.metadata(),
93        });
94        if let Some(email_domain_name) = &self.email_domain_name {
95            metadata["emailDomainName"] = serde_json::Value::String(email_domain_name.clone());
96        }
97        metadata
98    }
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
102pub struct WalletAddress {
103    pub id: String,
104    pub user_id: String,
105    pub address: String,
106    pub chain_id: i64,
107    pub is_primary: bool,
108    pub created_at: OffsetDateTime,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
112pub struct SiweVerifyMessageArgs {
113    pub message: String,
114    pub signature: String,
115    pub address: String,
116    pub chain_id: i64,
117    pub cacao: Cacao,
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
121pub struct Cacao {
122    pub h: CacaoHeader,
123    pub p: CacaoPayload,
124    pub s: CacaoSignature,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
128pub struct CacaoHeader {
129    pub t: String,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
133pub struct CacaoPayload {
134    pub domain: String,
135    pub aud: String,
136    pub nonce: String,
137    pub iss: String,
138    pub version: Option<String>,
139    pub iat: Option<String>,
140    pub nbf: Option<String>,
141    pub exp: Option<String>,
142    pub statement: Option<String>,
143    pub request_id: Option<String>,
144    pub resources: Option<Vec<String>>,
145    pub r#type: Option<String>,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
149pub struct CacaoSignature {
150    pub t: String,
151    pub s: String,
152    pub m: Option<String>,
153}
154
155#[derive(Debug, Clone, PartialEq, Eq)]
156pub struct EnsLookupArgs {
157    pub wallet_address: String,
158}
159
160#[derive(Debug, Clone, PartialEq, Eq)]
161pub struct EnsLookupResult {
162    pub name: String,
163    pub avatar: String,
164}
165
166#[derive(Debug, Deserialize)]
167#[serde(rename_all = "camelCase")]
168pub(crate) struct NonceRequest {
169    pub wallet_address: String,
170    #[serde(default = "default_chain_id")]
171    pub chain_id: i64,
172}
173
174#[derive(Debug, Deserialize)]
175#[serde(rename_all = "camelCase")]
176pub(crate) struct VerifyRequest {
177    pub message: String,
178    pub signature: String,
179    pub wallet_address: String,
180    #[serde(default = "default_chain_id")]
181    pub chain_id: i64,
182    pub email: Option<String>,
183}
184
185fn default_chain_id() -> i64 {
186    1
187}