mirror of https://github.com/jlelse/GoBlog
Rework login and captcha forms
This commit is contained in:
parent
63a069cec8
commit
0437f61ba9
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
46
captcha.go
46
captcha.go
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue