|
|
@@ -1,50 +1,216 @@
|
|
|
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"
|
|
|
- "golang.org/x/crypto/bcrypt"
|
|
|
"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 serveLogin(c echo.Context) error {
|
|
|
+ return c.File("server/templates/login.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 {
|
|
|
- return c.String(http.StatusOK, "ELEC0017 Project")
|
|
|
- //return c.JSON(http.StatusOK, &config)
|
|
|
+ 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("/new/admin", func(c echo.Context) error {
|
|
|
- name := c.QueryParam("username")
|
|
|
- pass := c.QueryParam("password")
|
|
|
+
|
|
|
+ e.GET("/login", serveLogin)
|
|
|
+ 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) {
|
|
|
+ fmt.Println("Not authorised")
|
|
|
+ return c.Redirect(http.StatusTemporaryRedirect, "/login")
|
|
|
+ }
|
|
|
+ fmt.Println("Authorised")
|
|
|
+ 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(400, "No username")
|
|
|
+ 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")
|
|
|
}
|
|
|
- if len(pass) == 0 {
|
|
|
- return c.String(400, "No password")
|
|
|
+ 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")
|
|
|
}
|
|
|
- hash, err := bcrypt.GenerateFromPassword([]byte(pass), 14)
|
|
|
+ key, err := hex.DecodeString(c.FormValue("key"))
|
|
|
if err != nil {
|
|
|
- fmt.Printf("Failed hash password %s\n", err)
|
|
|
- return c.String(400, "Error")
|
|
|
+ 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),
|
|
|
+ }
|
|
|
}
|
|
|
- cx := *config
|
|
|
- cx.Admins[name] = configAdmin{name, hash}
|
|
|
saveConfig()
|
|
|
- return c.String(200, "OK")
|
|
|
+ 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")
|
|
|
+ }
|
|
|
|
|
|
- //g := e.Group("/admin")
|
|
|
- e.Use(middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {
|
|
|
- user, ok := config.Admins[username]
|
|
|
+ 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 false, nil
|
|
|
+ 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) {
|
|
|
+ return c.File("server/templates/login.html")
|
|
|
}
|
|
|
- err := bcrypt.CompareHashAndPassword(user.Hash, []byte(password))
|
|
|
- return err == nil, nil
|
|
|
- }))
|
|
|
- e.GET("/admin/config", func(c echo.Context) error {
|
|
|
- return c.JSON(http.StatusOK, &config)
|
|
|
+ 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.Use(middleware.BasicAuth()
|
|
|
+ g.GET("/config", func(c echo.Context) error {
|
|
|
+ return c.JSON(http.StatusOK, genConfig())
|
|
|
})
|
|
|
|
|
|
e.Logger.Fatal(e.Start(address))
|