From 37d0d41570c33cf5ba6ed3d90d4919d32bbe5a5c Mon Sep 17 00:00:00 2001 From: greysoh Date: Sun, 22 Dec 2024 11:36:15 -0500 Subject: [PATCH] feature: Adds API refresh support. --- backend/api/controllers/v1/users/refresh.go | 117 ++++++++++++++++++ backend/api/main.go | 1 + routes/Hermes API/Users/Refresh JWT Token.bru | 17 +++ 3 files changed, 135 insertions(+) create mode 100644 backend/api/controllers/v1/users/refresh.go create mode 100644 routes/Hermes API/Users/Refresh JWT Token.bru diff --git a/backend/api/controllers/v1/users/refresh.go b/backend/api/controllers/v1/users/refresh.go new file mode 100644 index 0000000..e89611b --- /dev/null +++ b/backend/api/controllers/v1/users/refresh.go @@ -0,0 +1,117 @@ +package users + +import ( + "fmt" + "net/http" + "time" + + "git.terah.dev/imterah/hermes/api/dbcore" + "git.terah.dev/imterah/hermes/api/jwtcore" + "github.com/charmbracelet/log" + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" +) + +type UserRefreshRequest struct { + Token string `validate:"required"` +} + +func RefreshUserToken(c *gin.Context) { + var req UserRefreshRequest + + if err := c.BindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": fmt.Sprintf("Failed to parse body: %s", err.Error()), + }) + + return + } + + if err := validator.New().Struct(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": fmt.Sprintf("Failed to validate body: %s", err.Error()), + }) + + return + } + + var tokenInDatabase *dbcore.Token + tokenRequest := dbcore.DB.Where("token = ?", req.Token).Find(&tokenInDatabase) + + if tokenRequest.Error != nil { + log.Warnf("failed to find if token exists or not: %s", tokenRequest.Error) + + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to find if token exists", + }) + + return + } + + tokenExists := tokenRequest.RowsAffected > 0 + + if !tokenExists { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Token not found", + }) + + return + } + + // First, we check to make sure that the key expiry is disabled before checking if the key is expired. + // Then, we check if the IP addresses differ, or if it has been 7 days since the token has been created. + if !tokenInDatabase.DisableExpiry && (c.ClientIP() != tokenInDatabase.CreationIPAddr || time.Now().Before(tokenInDatabase.CreatedAt.Add((24*7)*time.Hour))) { + c.JSON(http.StatusForbidden, gin.H{ + "error": "Token has expired", + }) + + tx := dbcore.DB.Delete(tokenInDatabase) + + if tx.Error != nil { + log.Warnf("Failed to delete expired token from database: %s", tx.Error.Error()) + } + + return + } + + // Get the user to check if the user exists before doing anything + var user *dbcore.User + userRequest := dbcore.DB.Where("id = ?", tokenInDatabase.UserID).Find(&user) + + if tokenRequest.Error != nil { + log.Warnf("failed to find if token user or not: %s", userRequest.Error) + + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to find user", + }) + + return + } + + userExists := userRequest.RowsAffected > 0 + + if !userExists { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "User not found", + }) + + return + } + + jwt, err := jwtcore.Generate(user.ID) + + if err != nil { + log.Warnf("Failed to generate JWT: %s", err.Error()) + + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to generate refresh token", + }) + + return + } + + c.JSON(http.StatusOK, gin.H{ + "success": true, + "token": jwt, + }) +} diff --git a/backend/api/main.go b/backend/api/main.go index d13a0ab..c8b5ea4 100644 --- a/backend/api/main.go +++ b/backend/api/main.go @@ -93,6 +93,7 @@ func main() { // Initialize routes engine.POST("/api/v1/users/create", users.CreateUser) engine.POST("/api/v1/users/login", users.LoginUser) + engine.POST("/api/v1/users/refresh", users.RefreshUserToken) log.Infof("Listening on: %s", listeningAddress) err = engine.Run(listeningAddress) diff --git a/routes/Hermes API/Users/Refresh JWT Token.bru b/routes/Hermes API/Users/Refresh JWT Token.bru new file mode 100644 index 0000000..9d2e922 --- /dev/null +++ b/routes/Hermes API/Users/Refresh JWT Token.bru @@ -0,0 +1,17 @@ +meta { + name: Refresh JWT Token + type: http + seq: 5 +} + +post { + url: http://127.0.0.1:8000/api/v1/users/refresh + body: json + auth: none +} + +body:json { + { + "token": "HMtM7zKYyVJQqCayMe2G3dcjVU6rCd9Wc+nK6qKbgJ0OZZVBIDYOyBoKES9aVjBvJeM67xgHtJXgPUFDT1jn6JxuQ2Cd1YbhYZHEvt3dCh4=" + } +}