1
0
Fork 0
mirror of https://github.com/Eggbertx/gochan.git synced 2025-08-26 14:46:24 -07:00
gochan/pkg/posting/captcha.go

123 lines
3.4 KiB
Go

package posting
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
"github.com/gochan-org/gochan/pkg/config"
"github.com/gochan-org/gochan/pkg/gcsql"
"github.com/gochan-org/gochan/pkg/gctemplates"
"github.com/gochan-org/gochan/pkg/gcutil"
"github.com/gochan-org/gochan/pkg/server"
"github.com/gochan-org/gochan/pkg/server/serverutil"
)
var (
ErrNoCaptchaToken = errors.New("missing required CAPTCHA")
ErrUnsupportedCaptcha = errors.New("unsupported captcha type set in configuration (currently only hcaptcha is supported)")
validCaptchaTypes = []string{"hcaptcha"}
)
type CaptchaResult struct {
Hostname string `json:"hostname"`
Credit bool `json:"credit"`
Success bool `json:"success"`
Timestamp time.Time `json:"challenge_ts"`
}
func InitCaptcha() {
var typeIsValid bool
captchaCfg := config.GetSiteConfig().Captcha
if !captchaCfg.UseCaptcha() {
return
}
for _, vType := range validCaptchaTypes {
if captchaCfg.Type == vType {
typeIsValid = true
}
}
if !typeIsValid {
gcutil.LogFatal().
Str("captchaType", captchaCfg.Type).
Msg("Unsupported captcha type set in configuration")
}
}
// submitCaptchaResponse parses the incoming captcha form values, submits them, and returns the results
func submitCaptchaResponse(request *http.Request) (bool, error) {
captchaCfg := config.GetSiteConfig().Captcha
if !captchaCfg.UseCaptcha() {
return true, nil // captcha isn't required, skip the test
}
threadid, _ := strconv.Atoi(request.PostFormValue("threadid"))
if captchaCfg.OnlyNeededForThreads && threadid > 0 {
return true, nil
}
var token string
switch captchaCfg.Type {
case "hcaptcha":
token = request.PostFormValue("h-captcha-response")
default:
}
if token == "" {
return false, ErrNoCaptchaToken
}
params := url.Values{
"secret": []string{captchaCfg.AccountSecret},
"response": []string{token},
}
resp, err := http.PostForm("https://hcaptcha.com/siteverify", params)
if err != nil {
return false, err
}
var vals CaptchaResult
if err = json.NewDecoder(resp.Body).Decode(&vals); err != nil {
return false, err
}
return vals.Success, resp.Body.Close()
}
// ServeCaptcha handles requests to /captcha if the captcha is properly configured
func ServeCaptcha(writer http.ResponseWriter, request *http.Request) {
captchaCfg := config.GetSiteConfig().Captcha
if request.Method == "GET" && request.FormValue("needcaptcha") != "" {
fmt.Fprint(writer, captchaCfg.UseCaptcha())
return
}
errEv := gcutil.LogError(nil).
Str("IP", gcutil.GetRealIP(request))
defer func() {
errEv.Discard()
}()
wantsJSON := serverutil.IsRequestingJSON(request)
if !captchaCfg.UseCaptcha() {
server.ServeError(writer, "This site is not set up to require a CAPTCHA test", wantsJSON, nil)
return
}
if request.Method == "POST" {
result, err := submitCaptchaResponse(request)
if err != nil {
errEv.Err(err).Caller().Send()
server.ServeError(writer, "Error checking CAPTCHA results: "+err.Error(), wantsJSON, nil)
return
}
fmt.Println("Success:", result)
}
err := serverutil.MinifyTemplate(gctemplates.Captcha, map[string]any{
"boardConfig": config.GetBoardConfig(""),
"boards": gcsql.AllBoards,
"siteKey": captchaCfg.SiteKey,
}, writer, "text/html")
if err != nil {
errEv.Err(err).Caller().Send()
server.ServeError(writer, "Error serving CAPTCHA: "+err.Error(), wantsJSON, nil)
}
}