1
1
use std:: collections:: HashMap ;
2
+ use std:: time:: Instant ;
2
3
4
+ use axum:: extract:: Path ;
3
5
use axum:: { extract:: State , Json } ;
4
6
use axum_macros:: debug_handler;
7
+ use namada_sdk:: types:: string_encoding:: Format ;
5
8
use namada_sdk:: {
6
9
args:: InputAmount ,
7
10
rpc,
@@ -10,6 +13,7 @@ use namada_sdk::{
10
13
tx:: data:: ResultCode ,
11
14
types:: {
12
15
address:: Address ,
16
+ key:: { common, SigScheme } ,
13
17
masp:: { TransferSource , TransferTarget } ,
14
18
} ,
15
19
Namada ,
@@ -45,10 +49,39 @@ pub async fn faucet_settings(
45
49
46
50
pub async fn request_challenge (
47
51
State ( mut state) : State < FaucetState > ,
52
+ Path ( player_id) : Path < String > ,
48
53
) -> Result < Json < FaucetResponseDto > , ApiError > {
54
+ let is_player = match reqwest:: get ( format ! (
55
+ "https://{}/api/v1/player/exists/{}" ,
56
+ state. webserver_host, player_id
57
+ ) )
58
+ . await
59
+ . map ( |response| response. status ( ) . is_success ( ) )
60
+ {
61
+ Ok ( is_success) if is_success => true ,
62
+ _ => false ,
63
+ } ;
64
+ if !is_player {
65
+ return Err ( FaucetError :: NotPlayer ( player_id) . into ( ) ) ;
66
+ }
67
+
68
+ let now = Instant :: now ( ) ;
69
+ let too_many_requests = ' result: {
70
+ let Some ( last_request_instant) = state. last_requests . get ( & player_id) else {
71
+ break ' result false ;
72
+ } ;
73
+ let elapsed_request_time = now. duration_since ( * last_request_instant) ;
74
+ elapsed_request_time <= state. request_frequency
75
+ } ;
76
+
77
+ if too_many_requests {
78
+ return Err ( FaucetError :: TooManyRequests . into ( ) ) ;
79
+ }
80
+ state. last_requests . insert ( player_id. clone ( ) , now) ;
81
+
49
82
let faucet_request = state
50
83
. faucet_service
51
- . generate_faucet_request ( state. auth_key )
84
+ . generate_faucet_request ( state. auth_key , player_id )
52
85
. await ?;
53
86
let response = FaucetResponseDto :: from ( faucet_request) ;
54
87
@@ -66,6 +99,34 @@ pub async fn request_transfer(
66
99
return Err ( FaucetError :: InvalidWithdrawLimit ( state. withdraw_limit ) . into ( ) ) ;
67
100
}
68
101
102
+ let player_id_pk: common:: PublicKey = if let Ok ( pk) = payload. player_id . parse ( ) {
103
+ pk
104
+ } else {
105
+ return Err ( FaucetError :: InvalidPublicKey . into ( ) ) ;
106
+ } ;
107
+
108
+ let challenge_signature = if let Ok ( hex_decoded_sig) = hex:: decode ( payload. challenge_signature )
109
+ {
110
+ if let Ok ( sig) = common:: Signature :: decode_bytes ( & hex_decoded_sig) {
111
+ sig
112
+ } else {
113
+ return Err ( FaucetError :: InvalidSignature . into ( ) ) ;
114
+ }
115
+ } else {
116
+ return Err ( FaucetError :: InvalidSignature . into ( ) ) ;
117
+ } ;
118
+
119
+ if common:: SigScheme :: verify_signature (
120
+ & player_id_pk,
121
+ // NOTE: signing over the hex encoded challenge data
122
+ & payload. challenge . as_bytes ( ) ,
123
+ & challenge_signature,
124
+ )
125
+ . is_err ( )
126
+ {
127
+ return Err ( FaucetError :: InvalidSignature . into ( ) ) ;
128
+ }
129
+
69
130
let token_address = Address :: decode ( payload. transfer . token . clone ( ) ) ;
70
131
let token_address = if let Ok ( address) = token_address {
71
132
address
@@ -82,10 +143,13 @@ pub async fn request_transfer(
82
143
if state. faucet_repo . contains ( & payload. challenge ) . await {
83
144
return Err ( FaucetError :: DuplicateChallenge . into ( ) ) ;
84
145
}
85
- let is_valid_proof =
86
- state
87
- . faucet_service
88
- . verify_tag ( & auth_key, & payload. challenge , & payload. tag ) ;
146
+
147
+ let is_valid_proof = state. faucet_service . verify_tag (
148
+ & auth_key,
149
+ & payload. challenge ,
150
+ & payload. player_id ,
151
+ & payload. tag ,
152
+ ) ;
89
153
if !is_valid_proof {
90
154
return Err ( FaucetError :: InvalidProof . into ( ) ) ;
91
155
}
0 commit comments