From c8ca445bb7500b6220a1c0caa85f95c8e2e50e95 Mon Sep 17 00:00:00 2001 From: Midou Date: Mon, 10 Jul 2023 22:23:29 +0100 Subject: [PATCH] Require a captcha on the backend --- go.mod | 1 + go.sum | 2 + pages/signup.go | 154 +++++++++++++++++++++++++++++++++++------------- pages/users.go | 28 +++++++-- 4 files changed, 140 insertions(+), 45 deletions(-) diff --git a/go.mod b/go.mod index d649c61..8a556ee 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.19 require ( github.com/containrrr/shoutrrr v0.6.1 github.com/gofiber/fiber/v2 v2.41.0 + github.com/kataras/hcaptcha v0.0.2 github.com/sirupsen/logrus v1.9.0 github.com/spf13/viper v1.6.3 ) diff --git a/go.sum b/go.sum index 0c6e04a..73fa646 100644 --- a/go.sum +++ b/go.sum @@ -74,6 +74,8 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kataras/hcaptcha v0.0.2 h1:8gPteB5vPD1WvsKv4OcYF+EfntCY7cm7s1b8bB9ai7Y= +github.com/kataras/hcaptcha v0.0.2/go.mod h1:Ce7mO5B8q8RKyWWWJt2fczJ3O1vTlX+mZ2DZZOMnfSw= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= diff --git a/pages/signup.go b/pages/signup.go index 1ed5f4b..7ddb287 100644 --- a/pages/signup.go +++ b/pages/signup.go @@ -1,15 +1,21 @@ package pages import ( + "encoding/json" + "io/ioutil" + "net/http" + "net/url" + "os" + "strings" + "github.com/ProjectSegfault/publapi/utils" "github.com/containrrr/shoutrrr" "github.com/gofiber/fiber/v2" log "github.com/sirupsen/logrus" - "os" - "strings" ) // SignupPage is the signup page handler + func SignupPage(c *fiber.Ctx) error { SignupIP, SignupIPExists := os.LookupEnv("PUBLAPI_SIGNUP_IP") if SignupIPExists == true { @@ -22,55 +28,123 @@ func SignupPage(c *fiber.Ctx) error { email := c.FormValue("email") ssh := c.FormValue("ssh") ip := c.FormValue("ip") + captchaResp := c.FormValue("h-captcha-response") + + if captchaResp == "" { + log.Error("Nice try, but the registration won't work unless you answer the captcha.") + return c.SendStatus(fiber.StatusBadRequest) + } + if username == "" || email == "" || ssh == "" || ip == "" { log.Error("username, email, ssh and ip must be filled", username, email, ssh, ip) return c.SendStatus(fiber.StatusBadRequest) } raid, ok := os.LookupEnv("PUBLAPI_RAID_MODE") if !ok || raid == "1" { - log.Error("PUBLAPI_RAID_MODE is on accepting every request as OK and not doing anything...\n User info: ", username, email, ip) + log.Warn( + "PUBLAPI_RAID_MODE is on, accepting every request as OK and not doing anything...\n User info: ", + username, + " ", + email, + " ", + ip, + " ", + ) return c.SendStatus(fiber.StatusOK) } - // create user file - f, err := os.Create("/var/publapi/users/" + username + ".sh") + // Check the captcha validation. + + captchaSecret, ok := os.LookupEnv("PUBLAPI_CAPTCHA_SECRET") + + params := url.Values{} + params.Add("response", captchaResp) + params.Add("secret", captchaSecret) + body := strings.NewReader(params.Encode()) + + req, err := http.NewRequest("POST", "https://hcaptcha.com/siteverify", body) if err != nil { - log.Error("Error creating user file", err) - return c.SendStatus(fiber.StatusInternalServerError) - } - defer f.Close() - chmoderr := os.Chmod("/var/publapi/users/"+username+".sh", 0700) - if chmoderr != nil { - log.Error(err) - } - Bashscript := strings.ReplaceAll(utils.Bashscript, "{{sshkey}}", ssh) - Bashscript = strings.ReplaceAll(Bashscript, "{{email}}", email) - Bashscript = strings.ReplaceAll(Bashscript, "{{username}}", username) - // write to file - _, err = f.WriteString(Bashscript) - if err != nil { - log.Error("Error writing to user file", err) - return c.SendStatus(fiber.StatusInternalServerError) + // handle err } - log.Info("Registration request for " + username + " has been submitted by the frontend and has been written to /var/publapi/users/" + username + ".sh") - // send notification to user that their reg request was sent - err = shoutrrr.Send(os.Getenv("PUBLAPI_EMAIL_SHOUTRRRURL")+email, "Hello "+username+",\nYour registration request has been sent.\nIt will take a maximum of 48 hours for the request to be processed.\nThank you for being part of the Project Segfault Pubnix.") - if err != nil { - log.Error("Error sending email to user", err) - return c.SendStatus(fiber.StatusInternalServerError) - } - // send notification to admins - shoutrrrUrl := os.Getenv("PUBLAPI_NOTIFY_SHOUTRRRURL") + os.Getenv("PUBLAPI_NOTIFY_ROOMS") - err = shoutrrr.Send(shoutrrrUrl, "New user signup! Please review /var/publapi/users/"+username+".sh to approve or deny the user. IP: "+ip+" Email: "+email) - if err != nil { - log.Error("Error sending notification to admins", err) - return c.SendStatus(fiber.StatusInternalServerError) - } - return c.JSON(fiber.Map{ - "username": username, - "message": "User created! Please allow us 24 hours or more to review your account.", - "status": c.Response().StatusCode(), - }) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Error("Something went wrong fetching the HCatpcha API: ", err) + } + defer resp.Body.Close() + + bod, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Error("Error reading captcha response body", err) + } + sb := string(bod) + log.Info("Captcha response: ", sb) + + type CaptchaResponse struct { + Success bool `json:"success"` + } + var captchaResponse CaptchaResponse + err = json.Unmarshal([]byte(sb), &captchaResponse) + if err != nil { + log.Error("Error unmarshalling captcha response", err) + } + + if captchaResponse.Success == false { + log.Error("Captcha validation failed") + return c.SendStatus(fiber.StatusBadRequest) + } else { + + // create user file + + f, err := os.Create("/var/publapi/users/" + username + ".sh") + if err != nil { + log.Error("Error creating user file", err) + return c.SendStatus(fiber.StatusInternalServerError) + } + defer f.Close() + chmoderr := os.Chmod("/var/publapi/users/"+username+".sh", 0700) + if chmoderr != nil { + log.Error(err) + } + Bashscript := strings.ReplaceAll(utils.Bashscript, "{{sshkey}}", ssh) + Bashscript = strings.ReplaceAll(Bashscript, "{{email}}", email) + Bashscript = strings.ReplaceAll(Bashscript, "{{username}}", username) + // write to file + _, err = f.WriteString(Bashscript) + if err != nil { + log.Error("Error writing to user file", err) + return c.SendStatus(fiber.StatusInternalServerError) + } + + log.Info( + "Registration request for " + username + " has been submitted by the frontend and has been written to /var/publapi/users/" + username + ".sh", + ) + + // send notification to user that their reg request was sent + + // err = shoutrrr.Send(os.Getenv("PUBLAPI_EMAIL_SHOUTRRRURL")+email, "Hello "+username+",\nYour registration request has been sent.\nIt will take a maximum of 48 hours for the request to be processed.\nThank you for being part of the Project Segfault Pubnix.") + // if err != nil { + // log.Error("Error sending email to user", err) + // return c.SendStatus(fiber.StatusInternalServerError) + // } + + // send notification to admins + + shoutrrrUrl := os.Getenv("PUBLAPI_NOTIFY_SHOUTRRRURL") + os.Getenv("PUBLAPI_NOTIFY_ROOMS") + err = shoutrrr.Send( + shoutrrrUrl, + "New user signup! Please review /var/publapi/users/"+username+".sh to approve or deny the user. IP: "+ip+" Email: "+email, + ) + if err != nil { + log.Error("Error sending notification to admins", err) + return c.SendStatus(fiber.StatusInternalServerError) + } + return c.JSON(fiber.Map{ + "username": username, + "message": "User created! Please allow us 24 hours or more to review your account.", + "status": c.Response().StatusCode(), + }) + } } diff --git a/pages/users.go b/pages/users.go index 9c43f62..d22298d 100644 --- a/pages/users.go +++ b/pages/users.go @@ -1,16 +1,19 @@ package pages import ( - "github.com/ProjectSegfault/publapi/utils" - "github.com/gofiber/fiber/v2" - log "github.com/sirupsen/logrus" - "github.com/spf13/viper" "os" "os/exec" "regexp" "runtime" "strconv" "strings" + + "github.com/ProjectSegfault/publapi/utils" + "github.com/gofiber/fiber/v2" + log "github.com/sirupsen/logrus" + "github.com/spf13/viper" + + "github.com/kataras/hcaptcha" ) type Userstruct struct { @@ -35,6 +38,14 @@ type Userinfo struct { Loc string `json:"loc"` } +// Hcaptcha setup + +var ( + secretKey string = os.Getenv("PUBLAPI_HCAPTCHA_SECRET") + siteKey string = os.Getenv("PUBLAPI_HCAPTCHA_SITE") + client = hcaptcha.New(secretKey) +) + func userdata(username, usersonline, ops string) Userinfo { regex := "(^| )" + username + "($| )" isonline, err := regexp.MatchString(string(regex), string(usersonline)) @@ -144,7 +155,14 @@ func UsersPage(c *fiber.Ctx) error { var userinfostruct []Userinfo for i := 0; i < len(usersarr); i++ { uname := string(usersarr[i]) - userinfostruct = append(userinfostruct, userdata(uname, strings.TrimSuffix(usersonlinededup, "\n"), strings.TrimSuffix(opstr, "\n"))) + userinfostruct = append( + userinfostruct, + userdata( + uname, + strings.TrimSuffix(usersonlinededup, "\n"), + strings.TrimSuffix(opstr, "\n"), + ), + ) } data := Userstruct{ Status: c.Response().StatusCode(),