feature: Adds user deletion support.
This commit is contained in:
parent
37d0d41570
commit
cee4e62f53
7 changed files with 169 additions and 7 deletions
|
@ -9,9 +9,9 @@ import (
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
|
|
||||||
"git.terah.dev/imterah/hermes/api/constants"
|
|
||||||
"git.terah.dev/imterah/hermes/api/dbcore"
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
"git.terah.dev/imterah/hermes/api/jwtcore"
|
"git.terah.dev/imterah/hermes/api/jwtcore"
|
||||||
|
permissionHelper "git.terah.dev/imterah/hermes/api/permissions"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
@ -92,7 +92,7 @@ func CreateUser(c *gin.Context) {
|
||||||
|
|
||||||
permissions := []dbcore.Permission{}
|
permissions := []dbcore.Permission{}
|
||||||
|
|
||||||
for _, permission := range constants.DefaultPermissionNodes {
|
for _, permission := range permissionHelper.DefaultPermissionNodes {
|
||||||
permissionEnabledState := false
|
permissionEnabledState := false
|
||||||
|
|
||||||
if unsafeSignup || strings.HasPrefix(permission, "routes.") || permission == "permissions.see" {
|
if unsafeSignup || strings.HasPrefix(permission, "routes.") || permission == "permissions.see" {
|
||||||
|
|
105
backend/api/controllers/v1/users/remove.go
Normal file
105
backend/api/controllers/v1/users/remove.go
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
package users
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"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 UserRemovalRequest struct {
|
||||||
|
Token string `validate:"required"`
|
||||||
|
UID *uint `json:"uid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveUser(c *gin.Context) {
|
||||||
|
var req UserRemovalRequest
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := user.ID
|
||||||
|
|
||||||
|
if req.UID != nil {
|
||||||
|
uid = *req.UID
|
||||||
|
|
||||||
|
if uid != user.ID && !permissions.UserHasPermission(user, "users.remove") {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
"error": "Missing permissions",
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the user exists first if we have a custom UserID
|
||||||
|
|
||||||
|
if uid != user.ID {
|
||||||
|
var customUser *dbcore.User
|
||||||
|
userRequest := dbcore.DB.Where("id = ?", uid).Find(&customUser)
|
||||||
|
|
||||||
|
if userRequest.Error != nil {
|
||||||
|
log.Warnf("failed to find if user exists or not: %s", userRequest.Error)
|
||||||
|
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"error": "Failed to find if user exists",
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userExists := userRequest.RowsAffected > 0
|
||||||
|
|
||||||
|
if !userExists {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "User doesn't exist",
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbcore.DB.Select("Tokens", "Permissions", "Proxys", "Backends").Where("id = ?", uid).Delete(user)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
|
})
|
||||||
|
}
|
|
@ -2,11 +2,13 @@ package jwtcore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,6 +39,49 @@ func Parse(tokenString string, options ...jwt.ParserOption) (*jwt.Token, error)
|
||||||
return jwt.Parse(tokenString, JWTKeyCallback, options...)
|
return jwt.Parse(tokenString, JWTKeyCallback, options...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetUserFromJWT(token string) (*dbcore.User, error) {
|
||||||
|
parsedJWT, err := Parse(token)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, jwt.ErrTokenExpired) {
|
||||||
|
return nil, fmt.Errorf("token is expired")
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
audience, err := parsedJWT.Claims.GetAudience()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(audience) < 1 {
|
||||||
|
return nil, fmt.Errorf("audience is too small")
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, err := strconv.Atoi(audience[0])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &dbcore.User{}
|
||||||
|
userRequest := dbcore.DB.Preload("Permissions").Where("id = ?", uint(uid)).Find(&user)
|
||||||
|
|
||||||
|
if userRequest.Error != nil {
|
||||||
|
return user, fmt.Errorf("failed to find if user exists or not: %s", userRequest.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
userExists := userRequest.RowsAffected > 0
|
||||||
|
|
||||||
|
if !userExists {
|
||||||
|
return user, fmt.Errorf("user does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
func Generate(uid uint) (string, error) {
|
func Generate(uid uint) (string, error) {
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
|
||||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(3 * time.Minute)),
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(3 * time.Minute)),
|
||||||
|
|
|
@ -94,6 +94,7 @@ func main() {
|
||||||
engine.POST("/api/v1/users/create", users.CreateUser)
|
engine.POST("/api/v1/users/create", users.CreateUser)
|
||||||
engine.POST("/api/v1/users/login", users.LoginUser)
|
engine.POST("/api/v1/users/login", users.LoginUser)
|
||||||
engine.POST("/api/v1/users/refresh", users.RefreshUserToken)
|
engine.POST("/api/v1/users/refresh", users.RefreshUserToken)
|
||||||
|
engine.POST("/api/v1/users/remove", users.RemoveUser)
|
||||||
|
|
||||||
log.Infof("Listening on: %s", listeningAddress)
|
log.Infof("Listening on: %s", listeningAddress)
|
||||||
err = engine.Run(listeningAddress)
|
err = engine.Run(listeningAddress)
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
package constants
|
package permissions
|
||||||
|
|
||||||
|
import "git.terah.dev/imterah/hermes/api/dbcore"
|
||||||
|
|
||||||
var DefaultPermissionNodes []string = []string{
|
var DefaultPermissionNodes []string = []string{
|
||||||
"routes.add",
|
"routes.add",
|
||||||
|
@ -24,3 +26,13 @@ var DefaultPermissionNodes []string = []string{
|
||||||
"users.lookup",
|
"users.lookup",
|
||||||
"users.edit",
|
"users.edit",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UserHasPermission(user *dbcore.User, node string) bool {
|
||||||
|
for _, permission := range user.Permissions {
|
||||||
|
if permission.PermissionNode == node && permission.HasPermission {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
|
@ -12,6 +12,6 @@ post {
|
||||||
|
|
||||||
body:json {
|
body:json {
|
||||||
{
|
{
|
||||||
"token": "HMtM7zKYyVJQqCayMe2G3dcjVU6rCd9Wc+nK6qKbgJ0OZZVBIDYOyBoKES9aVjBvJeM67xgHtJXgPUFDT1jn6JxuQ2Cd1YbhYZHEvt3dCh4="
|
"token": "0AqAPZ6P7RTM1DsV18R3MzFmhyoHgn8xMCO35K1zLv6U1J3mUwOUADbT9ZaRsUZRtNLn87YRqyexZzHDgbaQAg9GGLV46g9+UWJKOUgCCSA="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,13 @@ meta {
|
||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
url: http://127.0.0.1:3000/api/v1/users/remove
|
url: http://127.0.0.1:8000/api/v1/users/remove
|
||||||
body: json
|
body: json
|
||||||
auth: inherit
|
auth: inherit
|
||||||
}
|
}
|
||||||
|
|
||||||
body:json {
|
body:json {
|
||||||
{
|
{
|
||||||
"token": "5e2cb92a338a832d385790861312eb85d69f46f82317bfa984ac5e3517368ab5a827897b0f9775a9181b02fa3b9cffed7e59e5b3111d5bdc37f729156caf5f",
|
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiMSJdLCJleHAiOjE3MzQ4ODg4NDMsIm5iZiI6MTczNDg4ODY2MywiaWF0IjoxNzM0ODg4NjYzfQ.diHYYJaQrVAPvWpeqsriH2sbJnuCaBCAzbAL4JoYzqc"
|
||||||
"uid": "2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue