Skip to content

Commit

Permalink
Merge pull request #3 from stratisproject/feature/strax-faucet
Browse files Browse the repository at this point in the history
Discord integration
  • Loading branch information
fenix2222 authored Feb 13, 2024
2 parents 418d097 + ae80591 commit 976ef92
Show file tree
Hide file tree
Showing 6 changed files with 446 additions and 111 deletions.
18 changes: 14 additions & 4 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ var (
versionFlag = flag.Bool("version", false, "Print version number")

//payoutFlag = flag.Int("faucet_amount", 10000, "Number of Ethers to transfer per user request")
intervalFlag = flag.Int("faucet_minutes", 1440, "Number of minutes to wait between funding rounds")
netnameFlag = flag.String("faucet_name", os.Getenv("FAUCET_NAME"), "Network name to display on the frontend")
symbolFlag = flag.String("faucet_symbol", os.Getenv("FAUCET_SYMBOL"), "Token symbol to display on the frontend")
//intervalFlag = flag.Int("faucet_minutes", os.Getenv("FAUCET_MINUTES"), "Number of minutes to wait between funding rounds")
netnameFlag = flag.String("faucet_name", os.Getenv("FAUCET_NAME"), "Network name to display on the frontend")
symbolFlag = flag.String("faucet_symbol", os.Getenv("FAUCET_SYMBOL"), "Token symbol to display on the frontend")

keyJSONFlag = flag.String("wallet_keyjson", os.Getenv("KEYSTORE"), "Keystore file to fund user requests with")
keyPassFlag = flag.String("wallet_keypass", "password.txt", "Passphrase text file to decrypt keystore")
Expand All @@ -37,6 +37,10 @@ var (

hcaptchaSiteKeyFlag = flag.String("hcaptcha_sitekey", os.Getenv("HCAPTCHA_SITEKEY"), "hCaptcha sitekey")
hcaptchaSecretFlag = flag.String("hcaptcha_secret", os.Getenv("HCAPTCHA_SECRET"), "hCaptcha secret")

discordClientId = flag.String("discord_client_id", os.Getenv("DISCORD_CLIENTID"), "Discord client id for oauth2")
discordClientSecret = flag.String("discord_client_secret", os.Getenv("DISCORD_CLIENTSECRET"), "Discord client secret for oauth2")
discordRedirectUrl = flag.String("discord_redirect_url", os.Getenv("DISCORD_REDIRECTURL"), "Discord redirect url for oauth2")
)

func init() {
Expand Down Expand Up @@ -68,13 +72,19 @@ func Execute() {
}
httpPortFlag := flag.Int("httpport", port, "Listener port to serve HTTP connection")

interval, err := strconv.Atoi(os.Getenv("FAUCET_MINUTES"))
if err != nil {
interval = 1440
}
intervalFlag := flag.Int("faucet_minutes", interval, "Number of minutes to wait between funding rounds")

faucetAmount, err := strconv.Atoi(os.Getenv("FAUCET_AMOUNT"))
if err != nil {
faucetAmount = 10000
}
payoutFlag := flag.Int("faucet.amount", faucetAmount, "Number of Ethers to transfer per user request")

config := server.NewConfig(*netnameFlag, *symbolFlag, *httpPortFlag, *intervalFlag, *payoutFlag, *proxyCntFlag, *hcaptchaSiteKeyFlag, *hcaptchaSecretFlag)
config := server.NewConfig(*netnameFlag, *symbolFlag, *httpPortFlag, *intervalFlag, *payoutFlag, *proxyCntFlag, *hcaptchaSiteKeyFlag, *hcaptchaSecretFlag, *discordClientId, *discordClientSecret, *discordRedirectUrl)
go server.NewServer(txBuilder, config).Run()

c := make(chan os.Signal, 1)
Expand Down
40 changes: 23 additions & 17 deletions internal/server/config.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
package server

type Config struct {
network string
symbol string
httpPort int
interval int
payout int
proxyCount int
hcaptchaSiteKey string
hcaptchaSecret string
network string
symbol string
httpPort int
interval int
payout int
proxyCount int
hcaptchaSiteKey string
hcaptchaSecret string
discordClientId string
discordClientSecret string
discordRedirectUrl string
}

func NewConfig(network, symbol string, httpPort, interval, payout, proxyCount int, hcaptchaSiteKey, hcaptchaSecret string) *Config {
func NewConfig(network, symbol string, httpPort, interval, payout, proxyCount int, hcaptchaSiteKey, hcaptchaSecret, discordClientId, discordClientSecret, discordRedirectUrl string) *Config {
return &Config{
network: network,
symbol: symbol,
httpPort: httpPort,
interval: interval,
payout: payout,
proxyCount: proxyCount,
hcaptchaSiteKey: hcaptchaSiteKey,
hcaptchaSecret: hcaptchaSecret,
network: network,
symbol: symbol,
httpPort: httpPort,
interval: interval,
payout: payout,
proxyCount: proxyCount,
hcaptchaSiteKey: hcaptchaSiteKey,
hcaptchaSecret: hcaptchaSecret,
discordClientId: discordClientId,
discordClientSecret: discordClientSecret,
discordRedirectUrl: discordRedirectUrl,
}
}
40 changes: 40 additions & 0 deletions internal/server/dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,38 @@ type claimRequest struct {
Address string `json:"address"`
}

type loginRequest struct {
Code string `json:"code"`
}

type claimResponse struct {
Message string `json:"msg"`
}

type loginResponse struct {
Message string `json:"msg"`
}

type discordTokenResponse struct {
AccessToken string `json:"access_token"`
Error string `json:"error"`
ErrorDesc string `json:"error_description"`
}

type infoResponse struct {
Account string `json:"account"`
Network string `json:"network"`
Payout string `json:"payout"`
Symbol string `json:"symbol"`
HcaptchaSiteKey string `json:"hcaptcha_sitekey,omitempty"`
RemoteAddr string `json:"remote_addr,omitempty"`
Forward string `json:"forward,omitempty"`
RealIP string `json:"real_ip,omitempty"`
DiscorClientId string `json:"discord_client_id"`
}

type authResponse struct {
Token string `json:"token"`
}

type malformedRequest struct {
Expand Down Expand Up @@ -92,6 +114,24 @@ func readAddress(r *http.Request) (string, error) {
return claimReq.Address, nil
}

func readCode(r *http.Request) (string, error) {
var loginReq loginRequest
if err := decodeJSONBody(r, &loginReq); err != nil {
return "", err
}

return loginReq.Code, nil
}

func readToken(r *http.Request) (string, error) {
var discordRes discordTokenResponse
if err := decodeJSONBody(r, &discordRes); err != nil {
return "", err
}

return discordRes.AccessToken, nil
}

func renderJSON(w http.ResponseWriter, v interface{}, code int) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
Expand Down
153 changes: 150 additions & 3 deletions internal/server/middleware.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package server

import (
"bytes"
"errors"
"fmt"
"net"
Expand Down Expand Up @@ -50,7 +51,11 @@ func (l *Limiter) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.Ha
}

clintIP := getClientIPFromRequest(l.proxyCount, r)

fmt.Println("Client Values", address, clintIP)

l.mutex.Lock()
// if l.limitByKey(w, address) {
if l.limitByKey(w, address) || l.limitByKey(w, clintIP) {
l.mutex.Unlock()
return
Expand All @@ -65,6 +70,7 @@ func (l *Limiter) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.Ha
l.cache.Remove(clintIP)
return
}

log.WithFields(log.Fields{
"address": address,
"clientIP": clintIP,
Expand All @@ -73,7 +79,8 @@ func (l *Limiter) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.Ha

func (l *Limiter) limitByKey(w http.ResponseWriter, key string) bool {
if _, ttl, err := l.cache.GetWithTTL(key); err == nil {
errMsg := fmt.Sprintf("You have exceeded the rate limit. Please wait %s before you try again", ttl.Round(time.Second))
errMsg := fmt.Sprintf("You have exceeded the rate limit for %s. Please wait %s before you try again", key, ttl.Round(time.Second))
fmt.Println(errMsg)
renderJSON(w, claimResponse{Message: errMsg}, http.StatusTooManyRequests)
return true
}
Expand All @@ -94,18 +101,105 @@ func getClientIPFromRequest(proxyCount int, r *http.Request) string {
}
}

remoteIP, _, err := net.SplitHostPort(r.RemoteAddr)
remoteIP, _, err := net.SplitHostPort(getIPAdress(r))
if err != nil {
remoteIP = r.RemoteAddr
remoteIP = getIPAdress(r)
if remoteIP == "" {
remoteIP = r.RemoteAddr
}
}
return remoteIP
}

// ipRange - a structure that holds the start and end of a range of ip addresses
type ipRange struct {
start net.IP
end net.IP
}

// inRange - check to see if a given ip address is within a range given
func inRange(r ipRange, ipAddress net.IP) bool {
// strcmp type byte comparison
if bytes.Compare(ipAddress, r.start) >= 0 && bytes.Compare(ipAddress, r.end) < 0 {
return true
}
return false
}

var privateRanges = []ipRange{
ipRange{
start: net.ParseIP("10.0.0.0"),
end: net.ParseIP("10.255.255.255"),
},
ipRange{
start: net.ParseIP("100.64.0.0"),
end: net.ParseIP("100.127.255.255"),
},
ipRange{
start: net.ParseIP("172.16.0.0"),
end: net.ParseIP("172.31.255.255"),
},
ipRange{
start: net.ParseIP("192.0.0.0"),
end: net.ParseIP("192.0.0.255"),
},
ipRange{
start: net.ParseIP("192.168.0.0"),
end: net.ParseIP("192.168.255.255"),
},
ipRange{
start: net.ParseIP("198.18.0.0"),
end: net.ParseIP("198.19.255.255"),
},
}

// isPrivateSubnet - check to see if this ip is in a private subnet
func isPrivateSubnet(ipAddress net.IP) bool {
// my use case is only concerned with ipv4 atm
if ipCheck := ipAddress.To4(); ipCheck != nil {
// iterate over all our ranges
for _, r := range privateRanges {
// check if this ip is in a private range
if inRange(r, ipAddress) {
return true
}
}
}
return false
}

func getIPAdress(r *http.Request) string {
for _, h := range []string{"X-Forwarded-For", "X-Real-Ip"} {
addresses := strings.Split(r.Header.Get(h), ",")
// march from right to left until we get a public address
// that will be the address right before our proxy.
for i := len(addresses) - 1; i >= 0; i-- {
ip := strings.TrimSpace(addresses[i])
realIP, _, err := net.SplitHostPort(ip)
if err != nil {
realIP = ip
}

parsedId := net.ParseIP(realIP)
if !parsedId.IsGlobalUnicast() || isPrivateSubnet(parsedId) {
// bad address, go to next
continue
}
return realIP
}
}
return ""
}

type Captcha struct {
client *hcaptcha.Client
secret string
}

type Auth struct {
code string
}

func NewCaptcha(hcaptchaSiteKey, hcaptchaSecret string) *Captcha {
client := hcaptcha.New(hcaptchaSecret)
client.SiteKey = hcaptchaSiteKey
Expand All @@ -115,6 +209,12 @@ func NewCaptcha(hcaptchaSiteKey, hcaptchaSecret string) *Captcha {
}
}

func NewAuth(code string) *Auth {
return &Auth{
code: code,
}
}

func (c *Captcha) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if c.secret == "" {
next.ServeHTTP(w, r)
Expand All @@ -129,3 +229,50 @@ func (c *Captcha) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.Ha

next.ServeHTTP(w, r)
}

func (c *Auth) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {

cookie, err := r.Cookie("token")
if err != nil {
// If the cookie is not set, return an unauthorized status
if err == http.ErrNoCookie {
renderJSON(w, loginResponse{Message: "Invalid login. Please authenticate with discord first"}, http.StatusUnauthorized)
return
}
// For any other error, return a bad request status
renderJSON(w, loginResponse{Message: "Invalid login. Please authenticate with discord first"}, http.StatusBadRequest)
return
}

token := cookie.Value

isValid := validateToken(token)
if !isValid {
renderJSON(w, loginResponse{Message: "Invalid login. Please authenticate with discord first"}, http.StatusUnauthorized)
return
}

next.ServeHTTP(w, r)
}

func validateToken(token string) bool {
req, err := http.NewRequest("GET", "https://discord.com/api/users/@me", nil)
if err != nil {
return false
}
req.Header.Set("Authorization", "Bearer "+token)

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

// Check if the response status code is 200 OK
if resp.StatusCode == http.StatusOK {
return true
}

return false
}
Loading

0 comments on commit 976ef92

Please sign in to comment.