From f6b1712edaa48f695ba6fdeeadcf00561726cc6a Mon Sep 17 00:00:00 2001 From: Chris Sexton <3216719+chrissexton@users.noreply.github.com> Date: Tue, 27 Feb 2024 12:02:16 -0500 Subject: [PATCH] admin: use htmx and templ for app pass --- bot/index.templ | 5 +- bot/index_templ.go | 4 +- plugins/admin/apppass.templ | 77 ++++++++++++ plugins/admin/apppass_templ.go | 210 +++++++++++++++++++++++++++++++++ plugins/admin/vars.html | 36 ------ plugins/admin/web.go | 100 +++++++--------- 6 files changed, 333 insertions(+), 99 deletions(-) create mode 100644 plugins/admin/apppass.templ create mode 100644 plugins/admin/apppass_templ.go delete mode 100644 plugins/admin/vars.html diff --git a/bot/index.templ b/bot/index.templ index 5c96abd..8397b71 100644 --- a/bot/index.templ +++ b/bot/index.templ @@ -5,8 +5,8 @@ templ (b *bot) index() { - - + + catbase @@ -16,6 +16,7 @@ templ (b *bot) index() { @b.Nav("", b.GetWebNavigation()) + } \ No newline at end of file diff --git a/bot/index_templ.go b/bot/index_templ.go index bb89e29..33e4dd7 100644 --- a/bot/index_templ.go +++ b/bot/index_templ.go @@ -23,7 +23,7 @@ func (b *bot) index() templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("catbase
") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("catbase
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -31,7 +31,7 @@ func (b *bot) index() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/plugins/admin/apppass.templ b/plugins/admin/apppass.templ new file mode 100644 index 0000000..133bcc6 --- /dev/null +++ b/plugins/admin/apppass.templ @@ -0,0 +1,77 @@ +package admin + +import "fmt" + +templ (a *AdminPlugin) page() { + + + + + + + Vars + + + +
+ +
+
+
+
+ +
+
+ +
+
+ + +
+
+
+ +
+
+
+
+ + + + +} + +templ (a *AdminPlugin) showPassword(entry PassEntry) { +
ID{ fmt.Sprintf("%d", entry.ID) }
+
Password{ entry.Secret }:{ entry.Pass }
+} + +templ (a *AdminPlugin) entries(items []PassEntry) { +
+ if len(items) == 0 { + No items + } + +
+} + +templ renderError(err error) { +
{ err.Error() }
+} \ No newline at end of file diff --git a/plugins/admin/apppass_templ.go b/plugins/admin/apppass_templ.go new file mode 100644 index 0000000..c12f44d --- /dev/null +++ b/plugins/admin/apppass_templ.go @@ -0,0 +1,210 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.543 +package admin + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import "context" +import "io" +import "bytes" + +import "fmt" + +func (a *AdminPlugin) page() templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("Vars
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func (a *AdminPlugin) showPassword(entry PassEntry) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var2 := templ.GetChildren(ctx) + if templ_7745c5c3_Var2 == nil { + templ_7745c5c3_Var2 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, 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)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `plugins/admin/apppass.templ`, Line: 48, Col: 59} + } + _, 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") + 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/apppass.templ`, Line: 49, Col: 50} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(":") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + 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/apppass.templ`, Line: 49, Col: 65} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func (a *AdminPlugin) entries(items []PassEntry) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var6 := templ.GetChildren(ctx) + if templ_7745c5c3_Var6 == nil { + templ_7745c5c3_Var6 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(items) == 0 { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("No items") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func renderError(err error) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var8 := templ.GetChildren(ctx) + if templ_7745c5c3_Var8 == nil { + templ_7745c5c3_Var8 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + 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/apppass.templ`, Line: 75, Col: 22} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} diff --git a/plugins/admin/vars.html b/plugins/admin/vars.html deleted file mode 100644 index 74b0bf5..0000000 --- a/plugins/admin/vars.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - vars - - - - -
- -
- - - - - - - - - {{range .Items}} - - - - {{else}} - - - - {{end}} - -
KeyValue
{{.Key}}{{.Value}}
No data
-
- - - \ No newline at end of file diff --git a/plugins/admin/web.go b/plugins/admin/web.go index c6eafa2..6caa8cb 100644 --- a/plugins/admin/web.go +++ b/plugins/admin/web.go @@ -1,18 +1,20 @@ package admin import ( + "context" "crypto/md5" "crypto/rand" "embed" "encoding/json" "fmt" - "io/ioutil" - "net/http" - "strings" - "github.com/go-chi/chi/v5" "github.com/rs/zerolog/log" "golang.org/x/crypto/bcrypt" + "io" + "io/ioutil" + "net/http" + "net/url" + "strconv" ) //go:embed *.html @@ -20,7 +22,6 @@ var embeddedFS embed.FS func (p *AdminPlugin) registerWeb() { r := chi.NewRouter() - r.HandleFunc("/api", p.handleVarsAPI) r.HandleFunc("/", p.handleVars) p.bot.RegisterWebName(r, "/vars", "Variables") r = chi.NewRouter() @@ -31,8 +32,7 @@ func (p *AdminPlugin) registerWeb() { } func (p *AdminPlugin) handleAppPass(w http.ResponseWriter, r *http.Request) { - index, _ := embeddedFS.ReadFile("apppass.html") - w.Write(index) + p.page().Render(r.Context(), w) } type PassEntry struct { @@ -76,26 +76,41 @@ func (p *AdminPlugin) handleAppPassCheck(w http.ResponseWriter, r *http.Request) } 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") + } + 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"` - }{} - body, _ := ioutil.ReadAll(r.Body) - _ = json.Unmarshal(body, &req) + }{ + password, + PassEntry{ + ID: id, + Secret: secret, + }, + } if req.PassEntry.Secret == "" { - writeErr(w, fmt.Errorf("missing secret")) + writeErr(r.Context(), w, fmt.Errorf("missing secret")) return } if req.Password == "" || !p.bot.CheckPassword(req.PassEntry.Secret, req.Password) { - writeErr(w, fmt.Errorf("missing or incorrect password")) + writeErr(r.Context(), w, fmt.Errorf("missing or incorrect password")) return } switch r.Method { case http.MethodPut: - if req.PassEntry.Secret == "" { - writeErr(w, fmt.Errorf("missing secret")) - return - } if string(req.PassEntry.Pass) == "" { c := 10 b := make([]byte, c) @@ -120,27 +135,27 @@ func (p *AdminPlugin) handleAppPassAPI(w http.ResponseWriter, r *http.Request) { res, err := p.db.Exec(q, req.PassEntry.Secret, req.PassEntry.encodedPass, req.PassEntry.Cost) if err != nil { - writeErr(w, err) + writeErr(r.Context(), w, err) return } id, err := res.LastInsertId() if err != nil { - writeErr(w, err) + writeErr(r.Context(), w, err) return } req.PassEntry.ID = id - j, _ := json.Marshal(req.PassEntry) - fmt.Fprint(w, string(j)) + p.showPassword(req.PassEntry).Render(r.Context(), w) return case http.MethodDelete: + if req.PassEntry.ID <= 0 { - writeErr(w, fmt.Errorf("missing ID")) + 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(w, err) + writeErr(r.Context(), w, err) return } } @@ -148,22 +163,15 @@ func (p *AdminPlugin) handleAppPassAPI(w http.ResponseWriter, r *http.Request) { passEntries := []PassEntry{} err := p.db.Select(&passEntries, q, req.PassEntry.Secret) if err != nil { - writeErr(w, err) + writeErr(r.Context(), w, err) return } - j, _ := json.Marshal(passEntries) - _, _ = fmt.Fprint(w, string(j)) + p.entries(passEntries).Render(r.Context(), w) } -func writeErr(w http.ResponseWriter, err error) { +func writeErr(ctx context.Context, w http.ResponseWriter, err error) { log.Error().Err(err).Msg("apppass error") - j, _ := json.Marshal(struct { - Err string `json:"err"` - }{ - err.Error(), - }) - w.WriteHeader(400) - fmt.Fprint(w, string(j)) + renderError(err).Render(ctx, w) } type configEntry struct { @@ -186,29 +194,3 @@ func (p *AdminPlugin) handleVars(w http.ResponseWriter, r *http.Request) { vars(configEntries).Render(r.Context(), w) } - -func (p *AdminPlugin) handleVarsAPI(w http.ResponseWriter, r *http.Request) { - var configEntries []struct { - Key string `json:"key"` - Value string `json:"value"` - } - q := `select key, value from config` - err := p.db.Select(&configEntries, q) - if err != nil { - log.Error(). - Err(err). - Msg("Error getting config entries.") - w.WriteHeader(500) - fmt.Fprint(w, err) - return - } - for i, e := range configEntries { - if strings.Contains(e.Value, ";;") { - e.Value = strings.ReplaceAll(e.Value, ";;", ", ") - e.Value = fmt.Sprintf("[%s]", e.Value) - configEntries[i] = e - } - } - j, _ := json.Marshal(configEntries) - fmt.Fprintf(w, "%s", j) -}