From c4f53a0be9eb029bc7cc3ce5f9452a0ee55bdc1d Mon Sep 17 00:00:00 2001 From: James Mills Date: Sat, 30 Jan 2021 15:56:03 +1000 Subject: [PATCH] It builds! --- cmd/spyda/main.go | 2 +- go.mod | 2 + go.sum | 5 + internal/handlers.go | 8 ++ internal/manage_handlers.go | 190 +++++++++++++++++++++++++++++++++++ internal/options.go | 10 +- internal/server.go | 9 +- internal/support_handlers.go | 1 - internal/utils.go | 38 ++++++- spyda.db/000000000.data | 0 spyda.db/000000002.data | 0 spyda.db/config.json | 1 + spyda.db/index | 0 spyda.db/meta.json | 1 + 14 files changed, 255 insertions(+), 12 deletions(-) create mode 100644 internal/manage_handlers.go create mode 100644 spyda.db/000000000.data create mode 100644 spyda.db/000000002.data create mode 100644 spyda.db/config.json create mode 100644 spyda.db/index create mode 100755 spyda.db/meta.json diff --git a/cmd/spyda/main.go b/cmd/spyda/main.go index 9de2e8e..1cb0033 100644 --- a/cmd/spyda/main.go +++ b/cmd/spyda/main.go @@ -38,7 +38,7 @@ var ( adminEmail string // Limits - resultsPerpage int + resultsPerPage int // Secrets apiSigningKey string diff --git a/go.mod b/go.mod index c596801..be7e7c6 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,9 @@ require ( github.com/prologic/bitcask v0.3.10 github.com/prologic/observe v0.0.0-20181231082615-747b185a0928 github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect + github.com/renstrom/shortuuid v2.0.3+incompatible github.com/robfig/cron v1.2.0 + github.com/satori/go.uuid v1.2.0 // indirect github.com/sirupsen/logrus v1.7.0 github.com/spf13/pflag v1.0.5 github.com/steambap/captcha v1.3.1 diff --git a/go.sum b/go.sum index c65da8f..46470b4 100644 --- a/go.sum +++ b/go.sum @@ -240,12 +240,17 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q= +github.com/renstrom/shortuuid v1.0.0 h1:RBZExpswQ58bdD70uCmUPhPfXNmICC+ir7IY4WNiCQ0= +github.com/renstrom/shortuuid v2.0.3+incompatible h1:wAE2LlEYCmNNyeLTzSrz3c9O8uNMkGckJj0Gzliqh9c= +github.com/renstrom/shortuuid v2.0.3+incompatible/go.mod h1:n18Ycpn8DijG+h/lLBQVnGKv1BCtTeXo8KKSbBOrQ8c= github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= diff --git a/internal/handlers.go b/internal/handlers.go index 95d4612..312e179 100644 --- a/internal/handlers.go +++ b/internal/handlers.go @@ -99,6 +99,14 @@ func (s *Server) IndexHandler() httprouter.Handle { } } +// AddHandler ... +func (s *Server) AddHandler() httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + ctx := NewContext(s.config, s.db, r) + s.render("add", w, ctx) + } +} + // CachedHandler ... func (s *Server) CachedHandler() httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { diff --git a/internal/manage_handlers.go b/internal/manage_handlers.go new file mode 100644 index 0000000..ab77b90 --- /dev/null +++ b/internal/manage_handlers.go @@ -0,0 +1,190 @@ +package internal + +import ( + "fmt" + "net/http" + "os" + "path/filepath" + "strings" + "time" + + "github.com/julienschmidt/httprouter" + "github.com/renstrom/shortuuid" + log "github.com/sirupsen/logrus" +) + +// ManageHandler ... +func (s *Server) ManageHandler() httprouter.Handle { + isAdminUser := IsAdminUserFactory(s.config) + + return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + ctx := NewContext(s.config, s.db, r) + + if !isAdminUser(ctx.User) { + ctx.Error = true + ctx.Message = "You are not a Pod Owner!" + s.render("403", w, ctx) + return + } + + if r.Method == "GET" { + s.render("managePod", w, ctx) + return + } + + name := strings.TrimSpace(r.FormValue("podName")) + description := strings.TrimSpace(r.FormValue("podDescription")) + + // Update name + if name != "" { + s.config.Name = name + } else { + ctx.Error = true + ctx.Message = "" + s.render("error", w, ctx) + return + } + + // Update pod description + if description != "" { + s.config.Description = description + } else { + ctx.Error = true + ctx.Message = "" + s.render("error", w, ctx) + return + } + + // Save config file + if err := s.config.Settings().Save(filepath.Join(s.config.Data, "settings.yaml")); err != nil { + log.WithError(err).Error("error saving config") + os.Exit(1) + } + + ctx.Error = false + ctx.Message = "Pod updated successfully" + s.render("error", w, ctx) + } +} + +// ManageUsersHandler ... +func (s *Server) ManageUsersHandler() httprouter.Handle { + isAdminUser := IsAdminUserFactory(s.config) + + return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + ctx := NewContext(s.config, s.db, r) + + if !isAdminUser(ctx.User) { + ctx.Error = true + ctx.Message = "You are not a Pod Owner!" + s.render("403", w, ctx) + return + } + + s.render("manageUsers", w, ctx) + + } +} + +// AddUserHandler ... +func (s *Server) AddUserHandler() httprouter.Handle { + isAdminUser := IsAdminUserFactory(s.config) + + return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + ctx := NewContext(s.config, s.db, r) + + if !isAdminUser(ctx.User) { + ctx.Error = true + ctx.Message = "You are not a Pod Owner!" + s.render("403", w, ctx) + return + } + + username := NormalizeUsername(r.FormValue("username")) + // XXX: We DO NOT store this! (EVER) + email := strings.TrimSpace(r.FormValue("email")) + + // Random password -- User is expected to user "Password Reset" + password := shortuuid.New() + + if err := ValidateUsername(username); err != nil { + ctx.Error = true + ctx.Message = fmt.Sprintf("Username validation failed: %s", err.Error()) + s.render("error", w, ctx) + return + } + + if s.db.HasUser(username) { + ctx.Error = true + ctx.Message = "User or Feed with that name already exists! Please pick another!" + s.render("error", w, ctx) + return + } + + hash, err := s.pm.CreatePassword(password) + if err != nil { + log.WithError(err).Error("error creating password hash") + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + recoveryHash := fmt.Sprintf("email:%s", FastHash(email)) + + user := NewUser() + user.Username = username + user.Recovery = recoveryHash + user.Password = hash + user.CreatedAt = time.Now() + + if err := s.db.SetUser(username, user); err != nil { + log.WithError(err).Error("error saving user object for new user") + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + ctx.Error = false + ctx.Message = "User successfully created" + s.render("error", w, ctx) + } +} + +// DelUserHandler ... +func (s *Server) DelUserHandler() httprouter.Handle { + isAdminUser := IsAdminUserFactory(s.config) + + return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + ctx := NewContext(s.config, s.db, r) + + if !isAdminUser(ctx.User) { + ctx.Error = true + ctx.Message = "You are not a Pod Owner!" + s.render("403", w, ctx) + return + } + + username := NormalizeUsername(r.FormValue("username")) + + user, err := s.db.GetUser(username) + if err != nil { + log.WithError(err).Errorf("error loading user object for %s", username) + ctx.Error = true + ctx.Message = "Error deleting account" + s.render("error", w, ctx) + return + } + + // Delete user + if err := s.db.DelUser(user.Username); err != nil { + ctx.Error = true + ctx.Message = "An error occured whilst deleting your account" + s.render("error", w, ctx) + return + } + + s.sm.Delete(w, r) + + ctx.Error = false + ctx.Message = "Successfully deleted account" + s.render("error", w, ctx) + } +} diff --git a/internal/options.go b/internal/options.go index 7fa2767..5ad9238 100644 --- a/internal/options.go +++ b/internal/options.go @@ -149,7 +149,7 @@ func WithBaseURL(baseURL string) Option { } } -// WithAdminUser sets the Admin user used for granting special features to +// WithAdminUser sets the Admin username func WithAdminUser(adminUser string) Option { return func(cfg *Config) error { cfg.AdminUser = adminUser @@ -157,6 +157,14 @@ func WithAdminUser(adminUser string) Option { } } +// WithAdminPass sets the Admin password +func WithAdminPass(adminPass string) Option { + return func(cfg *Config) error { + cfg.AdminPass = adminPass + return nil + } +} + // WithAdminName sets the Admin name used to identify the pod operator func WithAdminName(adminName string) Option { return func(cfg *Config) error { diff --git a/internal/server.go b/internal/server.go index bf0d362..254fa4b 100644 --- a/internal/server.go +++ b/internal/server.go @@ -300,10 +300,6 @@ func (s *Server) initRoutes() { s.router.GET("/add", s.AddHandler()) s.router.POST("/add", s.AddHandler()) - s.router.GET("/settings", s.am.MustAuth(s.SettingsHandler())) - s.router.POST("/settings", s.am.MustAuth(s.SettingsHandler())) - s.router.POST("/token/delete/:signature", s.am.MustAuth(s.DeleteTokenHandler())) - s.router.GET("/manage", s.ManageHandler()) s.router.POST("/manage", s.ManageHandler()) @@ -357,7 +353,7 @@ func NewServer(bind string, options ...Option) (*Server, error) { return nil, err } - tmplman, err := NewTemplateManager(config, blogs, cache) + tmplman, err := NewTemplateManager(config) if err != nil { log.WithError(err).Error("error creating template manager") return nil, err @@ -431,9 +427,6 @@ func NewServer(bind string, options ...Option) (*Server, error) { server.cron.Start() log.Info("started background jobs") - server.tasks.Start() - log.Info("started task dispatcher") - server.setupMetrics() log.Infof("serving metrics endpoint at %s/metrics", server.config.BaseURL) diff --git a/internal/support_handlers.go b/internal/support_handlers.go index 23dad58..c3345e7 100644 --- a/internal/support_handlers.go +++ b/internal/support_handlers.go @@ -122,7 +122,6 @@ func (s *Server) ReportHandler() httprouter.Handle { if r.Method == "GET" { ctx.Title = "Report abuse" - ctx.ReportNick = nick ctx.ReportURL = url s.render("report", w, ctx) return diff --git a/internal/utils.go b/internal/utils.go index 7272dae..ff7b96c 100644 --- a/internal/utils.go +++ b/internal/utils.go @@ -13,6 +13,7 @@ import ( "net/url" "os" "os/exec" + "regexp" "strconv" "strings" "syscall" @@ -30,6 +31,9 @@ import ( ) const ( + admin = "admin" + maxUsernameLength = 15 // avg 6 chars / 2 syllables per name commonly + CacheDir = "cache" requestTimeout = time.Second * 30 @@ -41,7 +45,16 @@ const ( ) var ( - ErrBadRequest = errors.New("error: request failed with non-200 response") + reservedUsernames = []string{ + admin, + } + + validUsername = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_-]+$`) + + ErrBadRequest = errors.New("error: request failed with non-200 response") + ErrInvalidUsername = errors.New("error: invalid username") + ErrUsernameTooLong = errors.New("error: username is too long") + ErrReservedUsername = errors.New("error: username is reserved") ) func GenerateRandomToken() string { @@ -370,6 +383,29 @@ func SafeParseInt(s string, d int) int { return n } +// ValidateUsername validates the username before allowing it to be created. +// This ensures usernames match a defined pattern and that some usernames +// that are reserved are never used by users. +func ValidateUsername(username string) error { + username = NormalizeUsername(username) + + if !validUsername.MatchString(username) { + return ErrInvalidUsername + } + + for _, reservedUsername := range reservedUsernames { + if username == reservedUsername { + return ErrReservedUsername + } + } + + if len(username) > maxUsernameLength { + return ErrUsernameTooLong + } + + return nil +} + func FormatForDateTime(t time.Time) string { var format string diff --git a/spyda.db/000000000.data b/spyda.db/000000000.data new file mode 100644 index 0000000..e69de29 diff --git a/spyda.db/000000002.data b/spyda.db/000000002.data new file mode 100644 index 0000000..e69de29 diff --git a/spyda.db/config.json b/spyda.db/config.json new file mode 100644 index 0000000..6e4c026 --- /dev/null +++ b/spyda.db/config.json @@ -0,0 +1 @@ +{"max_datafile_size":1048576,"max_key_size":256,"max_value_size":65536,"sync":false,"autorecovery":false,"DirFileModeBeforeUmask":448,"FileFileModeBeforeUmask":384} \ No newline at end of file diff --git a/spyda.db/index b/spyda.db/index new file mode 100644 index 0000000..e69de29 diff --git a/spyda.db/meta.json b/spyda.db/meta.json new file mode 100755 index 0000000..5af25ee --- /dev/null +++ b/spyda.db/meta.json @@ -0,0 +1 @@ +{"index_up_to_date":true,"reclaimable_space":0} \ No newline at end of file