diff --git a/.gitignore b/.gitignore index 1928d6e..14b9b95 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ node_modules/ dist/ # Server -server +storage-server # Local Files .env diff --git a/server/api.go b/server/api.go new file mode 100644 index 0000000..5e0f9d2 --- /dev/null +++ b/server/api.go @@ -0,0 +1,293 @@ +package server + +import ( + "bytes" + "fmt" + "log" + "time" + + "git.phc.dm.unipi.it/phc/storage/config" + "git.phc.dm.unipi.it/phc/storage/database" + "git.phc.dm.unipi.it/phc/storage/utils" + "github.com/gofiber/fiber/v2" +) + +func (r *Server) isAdminMiddleware(c *fiber.Ctx) error { + if _, found := r.adminSessions[c.Cookies("sid")]; !found { + return fmt.Errorf("invalid session token") + } + + return c.Next() +} + +func (r *Server) Api(api fiber.Router) { + isAPIKeyMiddleware := func(c *fiber.Ctx) error { + if _, isAdmin := r.adminSessions[c.Cookies("sid")]; !isAdmin { // if admin continue + token := c.Cookies("token") + if token == "" { + return fmt.Errorf("no api token") + } + + if err := r.Database.CheckAPIKey(token); err != nil { // otherwise also check api token + return err + } + } + return c.Next() + } + + // + // TODO: Change to /server-info (also in frontend) + // Setup "/api/monitor" routes + // + monitorRoute := api.Group("/monitor") + monitorRoute.Use(r.isAdminMiddleware) + r.ApiMonitor(monitorRoute) + + api.Post("/login", func(c *fiber.Ctx) error { + var form struct { + Password string `form:"password"` + } + + if err := c.BodyParser(&form); err != nil { + return err + } + + if form.Password != config.AdminPassword { + return c.JSON("invalid credentials") + } + + token := utils.GenerateRandomString(32) + r.adminSessions[token] = struct{}{} + + c.Cookie(&fiber.Cookie{ + Name: "sid", + Value: token, + Path: "/", + Expires: time.Now().Add(3 * 24 * time.Hour), + }) + + return c.Redirect("/") + }) + + api.Get("/status", func(c *fiber.Ctx) error { + return c.JSON("ok") + }) + + api.Get("/current-user", + func(c *fiber.Ctx) error { + if _, found := r.adminSessions[c.Cookies("sid")]; !found { + return c.JSON("anonymous") + } + + return c.JSON("admin") + }) + + api.Get("/dashboard-state", + r.isAdminMiddleware, + func(c *fiber.Ctx) error { + state, err := r.Database.GetDashboardState() + if err != nil { + return err + } + + return c.JSON(state) + }) + + api.Post("/dashboard-state", + r.isAdminMiddleware, + func(c *fiber.Ctx) error { + + var state database.DashboardState + + if err := c.BodyParser(&state); err != nil { + return err + } + + if err := r.Database.SetDashboardState(state); err != nil { + return err + } + + return c.JSON("ok") + }) + + api.Get("/buckets", + r.isAdminMiddleware, + func(c *fiber.Ctx) error { + buckets, err := r.Database.AllBuckets() + if err != nil { + return err + } + + return c.JSON(buckets) + }) + + api.Post("/buckets", + r.isAdminMiddleware, + func(c *fiber.Ctx) error { + var req struct { + Bucket string `json:"bucket"` + Path string `json:"path"` + } + + log.Printf("%v", string(c.Body())) + + if err := c.BodyParser(&req); err != nil { + return err + } + + settings := &database.JsonBucketSettings{} + if req.Path != "" { + settings.Path = req.Path + } + + if err := r.Database.CreateBucket(req.Bucket, settings); err != nil { + return err + } + + return c.JSON("ok") + }) + + api.Get("/buckets/:bucket", + isAPIKeyMiddleware, + func(c *fiber.Ctx) error { + bucket := c.Params("bucket") + + objects, err := r.Database.AllBucketObjects(bucket) + if err != nil { + return err + } + + return c.JSON(objects) + }) + + api.Get("/buckets/:bucket/settings", + r.isAdminMiddleware, + func(c *fiber.Ctx) error { + bucket := c.Params("bucket") + + settings, err := r.Database.GetBucketSettings(bucket) + if err != nil { + return err + } + + return c.JSON(settings) + }) + + api.Post("/buckets/:bucket/settings", + r.isAdminMiddleware, + func(c *fiber.Ctx) error { + bucket := c.Params("bucket") + + var settings *database.JsonBucketSettings + + if err := c.BodyParser(&settings); err != nil { + return err + } + + if err := r.Database.SetBucketSettings(bucket, settings); err != nil { + return err + } + + return c.JSON("ok") + }) + + api.Post("/buckets/:bucket", + isAPIKeyMiddleware, + func(c *fiber.Ctx) error { + bucket := c.Params("bucket") + + ff, err := c.FormFile("file") + if err != nil { + return err + } + + mf, err := ff.Open() + if err != nil { + return err + } + + id, err := r.Database.CreateBucketObject(bucket, mf) + if err != nil { + return err + } + + return c.JSON(fiber.Map{ + "bucket": bucket, + "id": id, + }) + }) + + api.Get("/buckets/:bucket/:id", + isAPIKeyMiddleware, + func(c *fiber.Ctx) error { + bucket := c.Params("bucket") + id := c.Params("id") + + buf := &bytes.Buffer{} + + if err := r.Database.GetBucketObject(bucket, id, buf); err != nil { + return err + } + + return c.SendStream(buf) + }) + + api.Delete("/buckets/:bucket/:id", + isAPIKeyMiddleware, + func(c *fiber.Ctx) error { + bucket := c.Params("bucket") + id := c.Params("id") + + if err := r.Database.DeleteBucketObject(bucket, id); err != nil { + return err + } + + return c.JSON("ok") + }) + + // + // API Keys + // + + api.Get("/api-keys", + r.isAdminMiddleware, + func(c *fiber.Ctx) error { + apiKeys, err := r.Database.AllAPIKeys() + if err != nil { + return err + } + + return c.JSON(apiKeys) + }) + + api.Post("/api-keys", + r.isAdminMiddleware, + func(c *fiber.Ctx) error { + key, err := r.Database.CreateAPIKey() + if err != nil { + return err + } + + return c.JSON(key) + }) + + api.Delete("/api-keys/:key", + r.isAdminMiddleware, + func(c *fiber.Ctx) error { + if err := r.Database.RemoveAPIKey(c.Params("key")); err != nil { + return err + } + + return c.JSON("ok") + }) + + api.Get("/api-keys/:key", + r.isAdminMiddleware, + func(c *fiber.Ctx) error { + if err := r.Database.CheckAPIKey(c.Params("key")); err != nil { + return err + } + + return c.JSON("valid") + }) +} diff --git a/server/monitor.go b/server/monitor.go new file mode 100644 index 0000000..8832f8c --- /dev/null +++ b/server/monitor.go @@ -0,0 +1,29 @@ +package server + +import ( + "fmt" + + "github.com/gofiber/fiber/v2" +) + +func (r *Server) ApiMonitor(api fiber.Router) { + // Respond to requests like + // - "/api/monitor/status?script=SCRIPT_NAME" where SCRIPT_NAME is the name of a file inside "./scripts" + api.Get("/status", func(c *fiber.Ctx) error { + if qScript := c.Query("script"); qScript != "" { + output, err := r.Monitor.GetOutput(qScript) + if err != nil { + return err + } + + return c.JSON(output) + } + + return fmt.Errorf("no script, device or entity provided") + }) + + api.Get("/invalidate-cache", func(c *fiber.Ctx) error { + r.Monitor.InvalidateCache() + return c.JSON("ok") + }) +} diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..a0a9d25 --- /dev/null +++ b/server/server.go @@ -0,0 +1,22 @@ +package server + +import ( + "git.phc.dm.unipi.it/phc/storage/database" + "git.phc.dm.unipi.it/phc/storage/serverinfo" +) + +type Server struct { + adminSessions map[string]struct{} + + Database database.Database + Monitor *serverinfo.Service +} + +func New(db database.Database, m *serverinfo.Service) *Server { + return &Server{ + adminSessions: map[string]struct{}{}, + + Database: db, + Monitor: m, + } +}