package main import ( "crypto/rand" "encoding/hex" "fmt" "github.com/gorilla/sessions" "github.com/labstack/echo" "github.com/labstack/echo-contrib/session" "github.com/labstack/echo/middleware" "net/http" "regexp" "strconv" "strings" "time" ) type userConfig struct { Admins []string `json:"admins"` Boards []configBoard `json:"boards"` } func genConfig() *userConfig { u := &userConfig{ make([]string, 0, len(config.Admins)), make([]configBoard, 0, len(config.Boards)), } for k := range config.Admins { u.Admins = append(u.Admins, k) } for b := range config.Boards { u.Boards = append(u.Boards, *config.Boards[b]) } return u } func serveIndex(c echo.Context) error { return c.File("server/templates/head.html") } func serveInterface(address string) { e := echo.New() e.Use(middleware.GzipWithConfig(middleware.GzipConfig{Level: 5})) e.Use(session.Middleware(sessions.NewCookieStore([]byte(config.Secret)))) e.GET("/", func(c echo.Context) error { sess, _ := session.Get("session", c) if auth, ok := sess.Values["authorised"]; ok && auth.(bool) { return c.Redirect(http.StatusTemporaryRedirect, "/admin") } return c.Redirect(http.StatusTemporaryRedirect, "/login") }) e.GET("/login", func(c echo.Context) error { sess, _ := session.Get("session", c) if auth, ok := sess.Values["authorised"]; ok && auth.(bool) { return c.Redirect(http.StatusTemporaryRedirect, "/admin") } return c.File("server/templates/login.html") }) g := e.Group("/admin") g.Use(func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { sess, _ := session.Get("session", c) auth, ok := sess.Values["authorised"] if !ok || !auth.(bool) { return c.Redirect(http.StatusTemporaryRedirect, "/login") } return next(c) } }) g.GET("", serveIndex) g.POST("", serveIndex) g.POST("/user", func(c echo.Context) error { sess, _ := session.Get("session", c) name := strings.ToLower(c.FormValue("username")) matched, _ := regexp.MatchString(`\w`, name) if !matched { return c.String(http.StatusNotAcceptable, "Invalid username. Allowed symbols: a-z 0-9 _") } pass := c.FormValue("password") if len(name) == 0 { return c.String(http.StatusNotAcceptable, "No username") } admin, exist := config.Admins[name] if exist { if len(pass) > 0 { if sess.Values["username"] != name { return c.String(http.StatusNotAcceptable, "Can't change other user passwords") } err := admin.newPassword(pass) if err != nil { fmt.Printf("Failed hash password %s\n", err) return c.String(http.StatusNotAcceptable, "Failed to hash") } } } else { if len(pass) == 0 { return c.String(http.StatusNotAcceptable, "No password") } admin := configAdmin{name, nil} err := admin.newPassword(pass) if err != nil { fmt.Printf("Failed hash password %s\n", err) return c.String(http.StatusNotAcceptable, "Failed to hash") } config.Admins[name] = &admin } saveConfig() return c.JSON(http.StatusOK, genConfig()) }) g.POST("/board", func(c echo.Context) error { id64, err := strconv.ParseUint(c.FormValue("id"), 10, 16) if err != nil { return c.String(http.StatusNotAcceptable, "Invalid ID") } id := uint16(id64) keysize, err := strconv.ParseUint(c.FormValue("keysize"), 10, 16) if err != nil || (keysize != 128 && keysize != 192 && keysize != 256) { return c.String(http.StatusNotAcceptable, "Invalid key size") } keystr := strings.Trim(c.FormValue("key"), " ") key, err := hex.DecodeString(keystr) if err != nil { return c.String(http.StatusNotAcceptable, "Key is not base 16") } if len(key) != int(keysize/8) && len(key) != 0 { return c.String(http.StatusNotAcceptable, "Key does not match key size") } if board, exist := config.Boards[id]; exist { // Update settings if len(key) > 0 { board.KEY = key board.KEY16 = hex.EncodeToString(key) } } else { // New board if len(key) == 0 { key = make([]byte, keysize/8) _, err = rand.Read(key) errCheck(err, "Failed generate random key") } config.Boards[id] = &configBoard{ id, key, hex.EncodeToString(key), time.Unix(0, 0), } } saveConfig() return c.JSON(http.StatusOK, genConfig()) }) g.DELETE("/user/:name", func(c echo.Context) error { sess, _ := session.Get("session", c) if sess.Values["username"] == c.Param("name") { return c.String(http.StatusNotAcceptable, "Can't delete yourself") } _, ok := config.Admins[c.Param("name")] if !ok { return c.String(http.StatusNotFound, "Not found") } delete(config.Admins, c.Param("name")) saveConfig() return c.JSON(http.StatusOK, genConfig()) }) g.DELETE("/board/:id", func(c echo.Context) error { id64, err := strconv.ParseUint(c.Param("id"), 10, 16) if err != nil { return c.String(http.StatusNotAcceptable, "Invalid ID") } id := uint16(id64) _, ok := config.Boards[id] if !ok { return c.String(http.StatusNotFound, "Not found") } delete(config.Boards, id) saveConfig() return c.JSON(http.StatusOK, genConfig()) }) e.POST("/login", func(c echo.Context) error { username := c.FormValue("username") password := c.FormValue("password") user, ok := config.Admins[username] if !ok || !user.checkPassword(password) { e.Logger.Printf("User %s failed to login\n", username) return c.File("server/templates/login.html") } e.Logger.Printf("User %s successfully logged in\n", username) sess, _ := session.Get("session", c) sess.Options = &sessions.Options{ Path: "/", MaxAge: 86400 * 7, HttpOnly: true, } sess.Values["username"] = user.Username sess.Values["authorised"] = true err := sess.Save(c.Request(), c.Response()) errCheck(err, "Failed to save session") return c.Redirect(http.StatusTemporaryRedirect, "/admin") }) g.GET("/logout", func(c echo.Context) error { sess, _ := session.Get("session", c) sess.Values["authorised"] = false err := sess.Save(c.Request(), c.Response()) errCheck(err, "Failed to save session") return c.Redirect(http.StatusTemporaryRedirect, "/login") }) g.GET("/logs", func(c echo.Context) error { return c.JSON(http.StatusOK, &serverLogs) }) //g.Use(middleware.BasicAuth() g.GET("/config", func(c echo.Context) error { return c.JSON(http.StatusOK, genConfig()) }) e.Logger.Fatal(e.Start(address)) }