GoBlog/captcha.go

140 lines
4.2 KiB
Go
Raw Permalink Normal View History

2021-01-23 16:24:47 +00:00
package main
import (
"context"
2021-01-23 16:24:47 +00:00
"encoding/base64"
"encoding/json"
"io"
"net/http"
"strings"
"time"
2021-01-23 16:24:47 +00:00
"github.com/dchest/captcha"
"go.goblog.app/app/pkgs/bodylimit"
2022-02-22 20:33:12 +00:00
"go.goblog.app/app/pkgs/bufferpool"
"go.goblog.app/app/pkgs/contenttype"
2021-01-23 16:24:47 +00:00
)
const captchaSolvedKey contextKey = "captchaSolved"
var captchaStore = captcha.NewMemoryStore(100, 10*time.Minute)
func init() {
captcha.SetCustomStore(captchaStore)
}
func (a *goBlog) captchaMiddleware(next http.Handler) http.Handler {
2021-01-23 16:24:47 +00:00
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Check if captcha already solved
if solved, ok := r.Context().Value(captchaSolvedKey).(bool); ok && solved {
next.ServeHTTP(w, r)
return
}
2021-12-19 15:56:25 +00:00
// Check session
ses, err := a.captchaSessions.Get(r, "c")
2021-12-19 15:56:25 +00:00
if err != nil {
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
2023-02-23 15:32:59 +00:00
if captchaVal, ok := ses.Values["captcha"]; ok && captchaVal == true {
2021-12-19 15:56:25 +00:00
// Captcha already solved
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), captchaSolvedKey, true)))
return
}
// Get captcha ID
captchaId := ""
if sesCaptchaId, ok := ses.Values["captchaid"]; ok {
// Already has a captcha ID
ci := sesCaptchaId.(string)
if captcha.Reload(ci) {
captchaId = ci
2021-01-23 16:24:47 +00:00
}
}
2021-12-19 15:56:25 +00:00
if captchaId == "" {
captchaId = captcha.New()
ses.Values["captchaid"] = captchaId
}
// Encode original request
2022-02-22 20:33:12 +00:00
headerBuffer, bodyBuffer := bufferpool.Get(), bufferpool.Get()
defer bufferpool.Put(headerBuffer, bodyBuffer)
// Encode headers
headerEncoder := base64.NewEncoder(base64.StdEncoding, headerBuffer)
_ = json.NewEncoder(headerEncoder).Encode(r.Header)
_ = headerEncoder.Close()
// Encode body
bodyEncoder := base64.NewEncoder(base64.StdEncoding, bodyBuffer)
limit := 3 * bodylimit.MB
2022-02-22 20:33:12 +00:00
written, _ := io.Copy(bodyEncoder, io.LimitReader(r.Body, limit))
if written == 0 {
2021-01-23 16:24:47 +00:00
// Maybe it's a form
_ = r.ParseForm()
2022-02-22 20:33:12 +00:00
// Encode form
written = int64(noError(io.WriteString(bodyEncoder, r.Form.Encode())))
2022-02-22 20:33:12 +00:00
}
2023-02-23 15:32:59 +00:00
_ = bodyEncoder.Close()
2022-02-22 20:33:12 +00:00
if written >= limit {
a.serveError(w, r, "Request body too large, first login", http.StatusRequestEntityTooLarge)
return
2021-01-23 16:24:47 +00:00
}
2021-12-19 15:56:25 +00:00
// Render captcha
_ = ses.Save(r, w)
2022-02-01 14:58:12 +00:00
w.Header().Set(cacheControl, "no-store,max-age=0")
a.renderWithStatusCode(w, r, http.StatusUnauthorized, a.renderCaptcha, &renderData{
Data: &captchaRenderData{
captchaMethod: r.Method,
2022-02-22 20:33:12 +00:00
captchaHeaders: headerBuffer.String(),
captchaBody: bodyBuffer.String(),
captchaId: captchaId,
2021-01-23 16:24:47 +00:00
},
})
})
}
func (a *goBlog) checkIsCaptcha(next http.Handler) http.Handler {
2021-01-23 16:24:47 +00:00
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if !a.checkCaptcha(rw, r) {
2021-01-23 16:24:47 +00:00
next.ServeHTTP(rw, r)
}
})
}
func (a *goBlog) checkCaptcha(w http.ResponseWriter, r *http.Request) bool {
2021-05-14 16:24:02 +00:00
if r.Method != http.MethodPost {
return false
}
if !strings.Contains(r.Header.Get(contentType), contenttype.WWWForm) {
2021-05-14 16:24:02 +00:00
return false
}
if r.FormValue("captchaaction") != "captcha" {
return false
}
2022-02-22 20:33:12 +00:00
// Prepare original request
bodyDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(r.FormValue("captchabody")))
2022-11-27 14:06:43 +00:00
origReq, _ := http.NewRequestWithContext(r.Context(), r.FormValue("captchamethod"), r.URL.RequestURI(), bodyDecoder)
2022-02-22 20:33:12 +00:00
headerDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(r.FormValue("captchaheaders")))
_ = json.NewDecoder(headerDecoder).Decode(&origReq.Header)
2021-12-19 15:56:25 +00:00
// Get session
ses, err := a.captchaSessions.Get(r, "c")
if err != nil {
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return true
}
// Check if session contains a captchaId and if captcha is solved
if sesCaptchaId, ok := ses.Values["captchaid"]; ok && captcha.VerifyString(sesCaptchaId.(string), r.FormValue("digits")) {
2021-05-14 16:24:02 +00:00
ses.Values["captcha"] = true
err = a.captchaSessions.Save(r, w, ses)
2021-05-14 16:24:02 +00:00
if err != nil {
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
2021-05-14 16:24:02 +00:00
return true
2021-01-23 16:24:47 +00:00
}
2021-12-19 15:56:25 +00:00
origReq = origReq.WithContext(context.WithValue(origReq.Context(), captchaSolvedKey, true))
}
// Copy captcha cookie to original request
if captchaCookie, err := r.Cookie("c"); err == nil {
origReq.AddCookie(captchaCookie)
2021-01-23 16:24:47 +00:00
}
2021-05-14 16:24:02 +00:00
// Serve original request
2021-12-19 15:56:25 +00:00
a.d.ServeHTTP(w, origReq)
2021-05-14 16:24:02 +00:00
return true
2021-01-23 16:24:47 +00:00
}