From 611d7f24f8695eeaa1b8ea037777e10ceaebeb1a Mon Sep 17 00:00:00 2001 From: greysoh Date: Sun, 22 Dec 2024 13:58:18 -0500 Subject: [PATCH] feature: Adds user lookup support. --- backend/api/controllers/v1/users/create.go | 4 +- backend/api/controllers/v1/users/lookup.go | 136 +++++++++++++++++++++ backend/api/main.go | 1 + routes/Hermes API/Users/Lookup.bru | 6 +- 4 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 backend/api/controllers/v1/users/lookup.go diff --git a/backend/api/controllers/v1/users/create.go b/backend/api/controllers/v1/users/create.go index 0530554..55d2a3b 100644 --- a/backend/api/controllers/v1/users/create.go +++ b/backend/api/controllers/v1/users/create.go @@ -29,8 +29,6 @@ type UserCreationRequest struct { } func CreateUser(c *gin.Context) { - var req UserCreationRequest - if !signupEnabled && !unsafeSignup { c.JSON(http.StatusForbidden, gin.H{ "error": "Signing up is not enabled at this time.", @@ -39,6 +37,8 @@ func CreateUser(c *gin.Context) { return } + var req UserCreationRequest + if err := c.BindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": fmt.Sprintf("Failed to parse body: %s", err.Error()), diff --git a/backend/api/controllers/v1/users/lookup.go b/backend/api/controllers/v1/users/lookup.go new file mode 100644 index 0000000..54192c3 --- /dev/null +++ b/backend/api/controllers/v1/users/lookup.go @@ -0,0 +1,136 @@ +package users + +import ( + "fmt" + "net/http" + "strings" + + "git.terah.dev/imterah/hermes/api/dbcore" + "git.terah.dev/imterah/hermes/api/jwtcore" + "git.terah.dev/imterah/hermes/api/permissions" + "github.com/charmbracelet/log" + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" +) + +type UserLookupRequest struct { + Token string `validate:"required"` + UID *uint `json:"id"` + Name *string `json:"name"` + Email *string `json:"email"` + Username *string `json:"username"` + IsBot *bool `json:"isServiceAccount"` +} + +type SanitizedUsers struct { + UID uint `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + IsBot bool `json:"isServiceAccount"` +} + +type LookupResponse struct { + Success bool `json:"success"` + Data []*SanitizedUsers `json:"data"` +} + +func LookupUser(c *gin.Context) { + var req UserLookupRequest + + 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 + } + + user, err := jwtcore.GetUserFromJWT(req.Token) + + if err != nil { + if err.Error() == "token is expired" || err.Error() == "user does not exist" { + c.JSON(http.StatusForbidden, gin.H{ + "error": err.Error(), + }) + + return + } else { + log.Warnf("Failed to get user from the provided JWT token: %s", err.Error()) + + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to parse token", + }) + + return + } + } + + users := []dbcore.User{} + queryString := []string{} + queryParameters := []interface{}{} + + if !permissions.UserHasPermission(user, "users.lookup") { + queryString = append(queryString, "id = ?") + queryParameters = append(queryParameters, user.ID) + } else if permissions.UserHasPermission(user, "users.lookup") && req.UID != nil { + queryString = append(queryString, "id = ?") + queryParameters = append(queryParameters, req.UID) + } + + if req.Name != nil { + queryString = append(queryString, "name = ?") + queryParameters = append(queryParameters, req.Name) + } + + if req.Email != nil { + queryString = append(queryString, "email = ?") + queryParameters = append(queryParameters, req.Email) + } + + if req.IsBot != nil { + queryString = append(queryString, "isbot = ?") + queryParameters = append(queryParameters, req.IsBot) + } + + if err := dbcore.DB.Where(strings.Join(queryString, " AND "), queryParameters...).Find(&users).Error; err != nil { + log.Warnf("Failed to get users: %s", err.Error()) + + c.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to get users", + }) + + return + } + + sanitizedUsers := make([]*SanitizedUsers, len(users)) + + for userIndex, user := range users { + isBot := false + + if user.IsBot != nil { + isBot = *user.IsBot + } + + sanitizedUsers[userIndex] = &SanitizedUsers{ + UID: user.ID, + Name: user.Name, + Email: user.Email, + Username: user.Username, + IsBot: isBot, + } + } + + c.JSON(http.StatusOK, &LookupResponse{ + Success: true, + Data: sanitizedUsers, + }) +} diff --git a/backend/api/main.go b/backend/api/main.go index ae1bcf7..d334b33 100644 --- a/backend/api/main.go +++ b/backend/api/main.go @@ -95,6 +95,7 @@ func main() { engine.POST("/api/v1/users/login", users.LoginUser) engine.POST("/api/v1/users/refresh", users.RefreshUserToken) engine.POST("/api/v1/users/remove", users.RemoveUser) + engine.POST("/api/v1/users/lookup", users.LookupUser) log.Infof("Listening on: %s", listeningAddress) err = engine.Run(listeningAddress) diff --git a/routes/Hermes API/Users/Lookup.bru b/routes/Hermes API/Users/Lookup.bru index 352b777..088f40c 100644 --- a/routes/Hermes API/Users/Lookup.bru +++ b/routes/Hermes API/Users/Lookup.bru @@ -5,14 +5,14 @@ meta { } post { - url: http://127.0.0.1:3000/api/v1/users/lookup + url: http://127.0.0.1:8000/api/v1/users/lookup body: json auth: none } body:json { { - "token": "5e2cb92a338a832d385790861312eb85d69f46f82317bfa984ac5e3517368ab5a827897b0f9775a9181b02fa3b9cffed7e59e5b3111d5bdc37f729156caf5f", - "name": "Greyson Hofer" + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiMSJdLCJleHAiOjE3MzQ4OTQwNTEsIm5iZiI6MTczNDg5Mzg3MSwiaWF0IjoxNzM0ODkzODcxfQ.l4GbSKejqeRxSze9Kjj3A-8mxKqUuOz58iHzPOraNmo", + "name": "Test User" } }