diff --git a/config/config.go b/config/config.go index 62ae8f7..f7fe615 100644 --- a/config/config.go +++ b/config/config.go @@ -28,6 +28,14 @@ var ( BcryptCost int `yaml:"bcrypt_cost"` JWTSecret string `yaml:"jwt_secret"` } + + Permissions struct { + Admin string `yaml:"admin"` + ManageUser string `yaml:"manage_user"` + ManagePermissions string `yaml:"manager_permissions"` + ManageProjects string `yaml:"manage_projects"` + ManageStore string `yaml:"manage_store"` + } ) func Parse() { @@ -47,6 +55,10 @@ func Parse() { log.Fatalf("error: %v", err) } + if err := yaml.Unmarshal(read("permissions.yaml"), &Permissions); err != nil { + log.Fatalf("error: %v", err) + } + log.Print("Loaded config files") } diff --git a/config/permissions.yaml b/config/permissions.yaml new file mode 100644 index 0000000..5f9c009 --- /dev/null +++ b/config/permissions.yaml @@ -0,0 +1,5 @@ +admin: admin +manage_user: manage_user +manage_permissions: manage_permissions +manage_projects: manage_projects +manage_store: manage_store diff --git a/internal/controller/http/v1/user.go b/internal/controller/http/v1/user.go index 2f28531..511661e 100644 --- a/internal/controller/http/v1/user.go +++ b/internal/controller/http/v1/user.go @@ -6,9 +6,11 @@ import ( "log" "net/http" + "github.com/devkcud/arkhon-foundation/arkhon-api/config" "github.com/devkcud/arkhon-foundation/arkhon-api/internal/model/dto" "github.com/devkcud/arkhon-foundation/arkhon-api/internal/service" "github.com/devkcud/arkhon-foundation/arkhon-api/pkg/middleware" + "github.com/devkcud/arkhon-foundation/arkhon-api/pkg/utils" "github.com/gin-gonic/gin" "gorm.io/gorm" ) @@ -16,10 +18,10 @@ import ( func newUserRoutes(handler *gin.RouterGroup) { h := handler.Group("/user") { - h.GET("/:username/profile", middleware.OptionalAuthMiddleware, GetProfileHandler) + h.GET("/:username/profile", middleware.OptionalAuthMiddleware, middleware.GetPermissionsMiddleware, GetProfileHandler) - h.GET("/:username/followers", middleware.OptionalAuthMiddleware, GetFollowersHandler) - h.GET("/:username/following", middleware.OptionalAuthMiddleware, GetFollowingHandler) + h.GET("/:username/followers", middleware.OptionalAuthMiddleware, middleware.GetPermissionsMiddleware, GetFollowersHandler) + h.GET("/:username/following", middleware.OptionalAuthMiddleware, middleware.GetPermissionsMiddleware, GetFollowingHandler) h.GET("/:username/permissions", GetUserPermissions) @@ -37,9 +39,11 @@ func GetProfileHandler(ctx *gin.Context) { username := ctx.Param("username") user, err := service.User.GetByUsername(username) if err == nil { - if user.Show.Profile == -1 && (issuer == nil || issuer.ID != user.ID) { - ctx.JSON(http.StatusForbidden, gin.H{"error": "User disabled viewing their profile"}) - return + if !utils.HasPermissionsByContext(ctx, config.Permissions.ManageUser) { + if user.Show.Profile == -1 && (issuer == nil || issuer.ID != user.ID) { + ctx.JSON(http.StatusForbidden, gin.H{"error": "User disabled viewing their profile"}) + return + } } ctx.JSON(http.StatusOK, user) @@ -76,14 +80,16 @@ func GetFollowersHandler(ctx *gin.Context) { return } - if user.Show.Profile == -1 && (issuer == nil || issuer.ID != user.ID) { - ctx.JSON(http.StatusForbidden, gin.H{"error": "User disabled viewing their profile"}) - return - } + if !utils.HasPermissionsByContext(ctx, config.Permissions.ManageUser) { + if user.Show.Profile == -1 && (issuer == nil || issuer.ID != user.ID) { + ctx.JSON(http.StatusForbidden, gin.H{"error": "User disabled viewing their profile"}) + return + } - if user.Show.Followers == -1 && (issuer == nil || issuer.ID != user.ID) { - ctx.JSON(http.StatusForbidden, gin.H{"error": "User disabled viewing whom are following them"}) - return + if user.Show.Followers == -1 && (issuer == nil || issuer.ID != user.ID) { + ctx.JSON(http.StatusForbidden, gin.H{"error": "User disabled viewing whom are following them"}) + return + } } followers, err := service.Follow.GetFollowers(user.ID) @@ -115,14 +121,16 @@ func GetFollowingHandler(ctx *gin.Context) { return } - if user.Show.Profile == -1 && (issuer == nil || issuer.ID != user.ID) { - ctx.JSON(http.StatusForbidden, gin.H{"error": "User disabled viewing their profile"}) - return - } + if !utils.HasPermissionsByContext(ctx, config.Permissions.ManageUser) { + if user.Show.Profile == -1 && (issuer == nil || issuer.ID != user.ID) { + ctx.JSON(http.StatusForbidden, gin.H{"error": "User disabled viewing their profile"}) + return + } - if user.Show.Following == -1 && (issuer == nil || issuer.ID != user.ID) { - ctx.JSON(http.StatusForbidden, gin.H{"error": "User disabled viewing whom they are following"}) - return + if user.Show.Following == -1 && (issuer == nil || issuer.ID != user.ID) { + ctx.JSON(http.StatusForbidden, gin.H{"error": "User disabled viewing whom they are following"}) + return + } } following, err := service.Follow.GetFollowing(user.ID) diff --git a/internal/service/repository/permission.go b/internal/service/repository/permission.go index 6eed89c..0a1ff15 100644 --- a/internal/service/repository/permission.go +++ b/internal/service/repository/permission.go @@ -22,8 +22,9 @@ func (pr permissionRepository) GetPermissions(userID uint) ([]*model.Permission, var permissions []*model.Permission err := pr.db.Table("users"). - Select("users.id, permissions.id, permissions.name"). - Joins("JOIN permissions ON permissions.id = users.id"). + Select("permissions.id, permissions.name"). + Joins("JOIN user_permissions ON user_permissions.user_id = users.id"). + Joins("JOIN permissions ON permissions.id = user_permissions.permission_id"). Where("users.id = ?", userID). Scan(&permissions).Error diff --git a/pkg/db/postgres.go b/pkg/db/postgres.go index 99d13f4..bbad43b 100644 --- a/pkg/db/postgres.go +++ b/pkg/db/postgres.go @@ -37,11 +37,11 @@ func Load() { } Postgres.Create([]model.Permission{ - {Name: "admin"}, - {Name: "manage_user"}, - {Name: "manage_permissions"}, - {Name: "manage_projects"}, - {Name: "manage_store"}, + {Name: config.Permissions.Admin}, + {Name: config.Permissions.ManageUser}, + {Name: config.Permissions.ManagePermissions}, + {Name: config.Permissions.ManageProjects}, + {Name: config.Permissions.ManageStore}, }) log.Print("Loaded migrations") diff --git a/pkg/middleware/permission.go b/pkg/middleware/permission.go new file mode 100644 index 0000000..37cb4a3 --- /dev/null +++ b/pkg/middleware/permission.go @@ -0,0 +1,36 @@ +package middleware + +import ( + "github.com/devkcud/arkhon-foundation/arkhon-api/internal/model/dto" + "github.com/devkcud/arkhon-foundation/arkhon-api/internal/service" + "github.com/gin-gonic/gin" +) + +// GetPermissionsMiddleware must be after OptionalAuthMiddleware or AuthMiddleware +func GetPermissionsMiddleware(ctx *gin.Context) { + var issuer *dto.ProfileSearch = nil + p, exists := ctx.Get("auth_user") + + if !exists { + ctx.Set("permissions", []string{}) // Set to an empty string so it can be queried anyway + ctx.Next() + return + } + + issuer = p.(*dto.ProfileSearch) + + permissions, err := service.Permission.GetPermissions(issuer.ID) + if err != nil { + ctx.Set("permissions", []string{}) + ctx.Next() + return + } + + var list []string + + for _, permission := range permissions { + list = append(list, permission.Name) + } + + ctx.Set("permissions", list) +} diff --git a/pkg/utils/permission.go b/pkg/utils/permission.go new file mode 100644 index 0000000..a4cf13b --- /dev/null +++ b/pkg/utils/permission.go @@ -0,0 +1,32 @@ +package utils + +import ( + "slices" + + "github.com/devkcud/arkhon-foundation/arkhon-api/config" + "github.com/gin-gonic/gin" +) + +// There is no need to pass anything to check for admin roles +func HasPermissions(list []string, lookup ...string) bool { + if slices.Contains(list, config.Permissions.Admin) { + return true + } + + if len(lookup) == 0 { + return false + } + + for _, perm := range lookup { + if !slices.Contains(list, perm) { + return false + } + } + + return true +} + +// WARN: must have the GetPermissionsMiddleware in the route before using +func HasPermissionsByContext(ctx *gin.Context, lookup ...string) bool { + return HasPermissions(ctx.Keys["permissions"].([]string), lookup...) +}