catbase/plugins/admin/web.go

197 lines
4.8 KiB
Go
Raw Normal View History

2021-07-21 13:54:29 +00:00
package admin
import (
2024-02-27 17:02:16 +00:00
"context"
2021-07-21 13:54:29 +00:00
"crypto/md5"
"crypto/rand"
"embed"
2021-07-21 13:54:29 +00:00
"encoding/json"
"fmt"
"github.com/go-chi/chi/v5"
2021-07-21 13:54:29 +00:00
"github.com/rs/zerolog/log"
"golang.org/x/crypto/bcrypt"
2024-02-27 17:02:16 +00:00
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
2021-07-21 13:54:29 +00:00
)
//go:embed *.html
var embeddedFS embed.FS
2021-07-21 13:54:29 +00:00
func (p *AdminPlugin) registerWeb() {
r := chi.NewRouter()
r.HandleFunc("/", p.handleVars)
p.bot.RegisterWebName(r, "/vars", "Variables")
r = chi.NewRouter()
r.HandleFunc("/verify", p.handleAppPassCheck)
r.HandleFunc("/api", p.handleAppPassAPI)
r.HandleFunc("/", p.handleAppPass)
p.bot.RegisterWebName(r, "/apppass", "App Pass")
2021-07-21 13:54:29 +00:00
}
func (p *AdminPlugin) handleAppPass(w http.ResponseWriter, r *http.Request) {
2024-02-27 17:02:16 +00:00
p.page().Render(r.Context(), w)
2021-07-21 13:54:29 +00:00
}
type PassEntry struct {
ID int64 `json:"id"`
Secret string `json:"secret"`
// Should be null unless inserting a new entry
Pass string `json:"pass"`
encodedPass []byte `json:"encodedPass"`
Cost int `json:"cost"`
}
func (p *PassEntry) EncodePass() {
encoded, err := bcrypt.GenerateFromPassword([]byte(p.Pass), p.Cost)
if err != nil {
log.Error().Err(err).Msg("could not hash password")
}
p.encodedPass = encoded
}
func (p *PassEntry) Compare(pass string) bool {
if err := bcrypt.CompareHashAndPassword(p.encodedPass, []byte(pass)); err != nil {
log.Error().Err(err).Msg("failure to match password")
return false
}
return true
}
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) {
w.WriteHeader(204)
} else {
w.WriteHeader(403)
}
}
func (p *AdminPlugin) handleAppPassAPI(w http.ResponseWriter, r *http.Request) {
2024-02-27 17:02:16 +00:00
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)
}
2021-07-21 13:54:29 +00:00
req := struct {
Password string `json:"password"`
PassEntry PassEntry `json:"passEntry"`
2024-02-27 17:02:16 +00:00
}{
password,
PassEntry{
ID: id,
Secret: secret,
},
}
2021-07-21 13:54:29 +00:00
if req.PassEntry.Secret == "" {
2024-02-27 17:02:16 +00:00
writeErr(r.Context(), w, fmt.Errorf("missing secret"))
2021-07-21 13:54:29 +00:00
return
}
if req.Password == "" || !p.bot.CheckPassword(req.PassEntry.Secret, req.Password) {
2024-02-27 17:02:16 +00:00
writeErr(r.Context(), w, fmt.Errorf("missing or incorrect password"))
2021-07-21 13:54:29 +00:00
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 err != nil {
2024-02-27 17:02:16 +00:00
writeErr(r.Context(), w, err)
2021-07-21 13:54:29 +00:00
return
}
id, err := res.LastInsertId()
if err != nil {
2024-02-27 17:02:16 +00:00
writeErr(r.Context(), w, err)
2021-07-21 13:54:29 +00:00
return
}
req.PassEntry.ID = id
2024-02-27 17:02:16 +00:00
p.showPassword(req.PassEntry).Render(r.Context(), w)
2021-07-21 13:54:29 +00:00
return
case http.MethodDelete:
2024-02-27 17:02:16 +00:00
2021-07-21 13:54:29 +00:00
if req.PassEntry.ID <= 0 {
2024-02-27 17:02:16 +00:00
writeErr(r.Context(), w, fmt.Errorf("missing ID"))
2021-07-21 13:54:29 +00:00
return
}
q := `delete from apppass where id = ?`
_, err := p.db.Exec(q, req.PassEntry.ID)
if err != nil {
2024-02-27 17:02:16 +00:00
writeErr(r.Context(), w, err)
2021-07-21 13:54:29 +00:00
return
}
}
q := `select id,secret from apppass where secret = ?`
passEntries := []PassEntry{}
err := p.db.Select(&passEntries, q, req.PassEntry.Secret)
if err != nil {
2024-02-27 17:02:16 +00:00
writeErr(r.Context(), w, err)
2021-07-21 13:54:29 +00:00
return
}
2024-02-27 17:02:16 +00:00
p.entries(passEntries).Render(r.Context(), w)
2021-07-21 13:54:29 +00:00
}
2024-02-27 17:02:16 +00:00
func writeErr(ctx context.Context, w http.ResponseWriter, err error) {
2021-07-21 13:54:29 +00:00
log.Error().Err(err).Msg("apppass error")
2024-02-27 17:02:16 +00:00
renderError(err).Render(ctx, w)
2021-07-21 13:54:29 +00:00
}
2023-08-17 19:43:27 +00:00
type configEntry struct {
Key string `json:"key"`
Value string `json:"value"`
}
2021-07-21 13:54:29 +00:00
func (p *AdminPlugin) handleVars(w http.ResponseWriter, r *http.Request) {
2023-08-17 19:43:27 +00:00
var configEntries []configEntry
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
}
2024-02-27 15:19:15 +00:00
vars(configEntries).Render(r.Context(), w)
2021-07-21 13:54:29 +00:00
}