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"
|
|
|
|
"fmt"
|
2024-03-13 16:15:38 +00:00
|
|
|
"github.com/ggicci/httpin"
|
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"
|
2024-02-27 17:02:16 +00:00
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"strconv"
|
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("/", p.handleVars)
|
2024-02-27 19:29:54 +00:00
|
|
|
p.bot.GetWeb().RegisterWebName(r, "/vars", "Variables")
|
2021-07-21 18:52:45 +00:00
|
|
|
r = chi.NewRouter()
|
2024-03-13 16:15:38 +00:00
|
|
|
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)
|
2021-07-21 18:52:45 +00:00
|
|
|
r.HandleFunc("/", p.handleAppPass)
|
2024-02-27 19:29:54 +00:00
|
|
|
p.bot.GetWeb().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 19:29:54 +00:00
|
|
|
p.bot.GetWeb().Index("App Pass", p.page()).Render(r.Context(), w)
|
2021-07-21 13:54:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type PassEntry struct {
|
2024-03-13 16:15:38 +00:00
|
|
|
ID int64 `json:"id" in:"form=id;query=password"`
|
|
|
|
Secret string `json:"secret" in:"form=secret;query=password"`
|
2021-07-21 13:54:29 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2024-03-13 16:15:38 +00:00
|
|
|
type AppPassCheckReq struct {
|
|
|
|
Secret string `json:"secret"`
|
|
|
|
Password string `json:"password"`
|
|
|
|
}
|
|
|
|
|
2021-07-21 13:54:29 +00:00
|
|
|
func (p *AdminPlugin) handleAppPassCheck(w http.ResponseWriter, r *http.Request) {
|
2024-03-13 16:15:38 +00:00
|
|
|
input := r.Context().Value(httpin.Input).(*AppPassCheckReq)
|
|
|
|
if p.bot.CheckPassword(input.Secret, input.Password) {
|
2021-07-21 13:54:29 +00:00
|
|
|
w.WriteHeader(204)
|
|
|
|
} else {
|
|
|
|
w.WriteHeader(403)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-13 16:15:38 +00:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("checkAPIInput: %#v", input)
|
|
|
|
|
|
|
|
if input.PassEntry.Secret == "" {
|
2024-02-27 17:02:16 +00:00
|
|
|
writeErr(r.Context(), w, fmt.Errorf("missing secret"))
|
2024-03-13 16:15:38 +00:00
|
|
|
return nil
|
2021-07-21 13:54:29 +00:00
|
|
|
}
|
2024-03-13 16:15:38 +00:00
|
|
|
if input.Password == "" || !p.bot.CheckPassword(input.PassEntry.Secret, input.Password) {
|
2024-02-27 17:02:16 +00:00
|
|
|
writeErr(r.Context(), w, fmt.Errorf("missing or incorrect password"))
|
2024-03-13 16:15:38 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return input
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *AdminPlugin) handleAppPassAPIPut(w http.ResponseWriter, r *http.Request) {
|
|
|
|
input := p.checkAPIInput(w, r)
|
|
|
|
if input == nil {
|
2021-07-21 13:54:29 +00:00
|
|
|
return
|
|
|
|
}
|
2024-03-13 16:15:38 +00:00
|
|
|
if string(input.PassEntry.Pass) == "" {
|
|
|
|
c := 10
|
|
|
|
b := make([]byte, c)
|
|
|
|
_, err := rand.Read(b)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("error:", err)
|
|
|
|
return
|
2021-07-21 13:54:29 +00:00
|
|
|
}
|
2024-03-13 16:15:38 +00:00
|
|
|
input.PassEntry.Pass = fmt.Sprintf("%x", md5.Sum(b))
|
|
|
|
}
|
|
|
|
q := `insert into apppass (secret, encoded_pass, cost) values (?, ?, ?)`
|
|
|
|
input.PassEntry.EncodePass()
|
2021-07-21 13:54:29 +00:00
|
|
|
|
2024-03-13 16:15:38 +00:00
|
|
|
check := bcrypt.CompareHashAndPassword(input.PassEntry.encodedPass, []byte(input.PassEntry.Pass))
|
2021-07-21 13:54:29 +00:00
|
|
|
|
2024-03-13 16:15:38 +00:00
|
|
|
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")
|
2021-07-21 13:54:29 +00:00
|
|
|
|
2024-03-13 16:15:38 +00:00
|
|
|
res, err := p.db.Exec(q, input.PassEntry.Secret, input.PassEntry.encodedPass, input.PassEntry.Cost)
|
|
|
|
if err != nil {
|
|
|
|
writeErr(r.Context(), w, err)
|
2021-07-21 13:54:29 +00:00
|
|
|
return
|
2024-03-13 16:15:38 +00:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
2024-02-27 17:02:16 +00:00
|
|
|
|
2024-03-13 16:15:38 +00:00
|
|
|
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
|
2021-07-21 13:54:29 +00:00
|
|
|
}
|
|
|
|
q := `select id,secret from apppass where secret = ?`
|
|
|
|
passEntries := []PassEntry{}
|
2024-03-13 16:15:38 +00:00
|
|
|
|
|
|
|
err := p.db.Select(&passEntries, q, input.PassEntry.Secret)
|
2021-07-21 13:54:29 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2024-05-07 01:28:48 +00:00
|
|
|
type ConfigEntry struct {
|
2023-08-17 19:43:27 +00:00
|
|
|
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) {
|
2024-05-07 01:28:48 +00:00
|
|
|
var configEntries []ConfigEntry
|
|
|
|
keys, err := p.cfg.KV.Keys()
|
2023-08-17 19:43:27 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Error().
|
|
|
|
Err(err).
|
|
|
|
Msg("Error getting config entries.")
|
|
|
|
w.WriteHeader(500)
|
|
|
|
fmt.Fprint(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-05-07 01:28:48 +00:00
|
|
|
for _, key := range keys {
|
|
|
|
value, err := p.cfg.KV.Get(key)
|
|
|
|
if err != nil {
|
|
|
|
log.Error().
|
|
|
|
Err(err).
|
|
|
|
Msg("Error getting config entries.")
|
|
|
|
w.WriteHeader(500)
|
|
|
|
fmt.Fprint(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
configEntries = append(configEntries, ConfigEntry{
|
|
|
|
Key: key,
|
|
|
|
Value: value,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-02-27 19:29:54 +00:00
|
|
|
p.bot.GetWeb().Index("Variables", vars(configEntries)).Render(r.Context(), w)
|
2021-07-21 13:54:29 +00:00
|
|
|
}
|