diff --git a/backend/cmd/main.go b/backend/cmd/main.go index 2593019..bae7a83 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -9,6 +9,8 @@ import ( "github.com/gofiber/fiber/v2" _ "github.com/mattn/go-sqlite3" + + jwtware "github.com/gofiber/contrib/jwt" ) func main() { @@ -33,14 +35,23 @@ func main() { // This will likely be replaced by an embedded filesystem in the future server.Static("/", "./static") - // Register our handlers + // Register our unprotected routes server.Post("/api/register", gs.Register) - server.Delete("/api/userdelete", gs.UserDelete) // Perhaps just use POST to avoid headaches // Register handlers for example button count server.Get("/api/button", gs.GetButtonCount) server.Post("/api/button", gs.IncrementButtonCount) + server.Post("/api/login", gs.Login) + + // Every route from here on will require a valid JWT + server.Use(jwtware.New(jwtware.Config{ + SigningKey: jwtware.SigningKey{Key: []byte("secret")}, + })) + + server.Post("/api/loginrenew", gs.LoginRenew) + server.Delete("/api/userdelete", gs.UserDelete) // Perhaps just use POST to avoid headaches + // Announce the port we are listening on and start the server err = server.Listen(fmt.Sprintf(":%d", conf.Port)) if err != nil { diff --git a/backend/internal/handlers/global_state.go b/backend/internal/handlers/global_state.go index 2d3e9f6..5dc8895 100644 --- a/backend/internal/handlers/global_state.go +++ b/backend/internal/handlers/global_state.go @@ -1,18 +1,20 @@ package handlers import ( + "time" "ttime/internal/database" "ttime/internal/types" "github.com/gofiber/fiber/v2" + "github.com/golang-jwt/jwt/v5" ) // The actual interface that we will use type GlobalState interface { Register(c *fiber.Ctx) error // To register a new user UserDelete(c *fiber.Ctx) error // To delete a user - // LoginPost(c *fiber.Ctx) error // To get the token - // LoginUpdate(c *fiber.Ctx) error // To renew the token + Login(c *fiber.Ctx) error // To get the token + LoginRenew(c *fiber.Ctx) error // To renew the token // CreateProject(c *fiber.Ctx) error // To create a new project // GetProjects(c *fiber.Ctx) error // To get all projects // GetProject(c *fiber.Ctx) error // To get a specific project @@ -80,3 +82,51 @@ func (gs *GState) IncrementButtonCount(c *fiber.Ctx) error { gs.ButtonCount++ return c.Status(200).JSON(fiber.Map{"pressCount": gs.ButtonCount}) } + +// Login is a simple login handler that returns a JWT token +func (gs *GState) Login(c *fiber.Ctx) error { + // To test: curl --data "user=user&pass=pass" http://localhost:8080/api/login + user := c.FormValue("user") + pass := c.FormValue("pass") + + // Throws Unauthorized error + if user != "user" || pass != "pass" { + return c.SendStatus(fiber.StatusUnauthorized) + } + + // Create the Claims + claims := jwt.MapClaims{ + "name": user, + "admin": false, + "exp": time.Now().Add(time.Hour * 72).Unix(), + } + + // Create token + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + // Generate encoded token and send it as response. + t, err := token.SignedString([]byte("secret")) + if err != nil { + return c.SendStatus(fiber.StatusInternalServerError) + } + + return c.JSON(fiber.Map{"token": t}) +} + +// LoginRenew is a simple handler that renews the token +func (gs *GState) LoginRenew(c *fiber.Ctx) error { + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + claims["exp"] = time.Now().Add(time.Hour * 72).Unix() + renewed := jwt.MapClaims{ + "name": claims["name"], + "admin": claims["admin"], + "exp": claims["exp"], + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, renewed) + t, err := token.SignedString([]byte("secret")) + if err != nil { + return c.SendStatus(fiber.StatusInternalServerError) + } + return c.JSON(fiber.Map{"token": t}) +}