diff --git a/bot/bot.go b/bot/bot.go index 5d82a97..51e5b9e 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -17,6 +17,7 @@ import ( "github.com/velour/catbase/bot/msglog" "github.com/velour/catbase/bot/user" "github.com/velour/catbase/config" + "golang.org/x/crypto/bcrypt" ) // bot type provides storage for bot-wide information, configs, and database connections @@ -370,3 +371,23 @@ func PluginName(p Plugin) string { t := reflect.TypeOf(p).String() return t } + +func (b *bot) CheckPassword(secret, password string) bool { + if b.password == password { + return true + } + parts := strings.SplitN(password, ":", 2) + if len(parts) == 2 { + secret = parts[0] + password = parts[1] + } + q := `select encoded_pass from apppass where secret = ?` + encodedPasswords := [][]byte{} + b.DB().Select(&encodedPasswords, q, secret) + for _, p := range encodedPasswords { + if err := bcrypt.CompareHashAndPassword(p, []byte(password)); err == nil { + return true + } + } + return false +} diff --git a/bot/interfaces.go b/bot/interfaces.go index cf17947..a8ccfbf 100644 --- a/bot/interfaces.go +++ b/bot/interfaces.go @@ -162,6 +162,9 @@ type Bot interface { // Check if a particular plugin is blacklisted OnBlacklist(string, string) bool + + // Check valid password + CheckPassword(secret, password string) bool } // Connector represents a server connection to a chat service diff --git a/bot/mock.go b/bot/mock.go index 55b1e61..8ee861d 100644 --- a/bot/mock.go +++ b/bot/mock.go @@ -117,9 +117,10 @@ func NewMockBot() *MockBot { return &b } -func (mb *MockBot) GetPluginNames() []string { return nil } -func (mb *MockBot) RefreshPluginBlacklist() error { return nil } -func (mb *MockBot) RefreshPluginWhitelist() error { return nil } -func (mb *MockBot) GetWhitelist() []string { return []string{} } -func (mb *MockBot) OnBlacklist(ch, p string) bool { return false } -func (mb *MockBot) URLFormat(title, url string) string { return title + url } +func (mb *MockBot) GetPluginNames() []string { return nil } +func (mb *MockBot) RefreshPluginBlacklist() error { return nil } +func (mb *MockBot) RefreshPluginWhitelist() error { return nil } +func (mb *MockBot) GetWhitelist() []string { return []string{} } +func (mb *MockBot) OnBlacklist(ch, p string) bool { return false } +func (mb *MockBot) URLFormat(title, url string) string { return title + url } +func (mb *MockBot) CheckPassword(secret, password string) bool { return true } diff --git a/bot/web.go b/bot/web.go index b15310d..2d02145 100644 --- a/bot/web.go +++ b/bot/web.go @@ -54,7 +54,7 @@ var rootIndex = ` - Factoids + catbase diff --git a/go.mod b/go.mod index df26f3d..12ac015 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,6 @@ require ( github.com/azr/backoff v0.0.0-20160115115103-53511d3c7330 // indirect github.com/bwmarrin/discordgo v0.22.0 github.com/cdipaolo/goml v0.0.0-20190412180403-e1f51f713598 - github.com/chrissexton/gofuck v1.0.0 github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff github.com/chrissexton/sentiment v0.0.0-20190927141846-d69c422ba035 github.com/dustin/go-jsonpointer v0.0.0-20160814072949-ba0abeacc3dc // indirect @@ -60,6 +59,7 @@ require ( github.com/ttacon/libphonenumber v1.1.0 // indirect github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89 github.com/velour/velour v0.0.0-20160303155839-8e090e68d158 + golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 golang.org/x/exp v0.0.0-20191014171548-69215a2ee97e // indirect golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 // indirect gonum.org/v1/gonum v0.6.0 // indirect diff --git a/go.sum b/go.sum index 44f3d1d..66d962c 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,6 @@ code.chrissexton.org/cws/getaoc v0.0.0-20191201043947-d5417d4b618d h1:XS13tP+cMAvXYHQiYqcst64wQ854pueMRZSU4+6puU4= code.chrissexton.org/cws/getaoc v0.0.0-20191201043947-d5417d4b618d/go.mod h1:rEpfJR9MplF2TUj2Oy+u4XAaLve2kwB8I2zlzeIQxl8= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/AlekSi/pointer v1.0.0 h1:KWCWzsvFxNLcmM5XmiqHsGTTsuwZMsLFwWF9Y+//bNE= github.com/AlekSi/pointer v1.0.0/go.mod h1:1kjywbfcPFCmncIxtk6fIEub6LKrfMz3gc5QKVOSOA8= github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI= github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE= @@ -13,7 +12,6 @@ github.com/ChimeraCoder/tokenbucket v0.0.0-20131201223612-c5a927568de7/go.mod h1 github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk= github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= @@ -31,8 +29,6 @@ github.com/bwmarrin/discordgo v0.22.0 h1:uBxY1HmlVCsW1IuaPjpCGT6A2DBwRn0nvOguQIx github.com/bwmarrin/discordgo v0.22.0/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M= github.com/cdipaolo/goml v0.0.0-20190412180403-e1f51f713598 h1:j2XRGH5Y5uWtBYXGwmrjKeM/kfu/jh7ZcnrGvyN5Ttk= github.com/cdipaolo/goml v0.0.0-20190412180403-e1f51f713598/go.mod h1:sduMkaHcXDIWurl/Bd/z0rNEUHw5tr6LUA9IO8E9o0o= -github.com/chrissexton/gofuck v1.0.0 h1:vxg/tIfI2HunJOErSotmMqMRNfLRVO+BTjSKpFoAizA= -github.com/chrissexton/gofuck v1.0.0/go.mod h1:voOFh7xp5oNB4N516blAUT0Vh2JQq/cFX9autdChv9o= github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff h1:+TEqaP0eO1unI7XHHFeMDhsxhLDIb0x8KYuZbqbAmxA= github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff/go.mod h1:QCRjR0b4qiJiNjuP7RFM89bh4UExGJalcWmYeSvlnRc= github.com/chrissexton/sentiment v0.0.0-20190927141846-d69c422ba035 h1:3+eJGFTbUgOMDCpa8PTmJABs1Z3EDHRrcz6d3oXfZm0= @@ -73,7 +69,6 @@ github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhS github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -120,7 +115,6 @@ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254 h1:JYoQR67E1vv1WGoeW8DkdFs7vrIEe/5wP+qJItd5tUE= github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254/go.mod h1:DPucAeQGDPUzYUt+NaWw6qsF5SFapWWToxEiVDh2aV0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -137,7 +131,6 @@ github.com/slack-go/slack v0.7.2 h1:oLy2a2YqrtoHSSxbjRhrtLDGbCKcZJwgbuQ826BWxaI= github.com/slack-go/slack v0.7.2/go.mod h1:FGqNzJBmxIsZURAxh2a8D21AnOVvvXZvGligs4npPUM= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -176,11 +169,9 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 h1:p9xBe/w/OzkeYVKm234g55gMdD1nSIooTir5kV11kfA= golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -191,7 +182,6 @@ golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b h1:kHlr0tATeLRMEiZJu5CknOw/E8V6h69sXXQFGoPtjcc= golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -219,7 +209,6 @@ gopkg.in/go-playground/webhooks.v5 v5.13.0 h1:e9vtkQZK464+UdL3YjRox2yR8JSmh2094P gopkg.in/go-playground/webhooks.v5 v5.13.0/go.mod h1:LZbya/qLVdbqDR1aKrGuWV6qbia2zCYSR5dpom2SInQ= gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/plugins/admin/admin.go b/plugins/admin/admin.go index 91d01d4..9490cf0 100644 --- a/plugins/admin/admin.go +++ b/plugins/admin/admin.go @@ -3,9 +3,7 @@ package admin import ( - "encoding/json" "fmt" - "net/http" "os" "regexp" "strings" @@ -38,6 +36,8 @@ func New(b bot.Bot) *AdminPlugin { cfg: b.Config(), } + p.mkDB() + b.RegisterRegex(p, bot.Message, comeBackRegex, p.comeBackCmd) b.RegisterRegexCmd(p, bot.Message, shutupRegex, p.shutupCmd) b.RegisterRegexCmd(p, bot.Message, addBlacklistRegex, p.isAdmin(p.addBlacklistCmd)) @@ -72,6 +72,16 @@ var forbiddenKeys = map[string]bool{ "meme.memes": true, } +func (p *AdminPlugin) mkDB() { + q := `create table if not exists apppass ( + id integer primary key autoincrement, + secret string not null, + encoded_pass string not null, + cost integer default 10 + )` + p.db.MustExec(q) +} + var shutupRegex = regexp.MustCompile(`(?i)^shut up$`) var comeBackRegex = regexp.MustCompile(`(?i)^come back$`) @@ -351,42 +361,6 @@ func (p *AdminPlugin) help(conn bot.Connector, kind bot.Kind, m msg.Message, arg return true } -func (p *AdminPlugin) registerWeb() { - http.HandleFunc("/vars/api", p.handleWebAPI) - http.HandleFunc("/vars", p.handleWeb) - p.bot.RegisterWeb("/vars", "Variables") -} - -func (p *AdminPlugin) handleWeb(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, varIndex) -} - -func (p *AdminPlugin) handleWebAPI(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) -} - func (p *AdminPlugin) addWhitelist(plugin string) error { return p.modList(`insert or replace into pluginWhitelist values (?)`, "", plugin) } diff --git a/plugins/admin/index.go b/plugins/admin/index.go index 207d66a..752d069 100644 --- a/plugins/admin/index.go +++ b/plugins/admin/index.go @@ -78,3 +78,164 @@ var varIndex = ` ` + +var apppassIndex = ` + + + + + + + + + + + + + + + + +Vars + + + +
+ +App Pass + +{{ item.name }} + + + + + + + +Password: + + + + + +Secret: + + + + + + +List + + +New + + + + + + +ID: +{{ entry.id }} + + +Password: +{{ entry.secret }}:{{ showPassword }} + + +Note: this password will only be displayed once. For single-field entry passwords, use the secret:password format. + + + + + +
    +
  • +X {{entry.id}}
  • +
+
+
+
+
+ + + + +` diff --git a/plugins/admin/web.go b/plugins/admin/web.go new file mode 100644 index 0000000..5d40267 --- /dev/null +++ b/plugins/admin/web.go @@ -0,0 +1,189 @@ +package admin + +import ( + "crypto/md5" + "crypto/rand" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + + "github.com/rs/zerolog/log" + "golang.org/x/crypto/bcrypt" +) + +func (p *AdminPlugin) registerWeb() { + http.HandleFunc("/vars/api", p.handleVarsAPI) + http.HandleFunc("/vars", p.handleVars) + p.bot.RegisterWeb("/vars", "Variables") + http.HandleFunc("/apppass/verify", p.handleAppPassCheck) + http.HandleFunc("/apppass/api", p.handleAppPassAPI) + http.HandleFunc("/apppass", p.handleAppPass) + p.bot.RegisterWeb("/apppass", "App Pass") +} + +func (p *AdminPlugin) handleAppPass(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, apppassIndex) +} + +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)) + } + 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 { + writeErr(w, err) + return + } + id, err := res.LastInsertId() + if err != nil { + writeErr(w, err) + return + } + req.PassEntry.ID = id + 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 + } + q := `delete from apppass where id = ?` + _, err := p.db.Exec(q, req.PassEntry.ID) + if err != nil { + writeErr(w, err) + return + } + } + q := `select id,secret from apppass where secret = ?` + passEntries := []PassEntry{} + err := p.db.Select(&passEntries, q, req.PassEntry.Secret) + 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) { + fmt.Fprint(w, varIndex) +} + +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) +} diff --git a/plugins/cli/cli.go b/plugins/cli/cli.go index 119c383..2324236 100644 --- a/plugins/cli/cli.go +++ b/plugins/cli/cli.go @@ -57,7 +57,7 @@ func (p *CliPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) { log.Debug(). Interface("postbody", info). Msg("Got a POST") - if info.Password != p.bot.GetPassword() { + if !p.bot.CheckPassword("", info.Password) { w.WriteHeader(http.StatusForbidden) j, _ := json.Marshal(struct{ Err string }{Err: "Invalid Password"}) w.Write(j) diff --git a/plugins/counter/counter.go b/plugins/counter/counter.go index 7f47fc4..36adf99 100644 --- a/plugins/counter/counter.go +++ b/plugins/counter/counter.go @@ -745,7 +745,7 @@ func (p *CounterPlugin) handleCounterAPI(w http.ResponseWriter, r *http.Request) log.Debug(). Interface("postbody", info). Msg("Got a POST") - if info.Password != p.Bot.GetPassword() { + if p.Bot.CheckPassword("", info.Password) { w.WriteHeader(http.StatusForbidden) j, _ := json.Marshal(struct{ Err string }{Err: "Invalid Password"}) w.Write(j) diff --git a/plugins/first/first.go b/plugins/first/first.go index dd02162..a5056fe 100644 --- a/plugins/first/first.go +++ b/plugins/first/first.go @@ -150,7 +150,7 @@ func (p *FirstPlugin) isNotToday(f *FirstEntry) bool { t := f.time t0 := Midnight(t) jitter := time.Duration(p.config.GetInt("first.jitter", 0)) - t0 = t0.Add(jitter * time.Second) + t0 = t0.Add(jitter * time.Millisecond) return t0.Before(Midnight(time.Now())) }