2021-07-21 13:54:29 +00:00
|
|
|
package admin
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/md5"
|
|
|
|
"crypto/rand"
|
2021-07-29 16:33:03 +00:00
|
|
|
"embed"
|
2021-07-21 13:54:29 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2021-12-20 17:40:10 +00:00
|
|
|
bh "github.com/timshannon/bolthold"
|
|
|
|
"github.com/velour/catbase/config"
|
2021-07-21 13:54:29 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
|
2021-07-21 18:52:45 +00:00
|
|
|
"github.com/go-chi/chi/v5"
|
2021-07-21 13:54:29 +00:00
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
)
|
|
|
|
|
2021-07-29 16:33:03 +00:00
|
|
|
//go:embed *.html
|
|
|
|
var embeddedFS embed.FS
|
|
|
|
|
2021-07-21 13:54:29 +00:00
|
|
|
func (p *AdminPlugin) registerWeb() {
|
2021-07-21 18:52:45 +00:00
|
|
|
r := chi.NewRouter()
|
|
|
|
r.HandleFunc("/api", p.handleVarsAPI)
|
|
|
|
r.HandleFunc("/", p.handleVars)
|
2021-07-28 15:32:59 +00:00
|
|
|
p.bot.RegisterWebName(r, "/vars", "Variables")
|
2021-07-21 18:52:45 +00:00
|
|
|
r = chi.NewRouter()
|
|
|
|
r.HandleFunc("/verify", p.handleAppPassCheck)
|
|
|
|
r.HandleFunc("/api", p.handleAppPassAPI)
|
|
|
|
r.HandleFunc("/", p.handleAppPass)
|
2021-07-28 15:32:59 +00:00
|
|
|
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) {
|
2021-07-29 16:33:03 +00:00
|
|
|
index, _ := embeddedFS.ReadFile("apppass.html")
|
|
|
|
w.Write(index)
|
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) {
|
|
|
|
req := struct {
|
|
|
|
Password string `json:"password"`
|
|
|
|
PassEntry PassEntry `json:"passEntry"`
|
|
|
|
}{}
|
|
|
|
body, _ := ioutil.ReadAll(r.Body)
|
|
|
|
_ = json.Unmarshal(body, &req)
|
|
|
|
if req.PassEntry.Secret == "" {
|
|
|
|
writeErr(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"))
|
|
|
|
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)
|
|
|
|
_, err := rand.Read(b)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("error:", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
req.PassEntry.Pass = fmt.Sprintf("%x", md5.Sum(b))
|
|
|
|
}
|
2021-12-20 17:40:10 +00:00
|
|
|
|
2021-07-21 13:54:29 +00:00
|
|
|
req.PassEntry.EncodePass()
|
|
|
|
|
|
|
|
check := bcrypt.CompareHashAndPassword(req.PassEntry.encodedPass, []byte(req.PassEntry.Pass))
|
|
|
|
|
2021-12-20 17:40:10 +00:00
|
|
|
entry := PassEntry{
|
|
|
|
Secret: req.PassEntry.Secret,
|
|
|
|
encodedPass: req.PassEntry.encodedPass,
|
|
|
|
Cost: req.PassEntry.Cost,
|
|
|
|
Pass: "",
|
|
|
|
}
|
|
|
|
|
2021-07-21 13:54:29 +00:00
|
|
|
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")
|
|
|
|
|
2021-12-20 17:40:10 +00:00
|
|
|
err := p.store.Insert(bh.NextSequence(), &entry)
|
2021-07-21 13:54:29 +00:00
|
|
|
if err != nil {
|
|
|
|
writeErr(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
j, _ := json.Marshal(req.PassEntry)
|
|
|
|
fmt.Fprint(w, string(j))
|
|
|
|
return
|
|
|
|
case http.MethodDelete:
|
|
|
|
if req.PassEntry.ID <= 0 {
|
|
|
|
writeErr(w, fmt.Errorf("missing ID"))
|
|
|
|
return
|
|
|
|
}
|
2021-12-20 17:40:10 +00:00
|
|
|
err := p.store.Delete(req.PassEntry.ID, PassEntry{})
|
2021-07-21 13:54:29 +00:00
|
|
|
if err != nil {
|
|
|
|
writeErr(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
passEntries := []PassEntry{}
|
2021-12-20 17:40:10 +00:00
|
|
|
err := p.store.Find(&passEntries, bh.Where("secret").Eq(req.PassEntry.Secret))
|
2021-07-21 13:54:29 +00:00
|
|
|
if err != nil {
|
|
|
|
writeErr(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
j, _ := json.Marshal(passEntries)
|
|
|
|
_, _ = fmt.Fprint(w, string(j))
|
|
|
|
}
|
|
|
|
|
|
|
|
func writeErr(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))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *AdminPlugin) handleVars(w http.ResponseWriter, r *http.Request) {
|
2021-07-29 16:33:03 +00:00
|
|
|
index, _ := embeddedFS.ReadFile("vars.html")
|
|
|
|
w.Write(index)
|
2021-07-21 13:54:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *AdminPlugin) handleVarsAPI(w http.ResponseWriter, r *http.Request) {
|
2021-12-20 17:40:10 +00:00
|
|
|
configEntries := []config.Value{}
|
|
|
|
err := p.store.Find(&configEntries, &bh.Query{})
|
2021-07-21 13:54:29 +00:00
|
|
|
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)
|
|
|
|
}
|