-
Notifications
You must be signed in to change notification settings - Fork 399
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: faucet github middleware with coolDown
- Loading branch information
1 parent
85b3c0b
commit c8a8456
Showing
5 changed files
with
150 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} | ||
} | ||
|
||
// 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 | ||
} | ||
} | ||
|
||
rl.cooldowns[ghLogin] = time.Now() | ||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
|
||
code := r.URL.Query().Get("code") | ||
if code == "" { | ||
http.Error(w, "missing code", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
res, err := exchangeCodeForToken(secret, clientID, code) | ||
if err != nil { | ||
http.Error(w, err.Error(), http.StatusBadRequest) | ||
return | ||
} | ||
|
||
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 | ||
} | ||
// 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 | ||
} | ||
|
||
// Possibility to have more conditions like accountAge, commits, pullRequest etc | ||
|
||
next.ServeHTTP(w, r) | ||
}, | ||
) | ||
} | ||
} | ||
|
||
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 | ||
} | ||
|
||
if tokenResponse.AccessToken == "" { | ||
return nil, fmt.Errorf("unable to exchange code for token") | ||
} | ||
|
||
return &tokenResponse, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters