From dd3d87bd97e41c77eea270812d338139fd87b9fc Mon Sep 17 00:00:00 2001 From: Benjamin Morrison Date: Thu, 21 Oct 2021 21:31:17 -0400 Subject: delete a user, new config option for admin pass. pass is bcrypt hashed on startup and not stored in plaintext. --- Makefile | 3 ++- README.md | 8 ++++++++ assets/tmpl/index.html | 4 ++++ go.mod | 2 +- go.sum | 7 +++---- svc/db.go | 11 +++++++++++ svc/handlers.go | 38 ++++++++++++++++++++++++++++++++++++++ svc/leveldb.go | 21 +++++++++++++++++++++ svc/sqlite.go | 4 ++++ svc/svc.go | 4 ++++ 10 files changed, 96 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index d76ac3a..4897f5d 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,8 @@ install: @printf "\n%s\n" "Copying files..." install -m755 getwtxt $(BINDIR) - @if [ -f "$(BINDIR)/getwtxt.yml" ]; then printf "%s\n" "getwtxt.yml exists. Skipping ..."; else printf "%s\n" "getwtxt.yml ..." && install -m644 getwtxt.yml "$(BINDIR)"; fi + @if [ -f "$(BINDIR)/getwtxt.yml" ]; then printf "%s\n" "getwtxt.yml exists. Skipping ..."; else printf "%s\n" "getwtxt.yml ..." && install -m600 getwtxt.yml "$(BINDIR)"; fi + chmod 600 $(BINDIR)/getwtxt.yml @if [ -f "$(BINDIR)/assets/style.css" ]; then printf "%s\n" "style.css exists. Skipping ..."; else printf "%s\n" "style.css ..." && install -m644 assets/style.css "$(BINDIR)/assets/style.css"; fi @if [ -f "$(BINDIR)/assets/tmpl/index.html" ]; then printf "%s\n" "tmpl/index.html exists. Skipping ..."; else printf "%s\n" "tmpl/index.html ..." && install -m644 assets/tmpl/index.html "$(BINDIR)/assets/tmpl/index.html"; fi install -m644 static/kognise.water.css.dark.min.css $(BINDIR)/static diff --git a/README.md b/README.md index 44f3e43..f1833f2 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,14 @@ $ curl 'https://twtxt.example.com/api/plain/tags/programming' foo https://example.com/twtxt.txt 2019-03-01T09:31:02.000Z I love #programming! ``` +### Delete a User + +``` +$ curl -X DELETE -H 'X-Auth: password_in_getwtxt.yml' 'https://twtxt.example.com/api/admin/users?url=https://example.com/twtxt.txt' + +200 OK +``` + ## Benchmarks * [bombardier](https://github.com/codesenberg/bombardier) diff --git a/assets/tmpl/index.html b/assets/tmpl/index.html index 0318810..1e4b6f5 100644 --- a/assets/tmpl/index.html +++ b/assets/tmpl/index.html @@ -43,6 +43,10 @@
$ curl '{{.URL}}/api/plain/version'
 getwtxt {{.Vers}}
         
+

Delete a user by issuing a DELETE request to the /api/admin/users endpoint. This + must include the X-Auth header with the password specified during configuration.

+
$ curl -X DELETE -H 'X-Auth: mypassword' '{{.URL}}/api/admin/users?url=https://foo.ext/twtxt.txt'
+200 OK

Add new user by submitting a POST request to the /api/plain/users endpoint. If both ?url=X and ?nickname=X are not passed, or the user already exists in this registry, you will receive 400 Bad Request as a response. If you are unsure what went diff --git a/go.mod b/go.mod index dfa6f22..85f2b1a 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,6 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.7.0 github.com/syndtr/goleveldb v1.0.0 - golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 ) diff --git a/go.sum b/go.sum index 1d2a295..0d7e5cb 100644 --- a/go.sum +++ b/go.sum @@ -237,8 +237,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/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-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -263,15 +263,14 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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 . 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)}"). -- cgit 1.4.1-2-gfad0