From 71283b224872f5dcc42761b7ff5f82ae730b5cc7 Mon Sep 17 00:00:00 2001 From: Keiran Date: Wed, 6 Aug 2025 06:32:45 +0100 Subject: [PATCH] add authenticated middleware for sending termails --- cmd/termbox_server/main.go | 7 ++ internal/handlers/handlers.go | 139 ++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) diff --git a/cmd/termbox_server/main.go b/cmd/termbox_server/main.go index 80faa54..7f44892 100644 --- a/cmd/termbox_server/main.go +++ b/cmd/termbox_server/main.go @@ -26,5 +26,12 @@ func main() { e.POST("/auth/verify", handlers.HandleVerifyCode) e.POST("/auth/login", handlers.HandleLogin) + protected := e.Group("/termail") + protected.Use(handlers.AuthMiddleware) + protected.POST("/send", handlers.HandleSendTermail) + protected.GET("/inbox", handlers.HandleGetInbox) + protected.POST("/:id/read", handlers.HandleMarkTermailRead) + protected.DELETE("/:id", handlers.HandleDeleteTermail) + log.Fatal(e.Start(":8080")) } diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index b60ae56..2105558 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -1,8 +1,10 @@ package handlers import ( + "fmt" "net/http" "os" + "strconv" "strings" "time" @@ -89,6 +91,143 @@ func HandleVerifyCode(c echo.Context) error { return c.JSON(http.StatusOK, map[string]string{"message": "Account verified successfully"}) } +func validateToken(tokenString string) (*jwt.MapClaims, error) { + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method") + } + return []byte(os.Getenv("JWT_SECRET")), nil + }) + + if err != nil { + return nil, err + } + + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + return &claims, nil + } + + return nil, fmt.Errorf("invalid token") +} + +func AuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + authHeader := c.Request().Header.Get("Authorization") + if authHeader == "" { + return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Authorization header required"}) + } + + bearerToken := strings.Split(authHeader, " ") + if len(bearerToken) != 2 || bearerToken[0] != "Bearer" { + return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Invalid authorization format"}) + } + + claims, err := validateToken(bearerToken[1]) + if err != nil { + return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Invalid token"}) + } + + c.Set("user_id", int((*claims)["user_id"].(float64))) + c.Set("username", (*claims)["username"].(string)) + return next(c) + } +} + +func HandleSendTermail(c echo.Context) error { + var req db.SendTermailRequest + if err := c.Bind(&req); err != nil { + return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid request body"}) + } + + if req.ReceiverUsername == "" || req.Subject == "" || req.Content == "" { + return c.JSON(http.StatusBadRequest, map[string]string{"error": "Receiver username, subject, and content are required"}) + } + + senderID := c.Get("user_id").(int) + termail, err := db.SendTermail(c.Request().Context(), senderID, req) + if err != nil { + if strings.Contains(err.Error(), "user not found") { + return c.JSON(http.StatusNotFound, map[string]string{"error": err.Error()}) + } + return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to send termail"}) + } + + return c.JSON(http.StatusCreated, map[string]any{ + "message": "Termail sent successfully", + "termail": termail, + }) +} + +func HandleGetInbox(c echo.Context) error { + userID := c.Get("user_id").(int) + + limit := 10 + if l := c.QueryParam("limit"); l != "" { + if parsed, err := strconv.Atoi(l); err == nil { + limit = parsed + } + } + + offset := 0 + if o := c.QueryParam("offset"); o != "" { + if parsed, err := strconv.Atoi(o); err == nil { + offset = parsed + } + } + + search := c.QueryParam("search") + var termails []db.Termail + var err error + + if search != "" { + termails, err = db.SearchTermails(c.Request().Context(), userID, search, limit, offset) + } else { + termails, err = db.GetInbox(c.Request().Context(), userID, limit, offset) + } + + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to fetch inbox"}) + } + + return c.JSON(http.StatusOK, map[string]any{ + "termails": termails, + "count": len(termails), + }) +} + +func HandleMarkTermailRead(c echo.Context) error { + userID := c.Get("user_id").(int) + termailID, err := strconv.Atoi(c.Param("id")) + if err != nil { + return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid termail ID"}) + } + + err = db.MarkTermailAsRead(c.Request().Context(), termailID, userID) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to mark termail as read"}) + } + + return c.JSON(http.StatusOK, map[string]string{"message": "Termail marked as read"}) +} + +func HandleDeleteTermail(c echo.Context) error { + userID := c.Get("user_id").(int) + termailID, err := strconv.Atoi(c.Param("id")) + if err != nil { + return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid termail ID"}) + } + + err = db.DeleteTermail(c.Request().Context(), termailID, userID) + if err != nil { + if strings.Contains(err.Error(), "not found") { + return c.JSON(http.StatusNotFound, map[string]string{"error": err.Error()}) + } + return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to delete termail"}) + } + + return c.JSON(http.StatusOK, map[string]string{"message": "Termail deleted"}) +} + func HandleLogin(c echo.Context) error { var req db.LoginRequest if err := c.Bind(&req); err != nil {