@@ -21,14 +21,15 @@ import (
2121 tm2Client "github.com/gnolang/faucet/client/http"
2222 "github.com/gnolang/faucet/config"
2323 "github.com/gnolang/faucet/estimate/static"
24- "github.com/gnolang/gno/tm2/pkg/crypto"
25- "github.com/gnolang/gno/tm2/pkg/std"
2624 "github.com/peterbourgon/ff/v3"
2725 "github.com/peterbourgon/ff/v3/fftoml"
2826 "github.com/redis/go-redis/v9"
2927 "go.uber.org/zap"
3028 "golang.org/x/sync/errgroup"
3129
30+ "github.com/gnolang/gno/tm2/pkg/crypto"
31+ "github.com/gnolang/gno/tm2/pkg/std"
32+
3233 "github.com/peterbourgon/ff/v3/ffcli"
3334)
3435
@@ -175,7 +176,7 @@ func registerFlags(fs *flag.FlagSet, c *rootCfg) {
175176 fs .StringVar (
176177 & c .redisURL ,
177178 "redis-url" ,
178- "redis://user:pass@ 127.0.0.1:6379" ,
179+ "redis://127.0.0.1:6379" ,
179180 "redis connection string" ,
180181 )
181182
@@ -221,12 +222,10 @@ func execMain(cfg *rootCfg) error {
221222 cli := tm2Client .NewClient (cfg .remote )
222223
223224 // Prepare the middlewares
224- middlewares := []faucet.Middleware {
225- prepareFundMiddleware (cli , fundLimit ),
226- }
225+ var middlewares []faucet.Middleware
227226
228- // TODO temporary
229227 if len (cfg .allowedTokens ) != 0 {
228+ // TODO temporary for testing purpose without redis
230229 middlewares = append (middlewares , prepareTokenListMiddleware (cfg .allowedTokens ))
231230 } else {
232231 redisOpts , err := redis .ParseURL (cfg .redisURL )
@@ -235,8 +234,21 @@ func execMain(cfg *rootCfg) error {
235234 }
236235 redisClient := redis .NewClient (redisOpts )
237236
237+ // Add /?addr={gnoAddr} endpoint as a middleware in first position.
238+ // If the request contains an `addr` field in the query string, it will fetch
239+ // the email from redis and returns it, without calling the other handlers
240+ // or middlewares behind it.
241+ //
242+ // Why not a faucet.Handler option instead of a middleware ?
243+ // This is not achievable with a real handler passed in the faucet options
244+ // because it inherits the other middlewares which requires a
245+ // faucet.Request or else fails with Bad Request.
246+ middlewares = append (middlewares , getEmail (redisClient ))
247+
238248 middlewares = append (middlewares , prepareTokenMiddleware (redisClient ))
239249 }
250+ // Call prepareFundMiddleware last to avoid funding users with invalid tokens
251+ middlewares = append (middlewares , prepareFundMiddleware (cli , fundLimit ))
240252
241253 // Create a new faucet with
242254 // static gas estimation
@@ -464,14 +476,74 @@ func prepareTokenMiddleware(redisClient *redis.Client) faucet.Middleware {
464476 return
465477 }
466478
467- // Continue with serving the faucet request
468- next .ServeHTTP (w , r )
469-
470479 _ , err = redisClient .HSet (r .Context (), "TOKEN:" + token , "used" , "true" ).Result ()
471480 if err != nil {
472481 http .Error (w , "Unable to lock token" , http .StatusInternalServerError )
473482 return
474483 }
484+
485+ // Parse the request to extract the address
486+ // XXX copied from prepareFundMiddleware, don't have time to refactor without unit tests!
487+ var request faucet.Request
488+ body , err := io .ReadAll (r .Body )
489+ if err != nil {
490+ http .Error (w , "Error reading request body" , http .StatusInternalServerError )
491+ return
492+ }
493+ // Close the original body
494+ if err := r .Body .Close (); err != nil {
495+ http .Error (w , "Error closing request body" , http .StatusInternalServerError )
496+ return
497+ }
498+ // Create a new ReadCloser from the read bytes
499+ // so that future middleware will be able to read
500+ r .Body = io .NopCloser (bytes .NewReader (body ))
501+ // Decode the original request
502+ if err := json .NewDecoder (bytes .NewBuffer (body )).Decode (& request ); err != nil {
503+ http .Error (w , "Invalid request" , http .StatusBadRequest )
504+ return
505+ }
506+
507+ // Read email
508+ email , err := redisClient .HGet (r .Context (), "TOKEN:" + token , "email" ).Result ()
509+ if err != nil {
510+ http .Error (w , "Unable to read token email" , http .StatusInternalServerError )
511+ return
512+ }
513+ // Store gno adress, email and token, so they are retrievable via the
514+ // getEmail middleware.
515+ err = redisClient .HSet (r .Context (), "GNO:" + request .To , "email" , email , "token" , token ).Err ()
516+ if err != nil {
517+ http .Error (w , "Unable to set gno address and email" , http .StatusInternalServerError )
518+ return
519+ }
520+
521+ // Continue with serving the faucet request
522+ next .ServeHTTP (w , r )
523+ })
524+ }
525+ }
526+
527+ // getEmail detects the presence of `addr` form value, if present returns the
528+ // corresponding hashset from redis and stops the request.
529+ func getEmail (redisClient * redis.Client ) faucet.Middleware {
530+ return func (next http.Handler ) http.Handler {
531+ return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
532+ addr := r .FormValue ("addr" )
533+ if addr != "" {
534+ res := redisClient .HGetAll (r .Context (), "GNO:" + addr )
535+ if res .Err () != nil {
536+ http .Error (w , "Unable to get gno address" , http .StatusInternalServerError )
537+ return
538+ }
539+ err := json .NewEncoder (w ).Encode (res .Val ())
540+ if err != nil {
541+ http .Error (w , "Unable to encode gno address values" , http .StatusInternalServerError )
542+ return
543+ }
544+ return
545+ }
546+ next .ServeHTTP (w , r )
475547 })
476548 }
477549}
0 commit comments