admin: apppass uses httpin

This commit is contained in:
Chris Sexton 2024-03-13 12:15:38 -04:00
parent a6d224c87b
commit b73e64ad72
3 changed files with 117 additions and 102 deletions

View File

@ -24,17 +24,17 @@ templ (a *AdminPlugin) page() {
<button hx-put="/apppass/api" hx-target="#data" class="submit success button">New</button> <button hx-put="/apppass/api" hx-target="#data" class="submit success button">New</button>
</div> </div>
</div> </div>
</form>
<div class="grid-x"> <div class="grid-x">
<div class="cell" id="data"></div> <div class="cell" id="data"></div>
</div> </div>
</form>
</div> </div>
} }
templ (a *AdminPlugin) showPassword(entry PassEntry) { templ (a *AdminPlugin) showPassword(entry PassEntry) {
<div><span>ID</span><span>{ fmt.Sprintf("%d", entry.ID) }</span></div> <div><span style="margin-right: 2em">ID</span><span>{ fmt.Sprintf(" %d", entry.ID) }</span></div>
<div><span>Password</span><span>{ entry.Secret }:{ entry.Pass }</span></div> <div><span style="margin-right: 2em">Password</span><span> { entry.Secret }:{ entry.Pass }</span></div>
} }
templ (a *AdminPlugin) entries(items []PassEntry) { templ (a *AdminPlugin) entries(items []PassEntry) {
@ -51,7 +51,6 @@ templ (a *AdminPlugin) entries(items []PassEntry) {
hx-delete="/apppass/api" hx-delete="/apppass/api"
hx-confirm={ fmt.Sprintf("Are you sure you want to delete %d?", entry.ID) } hx-confirm={ fmt.Sprintf("Are you sure you want to delete %d?", entry.ID) }
hx-target="#data" hx-target="#data"
hx-include="this,[name='password'],[name='secret']"
name="id" value={ fmt.Sprintf("%d", entry.ID) }>X</button> name="id" value={ fmt.Sprintf("%d", entry.ID) }>X</button>
{ fmt.Sprintf("%d", entry.ID) } { fmt.Sprintf("%d", entry.ID) }
</li> </li>

View File

@ -25,7 +25,7 @@ func (a *AdminPlugin) page() templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent templ_7745c5c3_Var1 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"grid-container\"><form><div class=\"grid-x grid-margin-x align-bottom\"><h2>App Pass</h2></div><div class=\"grid-x grid-margin-x align-bottom\"><div class=\"cell auto\"><label for=\"password\">Password: <input type=\"text\" name=\"password\"></label></div><div class=\"cell auto\"><label for=\"secret\">Secret: <input type=\"text\" name=\"secret\"></label></div><div class=\"cell auto\"><button hx-post=\"/apppass/api\" hx-target=\"#data\" class=\"button\">List</button> <button hx-put=\"/apppass/api\" hx-target=\"#data\" class=\"submit success button\">New</button></div></div></form><div class=\"grid-x\"><div class=\"cell\" id=\"data\"></div></div></div>") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"grid-container\"><form><div class=\"grid-x grid-margin-x align-bottom\"><h2>App Pass</h2></div><div class=\"grid-x grid-margin-x align-bottom\"><div class=\"cell auto\"><label for=\"password\">Password: <input type=\"text\" name=\"password\"></label></div><div class=\"cell auto\"><label for=\"secret\">Secret: <input type=\"text\" name=\"secret\"></label></div><div class=\"cell auto\"><button hx-post=\"/apppass/api\" hx-target=\"#data\" class=\"button\">List</button> <button hx-put=\"/apppass/api\" hx-target=\"#data\" class=\"submit success button\">New</button></div></div><div class=\"grid-x\"><div class=\"cell\" id=\"data\"></div></div></form></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -49,27 +49,27 @@ func (a *AdminPlugin) showPassword(entry PassEntry) templ.Component {
templ_7745c5c3_Var2 = templ.NopComponent templ_7745c5c3_Var2 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div><span>ID</span><span>") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div><span style=\"margin-right: 2em\">ID</span><span>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var3 string 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 { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></div><div><span>Password</span><span>") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></div><div><span style=\"margin-right: 2em\">Password</span><span>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var4 string var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Secret) templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Secret)
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -82,7 +82,7 @@ func (a *AdminPlugin) showPassword(entry PassEntry) templ.Component {
var templ_7745c5c3_Var5 string var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Pass) templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Pass)
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -135,7 +135,7 @@ func (a *AdminPlugin) entries(items []PassEntry) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-target=\"#data\" hx-include=\"this,[name=&#39;password&#39;],[name=&#39;secret&#39;]\" name=\"id\" value=\"") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-target=\"#data\" name=\"id\" value=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -150,7 +150,7 @@ func (a *AdminPlugin) entries(items []PassEntry) templ.Component {
var templ_7745c5c3_Var7 string var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", entry.ID)) templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", entry.ID))
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -192,7 +192,7 @@ func renderError(err error) templ.Component {
var templ_7745c5c3_Var9 string var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(err.Error()) templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(err.Error())
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -234,7 +234,7 @@ func vars(items []configEntry) templ.Component {
var templ_7745c5c3_Var11 string var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(item.Key) templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(item.Key)
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -247,7 +247,7 @@ func vars(items []configEntry) templ.Component {
var templ_7745c5c3_Var12 string var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(item.Value) templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(item.Value)
if templ_7745c5c3_Err != nil { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {

View File

@ -4,13 +4,12 @@ import (
"context" "context"
"crypto/md5" "crypto/md5"
"crypto/rand" "crypto/rand"
"encoding/json"
"fmt" "fmt"
"github.com/ggicci/httpin"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
@ -21,8 +20,15 @@ func (p *AdminPlugin) registerWeb() {
r.HandleFunc("/", p.handleVars) r.HandleFunc("/", p.handleVars)
p.bot.GetWeb().RegisterWebName(r, "/vars", "Variables") p.bot.GetWeb().RegisterWebName(r, "/vars", "Variables")
r = chi.NewRouter() r = chi.NewRouter()
r.HandleFunc("/verify", p.handleAppPassCheck) r.With(httpin.NewInput(AppPassCheckReq{})).
r.HandleFunc("/api", p.handleAppPassAPI) 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) r.HandleFunc("/", p.handleAppPass)
p.bot.GetWeb().RegisterWebName(r, "/apppass", "App Pass") 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 { type PassEntry struct {
ID int64 `json:"id"` ID int64 `json:"id" in:"form=id;query=password"`
Secret string `json:"secret"` Secret string `json:"secret" in:"form=secret;query=password"`
// Should be null unless inserting a new entry // Should be null unless inserting a new entry
Pass string `json:"pass"` Pass string `json:"pass"`
@ -57,57 +63,55 @@ func (p *PassEntry) Compare(pass string) bool {
return true return true
} }
func (p *AdminPlugin) handleAppPassCheck(w http.ResponseWriter, r *http.Request) { type AppPassCheckReq struct {
req := struct {
Secret string `json:"secret"` Secret string `json:"secret"`
Password string `json:"password"` Password string `json:"password"`
}{} }
body, _ := ioutil.ReadAll(r.Body)
_ = json.Unmarshal(body, &req) func (p *AdminPlugin) handleAppPassCheck(w http.ResponseWriter, r *http.Request) {
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) w.WriteHeader(204)
} else { } else {
w.WriteHeader(403) w.WriteHeader(403)
} }
} }
func (p *AdminPlugin) handleAppPassAPI(w http.ResponseWriter, r *http.Request) { type AppPassAPIReq struct {
r.ParseForm() 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) b, _ := io.ReadAll(r.Body)
query, _ := url.ParseQuery(string(b)) query, _ := url.ParseQuery(string(b))
secret := r.FormValue("secret") input.Password = query.Get("password")
password := r.FormValue("password") input.PassEntry.ID, _ = strconv.ParseInt(query.Get("id"), 10, 64)
id, _ := strconv.ParseInt(r.FormValue("id"), 10, 64) input.PassEntry.Secret = query.Get("secret")
if !r.Form.Has("secret") && query.Has("secret") {
secret = query.Get("secret")
} }
if !r.Form.Has("password") && query.Has("password") {
password = query.Get("password") log.Printf("checkAPIInput: %#v", input)
}
if !r.Form.Has("id") && query.Has("id") { if input.PassEntry.Secret == "" {
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 == "" {
writeErr(r.Context(), w, fmt.Errorf("missing 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")) 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 return
} }
switch r.Method { if string(input.PassEntry.Pass) == "" {
case http.MethodPut:
if string(req.PassEntry.Pass) == "" {
c := 10 c := 10
b := make([]byte, c) b := make([]byte, c)
_, err := rand.Read(b) _, err := rand.Read(b)
@ -115,21 +119,21 @@ func (p *AdminPlugin) handleAppPassAPI(w http.ResponseWriter, r *http.Request) {
fmt.Println("error:", err) fmt.Println("error:", err)
return return
} }
req.PassEntry.Pass = fmt.Sprintf("%x", md5.Sum(b)) input.PassEntry.Pass = fmt.Sprintf("%x", md5.Sum(b))
} }
q := `insert into apppass (secret, encoded_pass, cost) values (?, ?, ?)` q := `insert into apppass (secret, encoded_pass, cost) values (?, ?, ?)`
req.PassEntry.EncodePass() input.PassEntry.EncodePass()
check := bcrypt.CompareHashAndPassword(req.PassEntry.encodedPass, []byte(req.PassEntry.Pass)) check := bcrypt.CompareHashAndPassword(input.PassEntry.encodedPass, []byte(input.PassEntry.Pass))
log.Debug(). log.Debug().
Str("secret", req.PassEntry.Secret). Str("secret", input.PassEntry.Secret).
Str("encoded", string(req.PassEntry.encodedPass)). Str("encoded", string(input.PassEntry.encodedPass)).
Str("password", string(req.PassEntry.Pass)). Str("password", string(input.PassEntry.Pass)).
Interface("check", check). Interface("check", check).
Msg("debug pass creation") Msg("debug pass creation")
res, err := p.db.Exec(q, req.PassEntry.Secret, req.PassEntry.encodedPass, req.PassEntry.Cost) res, err := p.db.Exec(q, input.PassEntry.Secret, input.PassEntry.encodedPass, input.PassEntry.Cost)
if err != nil { if err != nil {
writeErr(r.Context(), w, err) writeErr(r.Context(), w, err)
return return
@ -139,25 +143,37 @@ func (p *AdminPlugin) handleAppPassAPI(w http.ResponseWriter, r *http.Request) {
writeErr(r.Context(), w, err) writeErr(r.Context(), w, err)
return return
} }
req.PassEntry.ID = id input.PassEntry.ID = id
p.showPassword(req.PassEntry).Render(r.Context(), w) p.showPassword(input.PassEntry).Render(r.Context(), w)
return }
case http.MethodDelete:
if req.PassEntry.ID <= 0 { 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")) writeErr(r.Context(), w, fmt.Errorf("missing ID"))
return return
} }
q := `delete from apppass where id = ?` q := `delete from apppass where id = ?`
_, err := p.db.Exec(q, req.PassEntry.ID) _, err := p.db.Exec(q, input.PassEntry.ID)
if err != nil { if err != nil {
writeErr(r.Context(), w, err) writeErr(r.Context(), w, err)
return 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 = ?` q := `select id,secret from apppass where secret = ?`
passEntries := []PassEntry{} passEntries := []PassEntry{}
err := p.db.Select(&passEntries, q, req.PassEntry.Secret)
err := p.db.Select(&passEntries, q, input.PassEntry.Secret)
if err != nil { if err != nil {
writeErr(r.Context(), w, err) writeErr(r.Context(), w, err)
return return