Skip to content

Commit

Permalink
feat: faucet github middleware with coolDown
Browse files Browse the repository at this point in the history
  • Loading branch information
Villaquiranm committed Feb 22, 2025
1 parent 85b3c0b commit c8a8456
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 2 deletions.
36 changes: 36 additions & 0 deletions contribs/gnofaucet/cooldown.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

import (
"sync"
"time"
)

// CooldownLimiter is a Limiter using an in-memory map
type CooldownLimiter struct {
cooldowns map[string]time.Time
mu sync.Mutex
cooldownTime time.Duration
}

// NewCooldownLimiter initializes a Cooldown Limiter with a given duration
func NewCooldownLimiter(cooldown time.Duration) *CooldownLimiter {
return &CooldownLimiter{
cooldowns: make(map[string]time.Time),
cooldownTime: cooldown,
}

Check warning on line 20 in contribs/gnofaucet/cooldown.go

View check run for this annotation

Codecov / codecov/patch

contribs/gnofaucet/cooldown.go#L16-L20

Added lines #L16 - L20 were not covered by tests
}

// CheckCooldown checks if a user is eligible for a reward claim
func (rl *CooldownLimiter) CheckCooldown(ghLogin string) bool {
rl.mu.Lock()
defer rl.mu.Unlock()

if lastClaim, found := rl.cooldowns[ghLogin]; found {
if time.Since(lastClaim) < rl.cooldownTime {
return false // Deny claim if within cooldown period
}

Check warning on line 31 in contribs/gnofaucet/cooldown.go

View check run for this annotation

Codecov / codecov/patch

contribs/gnofaucet/cooldown.go#L24-L31

Added lines #L24 - L31 were not covered by tests
}

rl.cooldowns[ghLogin] = time.Now()
return true

Check warning on line 35 in contribs/gnofaucet/cooldown.go

View check run for this annotation

Codecov / codecov/patch

contribs/gnofaucet/cooldown.go#L34-L35

Added lines #L34 - L35 were not covered by tests
}
88 changes: 88 additions & 0 deletions contribs/gnofaucet/gh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package main

import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"

"github.com/google/go-github/v64/github"
)

func getGithubMiddleware(clientID, secret string, cooldown time.Duration) func(next http.Handler) http.Handler {
coolDownLimiter := NewCooldownLimiter(cooldown)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
// Check if the captcha is enabled
if secret == "" || clientID == "" {
// Continue with serving the faucet request
next.ServeHTTP(w, r)

return
}

Check warning on line 24 in contribs/gnofaucet/gh.go

View check run for this annotation

Codecov / codecov/patch

contribs/gnofaucet/gh.go#L13-L24

Added lines #L13 - L24 were not covered by tests

code := r.URL.Query().Get("code")
if code == "" {
http.Error(w, "missing code", http.StatusBadRequest)
return
}

Check warning on line 30 in contribs/gnofaucet/gh.go

View check run for this annotation

Codecov / codecov/patch

contribs/gnofaucet/gh.go#L26-L30

Added lines #L26 - L30 were not covered by tests

res, err := exchangeCodeForToken(secret, clientID, code)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

Check warning on line 36 in contribs/gnofaucet/gh.go

View check run for this annotation

Codecov / codecov/patch

contribs/gnofaucet/gh.go#L32-L36

Added lines #L32 - L36 were not covered by tests

client := github.NewClient(http.DefaultClient).WithAuthToken(res.AccessToken)
user, _, err := client.Users.Get(r.Context(), "")
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

Check warning on line 43 in contribs/gnofaucet/gh.go

View check run for this annotation

Codecov / codecov/patch

contribs/gnofaucet/gh.go#L38-L43

Added lines #L38 - L43 were not covered by tests
// Just check if given account have asked for faucet before the cooldown period
if !coolDownLimiter.CheckCooldown(user.GetLogin()) {
http.Error(w, "user is on cooldown", http.StatusTooManyRequests)
return
}

Check warning on line 48 in contribs/gnofaucet/gh.go

View check run for this annotation

Codecov / codecov/patch

contribs/gnofaucet/gh.go#L45-L48

Added lines #L45 - L48 were not covered by tests

// Possibility to have more conditions like accountAge, commits, pullRequest etc

next.ServeHTTP(w, r)

Check warning on line 52 in contribs/gnofaucet/gh.go

View check run for this annotation

Codecov / codecov/patch

contribs/gnofaucet/gh.go#L52

Added line #L52 was not covered by tests
},
)
}
}

type GitHubTokenResponse struct {
AccessToken string `json:"access_token"`
}

func exchangeCodeForToken(secret, clientID, code string) (*GitHubTokenResponse, error) {
url := "https://github.com/login/oauth/access_token"
body := fmt.Sprintf("client_id=%s&client_secret=%s&code=%s", clientID, secret, code)
req, err := http.NewRequest("POST", url, strings.NewReader(body))
if err != nil {
return nil, err
}
req.Header.Set("Accept", "application/json")

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

var tokenResponse GitHubTokenResponse
if err := json.NewDecoder(resp.Body).Decode(&tokenResponse); err != nil {
return nil, err
}

Check warning on line 81 in contribs/gnofaucet/gh.go

View check run for this annotation

Codecov / codecov/patch

contribs/gnofaucet/gh.go#L62-L81

Added lines #L62 - L81 were not covered by tests

if tokenResponse.AccessToken == "" {
return nil, fmt.Errorf("unable to exchange code for token")
}

Check warning on line 85 in contribs/gnofaucet/gh.go

View check run for this annotation

Codecov / codecov/patch

contribs/gnofaucet/gh.go#L83-L85

Added lines #L83 - L85 were not covered by tests

return &tokenResponse, nil

Check warning on line 87 in contribs/gnofaucet/gh.go

View check run for this annotation

Codecov / codecov/patch

contribs/gnofaucet/gh.go#L87

Added line #L87 was not covered by tests
}
2 changes: 2 additions & 0 deletions contribs/gnofaucet/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.23.6
require (
github.com/gnolang/faucet v0.3.2
github.com/gnolang/gno v0.1.0-nightly.20240627
github.com/google/go-github/v64 v64.0.0
github.com/stretchr/testify v1.10.0
go.uber.org/zap v1.27.0
golang.org/x/time v0.5.0
Expand All @@ -21,6 +22,7 @@ require (
github.com/go-chi/chi/v5 v5.1.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect
Expand Down
5 changes: 5 additions & 0 deletions contribs/gnofaucet/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 19 additions & 2 deletions contribs/gnofaucet/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ type serveCfg struct {

remote string

captchaSecret string
isBehindProxy bool
captchaSecret string
ghClientID string
ghClientSecret string
isBehindProxy bool
}

func newServeCmd() *commands.Command {
Expand Down Expand Up @@ -127,6 +129,20 @@ func (c *serveCfg) RegisterFlags(fs *flag.FlagSet) {
"recaptcha secret key (if empty, captcha are disabled)",
)

fs.StringVar(
&c.ghClientSecret,
"github-client-secret",
"",
"github client secret for oauth authentication (if empty, middleware is disabled)",
)

fs.StringVar(
&c.ghClientID,
"github-client-id",
"",
"github client id for oauth authentication",
)

Check warning on line 145 in contribs/gnofaucet/serve.go

View check run for this annotation

Codecov / codecov/patch

contribs/gnofaucet/serve.go#L132-L145

Added lines #L132 - L145 were not covered by tests
fs.BoolVar(
&c.isBehindProxy,
"is-behind-proxy",
Expand Down Expand Up @@ -195,6 +211,7 @@ func execServe(ctx context.Context, cfg *serveCfg, io commands.IO) error {
middlewares := []faucet.Middleware{
getIPMiddleware(cfg.isBehindProxy, st),
getCaptchaMiddleware(cfg.captchaSecret),
getGithubMiddleware(cfg.ghClientID, cfg.ghClientSecret, 1*time.Hour),

Check warning on line 214 in contribs/gnofaucet/serve.go

View check run for this annotation

Codecov / codecov/patch

contribs/gnofaucet/serve.go#L214

Added line #L214 was not covered by tests
}

// Create a new faucet with
Expand Down

0 comments on commit c8a8456

Please sign in to comment.