From b73e64ad72c74c8dfd0c2d1f734b983234b1aefc Mon Sep 17 00:00:00 2001 From: Chris Sexton <3216719+chrissexton@users.noreply.github.com> Date: Wed, 13 Mar 2024 12:15:38 -0400 Subject: [PATCH] admin: apppass uses httpin --- plugins/admin/admin.templ | 7 +- plugins/admin/admin_templ.go | 24 ++--- plugins/admin/web.go | 188 +++++++++++++++++++---------------- 3 files changed, 117 insertions(+), 102 deletions(-) diff --git a/plugins/admin/admin.templ b/plugins/admin/admin.templ index 2626243..2dae034 100644 --- a/plugins/admin/admin.templ +++ b/plugins/admin/admin.templ @@ -24,17 +24,17 @@ templ (a *AdminPlugin) page() { -
+ } templ (a *AdminPlugin) showPassword(entry PassEntry) { -
ID{ fmt.Sprintf("%d", entry.ID) }
-
Password{ entry.Secret }:{ entry.Pass }
+
ID{ fmt.Sprintf(" %d", entry.ID) }
+
Password { entry.Secret }:{ entry.Pass }
} templ (a *AdminPlugin) entries(items []PassEntry) { @@ -51,7 +51,6 @@ templ (a *AdminPlugin) entries(items []PassEntry) { hx-delete="/apppass/api" hx-confirm={ fmt.Sprintf("Are you sure you want to delete %d?", entry.ID) } hx-target="#data" - hx-include="this,[name='password'],[name='secret']" name="id" value={ fmt.Sprintf("%d", entry.ID) }>X { fmt.Sprintf("%d", entry.ID) } diff --git a/plugins/admin/admin_templ.go b/plugins/admin/admin_templ.go index c4de530..72416e3 100644 --- a/plugins/admin/admin_templ.go +++ b/plugins/admin/admin_templ.go @@ -25,7 +25,7 @@ func (a *AdminPlugin) page() templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

App Pass

") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

App Pass

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -49,27 +49,27 @@ func (a *AdminPlugin) showPassword(entry PassEntry) templ.Component { templ_7745c5c3_Var2 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
ID") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
ID") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var3 string - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", entry.ID)) + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(" %d", entry.ID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 35, Col: 59} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 35, Col: 86} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Password") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Password") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Secret) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 36, Col: 50} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 36, Col: 77} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -82,7 +82,7 @@ func (a *AdminPlugin) showPassword(entry PassEntry) templ.Component { var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Pass) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 36, Col: 65} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 36, Col: 92} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -135,7 +135,7 @@ func (a *AdminPlugin) entries(items []PassEntry) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-target=\"#data\" hx-include=\"this,[name='password'],[name='secret']\" name=\"id\" value=\"") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-target=\"#data\" name=\"id\" value=\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -150,7 +150,7 @@ func (a *AdminPlugin) entries(items []PassEntry) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", entry.ID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 55, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 54, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -192,7 +192,7 @@ func renderError(err error) templ.Component { var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(err.Error()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 63, Col: 22} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 62, Col: 22} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -234,7 +234,7 @@ func vars(items []configEntry) templ.Component { var templ_7745c5c3_Var11 string templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(item.Key) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 79, Col: 38} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 78, Col: 38} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { @@ -247,7 +247,7 @@ func vars(items []configEntry) templ.Component { var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(item.Value) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 79, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/admin.templ`, Line: 78, Col: 61} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { diff --git a/plugins/admin/web.go b/plugins/admin/web.go index 97f16d2..d1bbc52 100644 --- a/plugins/admin/web.go +++ b/plugins/admin/web.go @@ -4,13 +4,12 @@ import ( "context" "crypto/md5" "crypto/rand" - "encoding/json" "fmt" + "github.com/ggicci/httpin" "github.com/go-chi/chi/v5" "github.com/rs/zerolog/log" "golang.org/x/crypto/bcrypt" "io" - "io/ioutil" "net/http" "net/url" "strconv" @@ -21,8 +20,15 @@ func (p *AdminPlugin) registerWeb() { r.HandleFunc("/", p.handleVars) p.bot.GetWeb().RegisterWebName(r, "/vars", "Variables") r = chi.NewRouter() - r.HandleFunc("/verify", p.handleAppPassCheck) - r.HandleFunc("/api", p.handleAppPassAPI) + r.With(httpin.NewInput(AppPassCheckReq{})). + HandleFunc("/verify", p.handleAppPassCheck) + //r.HandleFunc("/api", p.handleAppPassAPI) + r.With(httpin.NewInput(AppPassAPIReq{})). + Put("/api", p.handleAppPassAPIPut) + r.With(httpin.NewInput(AppPassAPIReq{})). + Delete("/api", p.handleAppPassAPIDelete) + r.With(httpin.NewInput(AppPassAPIReq{})). + Post("/api", p.handleAppPassAPIPost) r.HandleFunc("/", p.handleAppPass) p.bot.GetWeb().RegisterWebName(r, "/apppass", "App Pass") } @@ -32,8 +38,8 @@ func (p *AdminPlugin) handleAppPass(w http.ResponseWriter, r *http.Request) { } type PassEntry struct { - ID int64 `json:"id"` - Secret string `json:"secret"` + ID int64 `json:"id" in:"form=id;query=password"` + Secret string `json:"secret" in:"form=secret;query=password"` // Should be null unless inserting a new entry Pass string `json:"pass"` @@ -57,107 +63,117 @@ func (p *PassEntry) Compare(pass string) bool { return true } +type AppPassCheckReq struct { + Secret string `json:"secret"` + Password string `json:"password"` +} + func (p *AdminPlugin) handleAppPassCheck(w http.ResponseWriter, r *http.Request) { - req := struct { - Secret string `json:"secret"` - Password string `json:"password"` - }{} - body, _ := ioutil.ReadAll(r.Body) - _ = json.Unmarshal(body, &req) - if p.bot.CheckPassword(req.Secret, req.Password) { + input := r.Context().Value(httpin.Input).(*AppPassCheckReq) + if p.bot.CheckPassword(input.Secret, input.Password) { w.WriteHeader(204) } else { w.WriteHeader(403) } } -func (p *AdminPlugin) handleAppPassAPI(w http.ResponseWriter, r *http.Request) { - r.ParseForm() - b, _ := io.ReadAll(r.Body) - query, _ := url.ParseQuery(string(b)) - secret := r.FormValue("secret") - password := r.FormValue("password") - id, _ := strconv.ParseInt(r.FormValue("id"), 10, 64) - if !r.Form.Has("secret") && query.Has("secret") { - secret = query.Get("secret") +type AppPassAPIReq struct { + Password string `in:"form=password;query=password"` + PassEntry PassEntry +} + +func (p *AdminPlugin) checkAPIInput(w http.ResponseWriter, r *http.Request) *AppPassAPIReq { + input := r.Context().Value(httpin.Input).(*AppPassAPIReq) + + if input.Password == "" && input.PassEntry.Secret == "" { + b, _ := io.ReadAll(r.Body) + query, _ := url.ParseQuery(string(b)) + input.Password = query.Get("password") + input.PassEntry.ID, _ = strconv.ParseInt(query.Get("id"), 10, 64) + input.PassEntry.Secret = query.Get("secret") } - if !r.Form.Has("password") && query.Has("password") { - password = query.Get("password") - } - if !r.Form.Has("id") && query.Has("id") { - id, _ = strconv.ParseInt(query.Get("id"), 10, 64) - } - req := struct { - Password string `json:"password"` - PassEntry PassEntry `json:"passEntry"` - }{ - password, - PassEntry{ - ID: id, - Secret: secret, - }, - } - if req.PassEntry.Secret == "" { + + log.Printf("checkAPIInput: %#v", input) + + if input.PassEntry.Secret == "" { writeErr(r.Context(), w, fmt.Errorf("missing secret")) - return + return nil } - if req.Password == "" || !p.bot.CheckPassword(req.PassEntry.Secret, req.Password) { + if input.Password == "" || !p.bot.CheckPassword(input.PassEntry.Secret, input.Password) { writeErr(r.Context(), w, fmt.Errorf("missing or incorrect password")) + return nil + } + return input +} + +func (p *AdminPlugin) handleAppPassAPIPut(w http.ResponseWriter, r *http.Request) { + input := p.checkAPIInput(w, r) + if input == nil { return } - switch r.Method { - case http.MethodPut: - if string(req.PassEntry.Pass) == "" { - c := 10 - b := make([]byte, c) - _, err := rand.Read(b) - if err != nil { - fmt.Println("error:", err) - return - } - req.PassEntry.Pass = fmt.Sprintf("%x", md5.Sum(b)) - } - q := `insert into apppass (secret, encoded_pass, cost) values (?, ?, ?)` - req.PassEntry.EncodePass() - - check := bcrypt.CompareHashAndPassword(req.PassEntry.encodedPass, []byte(req.PassEntry.Pass)) - - log.Debug(). - Str("secret", req.PassEntry.Secret). - Str("encoded", string(req.PassEntry.encodedPass)). - Str("password", string(req.PassEntry.Pass)). - Interface("check", check). - Msg("debug pass creation") - - res, err := p.db.Exec(q, req.PassEntry.Secret, req.PassEntry.encodedPass, req.PassEntry.Cost) + if string(input.PassEntry.Pass) == "" { + c := 10 + b := make([]byte, c) + _, err := rand.Read(b) if err != nil { - writeErr(r.Context(), w, err) + fmt.Println("error:", err) return } - id, err := res.LastInsertId() - if err != nil { - writeErr(r.Context(), w, err) - return - } - req.PassEntry.ID = id - p.showPassword(req.PassEntry).Render(r.Context(), w) + input.PassEntry.Pass = fmt.Sprintf("%x", md5.Sum(b)) + } + q := `insert into apppass (secret, encoded_pass, cost) values (?, ?, ?)` + input.PassEntry.EncodePass() + + check := bcrypt.CompareHashAndPassword(input.PassEntry.encodedPass, []byte(input.PassEntry.Pass)) + + log.Debug(). + Str("secret", input.PassEntry.Secret). + Str("encoded", string(input.PassEntry.encodedPass)). + Str("password", string(input.PassEntry.Pass)). + Interface("check", check). + Msg("debug pass creation") + + res, err := p.db.Exec(q, input.PassEntry.Secret, input.PassEntry.encodedPass, input.PassEntry.Cost) + if err != nil { + writeErr(r.Context(), w, err) return - case http.MethodDelete: + } + id, err := res.LastInsertId() + if err != nil { + writeErr(r.Context(), w, err) + return + } + input.PassEntry.ID = id + p.showPassword(input.PassEntry).Render(r.Context(), w) +} - if req.PassEntry.ID <= 0 { - writeErr(r.Context(), w, fmt.Errorf("missing ID")) - return - } - q := `delete from apppass where id = ?` - _, err := p.db.Exec(q, req.PassEntry.ID) - if err != nil { - writeErr(r.Context(), w, err) - return - } +func (p *AdminPlugin) handleAppPassAPIDelete(w http.ResponseWriter, r *http.Request) { + input := p.checkAPIInput(w, r) + if input == nil { + return + } + if input.PassEntry.ID <= 0 { + writeErr(r.Context(), w, fmt.Errorf("missing ID")) + return + } + q := `delete from apppass where id = ?` + _, err := p.db.Exec(q, input.PassEntry.ID) + if err != nil { + writeErr(r.Context(), w, err) + return + } + p.handleAppPassAPIPost(w, r) +} + +func (p *AdminPlugin) handleAppPassAPIPost(w http.ResponseWriter, r *http.Request) { + input := p.checkAPIInput(w, r) + if input == nil { + return } q := `select id,secret from apppass where secret = ?` passEntries := []PassEntry{} - err := p.db.Select(&passEntries, q, req.PassEntry.Secret) + + err := p.db.Select(&passEntries, q, input.PassEntry.Secret) if err != nil { writeErr(r.Context(), w, err) return