1
- use super :: types:: { Claims , JWT_KEY } ;
2
- use crate :: database:: {
3
- errors:: ParsingError ,
4
- types:: { Customers , RELATIONAL_DATABASE , Role } ,
1
+ use super :: {
2
+ siwe:: Siwe ,
3
+ types:: { Claims , JWT_KEY } ,
5
4
} ;
6
- use alloy:: primitives:: Address ;
5
+ use crate :: {
6
+ database:: {
7
+ errors:: ParsingError ,
8
+ types:: { Customers , RELATIONAL_DATABASE , Role } ,
9
+ } ,
10
+ eth_rpc:: types:: ETHEREUM_ENDPOINT ,
11
+ } ;
12
+ use alloy:: { primitives:: Address , providers:: ProviderBuilder } ;
7
13
use argon2:: { Argon2 , PasswordHash , PasswordVerifier } ;
8
14
use axum:: {
9
15
Json ,
@@ -12,14 +18,74 @@ use axum::{
12
18
} ;
13
19
use jwt_simple:: { algorithms:: MACLike , reexports:: coarsetime:: Duration } ;
14
20
use serde:: { Deserialize , Serialize } ;
21
+ use siwe:: { Message , VerificationError , VerificationOpts } ;
15
22
use thiserror:: Error ;
23
+ use time:: OffsetDateTime ;
16
24
17
25
#[ derive( Serialize , Deserialize , Clone , Debug ) ]
18
26
pub struct LoginRequest {
19
27
pub ( crate ) email : String ,
20
28
pub ( crate ) password : String ,
21
29
}
22
30
31
+ pub struct SiweLogin {
32
+ pub email : String ,
33
+ pub wallet : Option < String > ,
34
+ pub role : Role ,
35
+ pub nonce : Option < String > ,
36
+ }
37
+
38
+ #[ tracing:: instrument]
39
+ pub async fn user_login_siwe ( Json ( payload) : Json < Siwe > ) -> Result < impl IntoResponse , LoginError > {
40
+ let msg: Message = payload. message . parse ( ) ?;
41
+ let address = Address :: new ( msg. address ) ;
42
+
43
+ let customer = sqlx:: query_as!(
44
+ SiweLogin ,
45
+ r#"SELECT email, wallet, nonce, role as "role!: Role" FROM Customers where wallet = $1"# ,
46
+ address. to_string( ) ,
47
+ )
48
+ . fetch_optional ( RELATIONAL_DATABASE . get ( ) . unwrap ( ) )
49
+ . await ?
50
+ . ok_or_else ( || LoginError :: InvalidAddress ) ?;
51
+
52
+ let nonce = customer. nonce . ok_or_else ( || LoginError :: MissingNonce ) ?;
53
+
54
+ let rpc = ProviderBuilder :: new ( ) . on_http ( ETHEREUM_ENDPOINT [ 0 ] . as_str ( ) . parse ( ) . unwrap ( ) ) ;
55
+
56
+ let verification_opts = VerificationOpts {
57
+ domain : Some ( "Developer DAO Cloud" . parse ( ) . unwrap ( ) ) ,
58
+ nonce : Some ( nonce) ,
59
+ timestamp : Some ( OffsetDateTime :: now_utc ( ) ) ,
60
+ rpc_provider : Some ( rpc) ,
61
+ } ;
62
+
63
+ msg. verify ( & payload. signature , & verification_opts) . await ?;
64
+
65
+ let user_info = Claims {
66
+ role : customer. role ,
67
+ email : customer. email ,
68
+ wallet : Some ( address) ,
69
+ } ;
70
+ let claims = jwt_simple:: claims:: Claims :: with_custom_claims ( user_info, Duration :: from_hours ( 2 ) ) ;
71
+ let key = JWT_KEY . get ( ) . unwrap ( ) ;
72
+ let auth = key. authenticate ( claims) ?;
73
+
74
+ let mut headers = HeaderMap :: new ( ) ;
75
+ headers. insert (
76
+ SET_COOKIE ,
77
+ HeaderValue :: from_str ( & format ! ( "jwt={}" , auth) ) . unwrap ( ) ,
78
+ ) ;
79
+ headers. append ( SET_COOKIE , HeaderValue :: from_str ( "Secure" ) . unwrap ( ) ) ;
80
+ headers. append ( SET_COOKIE , HeaderValue :: from_str ( "HttpOnly" ) . unwrap ( ) ) ;
81
+ headers. append (
82
+ SET_COOKIE ,
83
+ HeaderValue :: from_str ( "SameSite=Strict" ) . unwrap ( ) ,
84
+ ) ;
85
+
86
+ Ok ( ( StatusCode :: OK , headers) )
87
+ }
88
+
23
89
#[ tracing:: instrument]
24
90
pub async fn user_login (
25
91
Json ( payload) : Json < LoginRequest > ,
@@ -71,6 +137,10 @@ pub async fn user_login(
71
137
72
138
#[ derive( Debug , Error ) ]
73
139
pub enum LoginError {
140
+ #[ error( transparent) ]
141
+ VerificationError ( #[ from] VerificationError ) ,
142
+ #[ error( "User did not generate nonce" ) ]
143
+ MissingNonce ,
74
144
#[ error( "The email or password you provided is invalid." ) ]
75
145
InvalidEmailOrPassword ,
76
146
#[ error( transparent) ]
@@ -85,6 +155,10 @@ pub enum LoginError {
85
155
AddressParsingError ( #[ from] ParsingError ) ,
86
156
#[ error( transparent) ]
87
157
BuilderResponseError ( #[ from] axum:: http:: Error ) ,
158
+ #[ error( "No account found for address" ) ]
159
+ InvalidAddress ,
160
+ #[ error( transparent) ]
161
+ ParseError ( #[ from] siwe:: ParseError ) ,
88
162
}
89
163
90
164
impl IntoResponse for LoginError {
0 commit comments