Rework login and captcha forms

This commit is contained in:
Jan-Lukas Else 2022-02-22 21:33:12 +01:00
parent 63a069cec8
commit 0437f61ba9
2 changed files with 61 additions and 40 deletions

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"bytes"
"context" "context"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
@ -10,6 +9,7 @@ import (
"strings" "strings"
"github.com/pquerna/otp/totp" "github.com/pquerna/otp/totp"
"go.goblog.app/app/pkgs/bufferpool"
"go.goblog.app/app/pkgs/contenttype" "go.goblog.app/app/pkgs/contenttype"
) )
@ -51,22 +51,38 @@ func (a *goBlog) authMiddleware(next http.Handler) http.Handler {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
return return
} }
// Show login form // Remember to close body
w.Header().Set(cacheControl, "no-store,max-age=0") defer r.Body.Close()
w.Header().Set("X-Robots-Tag", "noindex") // Encode original request
h, _ := json.Marshal(r.Header) headerBuffer, bodyBuffer := bufferpool.Get(), bufferpool.Get()
b, _ := io.ReadAll(io.LimitReader(r.Body, 20*1000*1000)) // Only allow 20 MB defer bufferpool.Put(headerBuffer, bodyBuffer)
_ = r.Body.Close() // Encode headers
if len(b) == 0 { headerEncoder := base64.NewEncoder(base64.StdEncoding, headerBuffer)
_ = json.NewEncoder(headerEncoder).Encode(r.Header)
_ = headerEncoder.Close()
// Encode body
bodyEncoder := base64.NewEncoder(base64.StdEncoding, bodyBuffer)
limit := int64(3 * 1000 * 1000) // 3 MB
written, _ := io.Copy(bodyEncoder, io.LimitReader(r.Body, limit))
if written == 0 {
// Maybe it's a form // Maybe it's a form
_ = r.ParseForm() _ = r.ParseForm()
b = []byte(r.PostForm.Encode()) // Encode form
written, _ = io.Copy(bodyEncoder, strings.NewReader(r.Form.Encode()))
} }
bodyEncoder.Close()
if written >= limit {
a.serveError(w, r, "Request body too large, first login", http.StatusRequestEntityTooLarge)
return
}
// Render login form
w.Header().Set(cacheControl, "no-store,max-age=0")
w.Header().Set("X-Robots-Tag", "noindex")
a.render(w, r, a.renderLogin, &renderData{ a.render(w, r, a.renderLogin, &renderData{
Data: &loginRenderData{ Data: &loginRenderData{
loginMethod: r.Method, loginMethod: r.Method,
loginHeaders: base64.StdEncoding.EncodeToString(h), loginHeaders: headerBuffer.String(),
loginBody: base64.StdEncoding.EncodeToString(b), loginBody: bodyBuffer.String(),
totp: a.cfg.User.TOTP != "", totp: a.cfg.User.TOTP != "",
}, },
}) })
@ -99,15 +115,10 @@ func (a *goBlog) checkLogin(w http.ResponseWriter, r *http.Request) bool {
return true return true
} }
// Prepare original request // Prepare original request
loginbody, _ := base64.StdEncoding.DecodeString(r.FormValue("loginbody")) bodyDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(r.FormValue("loginbody")))
req, _ := http.NewRequestWithContext(r.Context(), r.FormValue("loginmethod"), r.RequestURI, bytes.NewReader(loginbody)) origReq, _ := http.NewRequestWithContext(r.Context(), r.FormValue("loginmethod"), r.RequestURI, bodyDecoder)
// Copy original headers headerDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(r.FormValue("loginheaders")))
loginheaders, _ := base64.StdEncoding.DecodeString(r.FormValue("loginheaders")) _ = json.NewDecoder(headerDecoder).Decode(&origReq.Header)
var headers http.Header
_ = json.Unmarshal(loginheaders, &headers)
for k, v := range headers {
req.Header[k] = v
}
// Cookie // Cookie
ses, err := a.loginSessions.Get(r, "l") ses, err := a.loginSessions.Get(r, "l")
if err != nil { if err != nil {
@ -121,8 +132,8 @@ func (a *goBlog) checkLogin(w http.ResponseWriter, r *http.Request) bool {
return true return true
} }
// Serve original request // Serve original request
setLoggedIn(req, true) setLoggedIn(origReq, true)
a.d.ServeHTTP(w, req) a.d.ServeHTTP(w, origReq)
return true return true
} }

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"bytes"
"context" "context"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
@ -11,6 +10,7 @@ import (
"time" "time"
"github.com/dchest/captcha" "github.com/dchest/captcha"
"go.goblog.app/app/pkgs/bufferpool"
"go.goblog.app/app/pkgs/contenttype" "go.goblog.app/app/pkgs/contenttype"
) )
@ -40,6 +40,8 @@ func (a *goBlog) captchaMiddleware(next http.Handler) http.Handler {
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), captchaSolvedKey, true))) next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), captchaSolvedKey, true)))
return return
} }
// Remember to close body
defer r.Body.Close()
// Get captcha ID // Get captcha ID
captchaId := "" captchaId := ""
if sesCaptchaId, ok := ses.Values["captchaid"]; ok { if sesCaptchaId, ok := ses.Values["captchaid"]; ok {
@ -54,13 +56,26 @@ func (a *goBlog) captchaMiddleware(next http.Handler) http.Handler {
ses.Values["captchaid"] = captchaId ses.Values["captchaid"] = captchaId
} }
// Encode original request // Encode original request
h, _ := json.Marshal(r.Header) headerBuffer, bodyBuffer := bufferpool.Get(), bufferpool.Get()
b, _ := io.ReadAll(io.LimitReader(r.Body, 20000000)) // Only allow 20 MB defer bufferpool.Put(headerBuffer, bodyBuffer)
_ = r.Body.Close() // Encode headers
if len(b) == 0 { headerEncoder := base64.NewEncoder(base64.StdEncoding, headerBuffer)
_ = json.NewEncoder(headerEncoder).Encode(r.Header)
_ = headerEncoder.Close()
// Encode body
bodyEncoder := base64.NewEncoder(base64.StdEncoding, bodyBuffer)
limit := int64(1000 * 1000) // 1 MB
written, _ := io.Copy(bodyEncoder, io.LimitReader(r.Body, limit))
if written == 0 {
// Maybe it's a form // Maybe it's a form
_ = r.ParseForm() _ = r.ParseForm()
b = []byte(r.PostForm.Encode()) // Encode form
written, _ = io.Copy(bodyEncoder, strings.NewReader(r.Form.Encode()))
}
bodyEncoder.Close()
if written >= limit {
a.serveError(w, r, "Request body too large, first login", http.StatusRequestEntityTooLarge)
return
} }
// Render captcha // Render captcha
_ = ses.Save(r, w) _ = ses.Save(r, w)
@ -68,8 +83,8 @@ func (a *goBlog) captchaMiddleware(next http.Handler) http.Handler {
a.renderWithStatusCode(w, r, http.StatusUnauthorized, a.renderCaptcha, &renderData{ a.renderWithStatusCode(w, r, http.StatusUnauthorized, a.renderCaptcha, &renderData{
Data: &captchaRenderData{ Data: &captchaRenderData{
captchaMethod: r.Method, captchaMethod: r.Method,
captchaHeaders: base64.StdEncoding.EncodeToString(h), captchaHeaders: headerBuffer.String(),
captchaBody: base64.StdEncoding.EncodeToString(b), captchaBody: bodyBuffer.String(),
captchaId: captchaId, captchaId: captchaId,
}, },
}) })
@ -94,16 +109,11 @@ func (a *goBlog) checkCaptcha(w http.ResponseWriter, r *http.Request) bool {
if r.FormValue("captchaaction") != "captcha" { if r.FormValue("captchaaction") != "captcha" {
return false return false
} }
// Decode and prepare original request // Prepare original request
captchabody, _ := base64.StdEncoding.DecodeString(r.FormValue("captchabody")) bodyDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(r.FormValue("captchabody")))
origReq, _ := http.NewRequestWithContext(r.Context(), r.FormValue("captchamethod"), r.RequestURI, bytes.NewReader(captchabody)) origReq, _ := http.NewRequestWithContext(r.Context(), r.FormValue("captchamethod"), r.RequestURI, bodyDecoder)
// Copy original headers headerDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(r.FormValue("captchaheaders")))
captchaheaders, _ := base64.StdEncoding.DecodeString(r.FormValue("captchaheaders")) _ = json.NewDecoder(headerDecoder).Decode(&origReq.Header)
var headers http.Header
_ = json.Unmarshal(captchaheaders, &headers)
for k, v := range headers {
origReq.Header[k] = v
}
// Get session // Get session
ses, err := a.captchaSessions.Get(r, "c") ses, err := a.captchaSessions.Get(r, "c")
if err != nil { if err != nil {