diff options
Diffstat (limited to 'svc')
-rw-r--r-- | svc/common.go | 16 | ||||
-rw-r--r-- | svc/common_test.go | 34 | ||||
-rw-r--r-- | svc/conf.go | 13 | ||||
-rw-r--r-- | svc/db.go | 11 | ||||
-rw-r--r-- | svc/handlers.go | 38 | ||||
-rw-r--r-- | svc/leveldb.go | 21 | ||||
-rw-r--r-- | svc/sqlite.go | 4 | ||||
-rw-r--r-- | svc/svc.go | 4 |
8 files changed, 141 insertions, 0 deletions
diff --git a/svc/common.go b/svc/common.go new file mode 100644 index 0000000..5f169af --- /dev/null +++ b/svc/common.go @@ -0,0 +1,16 @@ +package svc + +import "golang.org/x/crypto/bcrypt" + +// HashPass returns the bcrypt hash of the provided string. +// If an empty string is provided, return an empty string. +func HashPass(s string) (string, error) { + if s == "" { + return "", nil + } + h, err := bcrypt.GenerateFromPassword([]byte(s), 14) + if err != nil { + return "", err + } + return string(h), nil +} diff --git a/svc/common_test.go b/svc/common_test.go new file mode 100644 index 0000000..d9a08b3 --- /dev/null +++ b/svc/common_test.go @@ -0,0 +1,34 @@ +package svc + +import ( + "testing" +) + +func TestHashPass(t *testing.T) { + cases := []struct { + in, name string + shouldFail bool + }{ + { + in: "foo", + name: "non-empty password", + shouldFail: false, + }, + { + in: "", + name: "empty password", + shouldFail: true, + }, + } + for _, v := range cases { + t.Run(v.name, func(t *testing.T) { + out, err := HashPass(v.in) + if err != nil && !v.shouldFail { + t.Errorf("Shouldn't have failed: Case %s, Error: %s", v.name, err) + } + if out == "" && v.in != "" { + t.Errorf("Got empty out for case %s input %s", v.name, v.in) + } + }) + } +} diff --git a/svc/conf.go b/svc/conf.go index 7365b2b..5f826fb 100644 --- a/svc/conf.go +++ b/svc/conf.go @@ -20,6 +20,7 @@ along with Getwtxt. If not, see <https://www.gnu.org/licenses/>. package svc // import "git.sr.ht/~gbmor/getwtxt/svc" import ( + "fmt" "log" "os" "path/filepath" @@ -43,6 +44,7 @@ type Configuration struct { DBPath string `yaml:"DatabasePath"` AssetsDir string `yaml:"AssetsDirectory"` StaticDir string `yaml:"StaticFilesDirectory"` + AdminPassHash string `yaml:"-"` StdoutLogging bool `yaml:"StdoutLogging"` CacheInterval time.Duration `yaml:"StatusFetchInterval"` DBInterval time.Duration `yaml:"DatabasePushInterval"` @@ -126,6 +128,7 @@ func setConfigDefaults() { viper.SetDefault("StdoutLogging", false) viper.SetDefault("ReCacheInterval", "1h") viper.SetDefault("DatabasePushInterval", "5m") + viper.SetDefault("AdminPassword", "please_change_me") viper.SetDefault("Instance.SiteName", "getwtxt") viper.SetDefault("Instance.OwnerName", "Anonymous Microblogger") @@ -173,6 +176,16 @@ func bindConfig() { confObj.StdoutLogging = viper.GetBool("StdoutLogging") confObj.CacheInterval = viper.GetDuration("StatusFetchInterval") confObj.DBInterval = viper.GetDuration("DatabasePushInterval") + txtPass := viper.GetString("AdminPassword") + if txtPass == "please_change_me" { + fmt.Println("Please set AdminPassword in getwtxt.yml") + os.Exit(1) + } + passHash, err := HashPass(txtPass) + if err != nil { + errFatal("Failed to hash administrator password: ", err) + } + confObj.AdminPassHash = passHash confObj.Instance.Vers = Vers confObj.Instance.Name = viper.GetString("Instance.SiteName") diff --git a/svc/db.go b/svc/db.go index 8cd05d1..ff92753 100644 --- a/svc/db.go +++ b/svc/db.go @@ -39,6 +39,7 @@ import ( type dbase interface { push() error pull() + delUser(string) error } // Opens a new connection to the specified @@ -96,3 +97,13 @@ func pullDB() { dbChan <- db log.Printf("Database pull took: %v\n", time.Since(start)) } + +func delUser(userURL string) error { + db := <-dbChan + err := db.delUser(userURL) + dbChan <- db + if err != nil { + return err + } + return twtxtCache.DelUser(userURL) +} diff --git a/svc/handlers.go b/svc/handlers.go index cb07349..5dbb10d 100644 --- a/svc/handlers.go +++ b/svc/handlers.go @@ -20,15 +20,18 @@ along with Getwtxt. If not, see <https://www.gnu.org/licenses/>. package svc // import "git.sr.ht/~gbmor/getwtxt/svc" import ( + "errors" "fmt" "hash/fnv" "net/http" + "net/url" "strconv" "strings" "time" "git.sr.ht/~gbmor/getwtxt/registry" "github.com/gorilla/mux" + "golang.org/x/crypto/bcrypt" ) // Takes the modtime of one of the static files, derives @@ -242,3 +245,38 @@ func apiTagsHandler(w http.ResponseWriter, r *http.Request) { } log200(r) } + +func handleUserDelete(w http.ResponseWriter, r *http.Request) { + pass := r.Header.Get("X-Auth") + if pass == "" { + errHTTP(w, r, errors.New("unauthorized"), http.StatusUnauthorized) + return + } + confObj.Mu.RLock() + adminHash := []byte(confObj.AdminPassHash) + confObj.Mu.RUnlock() + + if err := bcrypt.CompareHashAndPassword(adminHash, []byte(pass)); err != nil { + errHTTP(w, r, errors.New("unauthorized"), http.StatusUnauthorized) + return + } + + r.ParseForm() + userURL := strings.TrimSpace(r.Form.Get("url")) + if userURL == "" { + errHTTP(w, r, errors.New("bad request"), http.StatusBadRequest) + return + } + if _, err := url.Parse(userURL); err != nil { + errHTTP(w, r, errors.New("bad request"), http.StatusBadRequest) + return + } + + if err := delUser(userURL); err != nil { + return + } + + w.WriteHeader(200) + w.Write([]byte("200 OK\n")) + log200(r) +} diff --git a/svc/leveldb.go b/svc/leveldb.go index 5fb4a45..a429934 100644 --- a/svc/leveldb.go +++ b/svc/leveldb.go @@ -33,6 +33,27 @@ type dbLevel struct { db *leveldb.DB } +func (lvl *dbLevel) delUser(userURL string) error { + twtxtCache.Mu.RLock() + defer twtxtCache.Mu.RUnlock() + + userStatuses := twtxtCache.Users[userURL].Status + var dbBasket = &leveldb.Batch{} + + dbBasket.Delete([]byte(userURL + "*Nick")) + dbBasket.Delete([]byte(userURL + "*URL")) + dbBasket.Delete([]byte(userURL + "*IP")) + dbBasket.Delete([]byte(userURL + "*Date")) + dbBasket.Delete([]byte(userURL + "*LastModified")) + + for i := range userStatuses { + rfc := i.Format(time.RFC3339) + dbBasket.Delete([]byte(userURL + "*Status*" + rfc)) + } + + return lvl.db.Write(dbBasket, nil) +} + // Called intermittently to commit registry data to // a LevelDB database. func (lvl *dbLevel) push() error { diff --git a/svc/sqlite.go b/svc/sqlite.go index 128aed3..98a4f93 100644 --- a/svc/sqlite.go +++ b/svc/sqlite.go @@ -64,6 +64,10 @@ func initSqlite() *dbSqlite { } } +func (lite *dbSqlite) delUser(userURL string) error { + return nil +} + // Commits data from memory to a SQLite database intermittently. func (lite *dbSqlite) push() error { if err := lite.db.Ping(); err != nil { diff --git a/svc/svc.go b/svc/svc.go index 72ccdc3..10cf56a 100644 --- a/svc/svc.go +++ b/svc/svc.go @@ -91,6 +91,10 @@ func setIndexRouting(index *mux.Router) { } func setEndpointRouting(api *mux.Router) { + api.Path("/admin/users"). + Methods("DELETE"). + HandlerFunc(handleUserDelete) + // May add support for other formats later. // Making this future-proof. api.Path("/{format:(?:plain)}"). |