diff --git a/api/routes.go b/api/routes.go index 98efe0c..8f06a27 100644 --- a/api/routes.go +++ b/api/routes.go @@ -1,27 +1,77 @@ package api +import ( + "gitea.zjmud.xyz/phyer/rbac/controllers" + "gitea.zjmud.xyz/phyer/rbac/middleware" + "github.com/gin-gonic/gin" +) + +func SetupRoutes(r *gin.Engine) { + auth := r.Group("/auth") + { + auth.POST("/register", controllers.Register) + auth.POST("/login", controllers.Login) + } + + api := r.Group("/api") + api.Use(middleware.AuthMiddleware()) + { + users := api.Group("/users") + { + users.GET("/", controllers.GetUsers) + users.GET("/:id", controllers.GetUser) + users.PUT("/:id", controllers.UpdateUser) + users.DELETE("/:id", controllers.DeleteUser) + } + } +} +package api + import ( "gitea.zjmud.xyz/phyer/rbac/controllers" "gitea.zjmud.xyz/phyer/rbac/middleware" - "github.com/gin-gonic/gin" ) func SetupRoutes(r *gin.Engine) { + // 认证相关路由 auth := r.Group("/auth") { - auth.POST("/login", controllers.Login) auth.POST("/register", controllers.Register) + auth.POST("/login", controllers.Login) } - admin := r.Group("/admin") - admin.Use(middleware.RBACMiddleware("admin", "full-access")) + // 需要认证的路由 + api := r.Group("/api") + api.Use(middleware.JWTAuth()) { - admin.POST("/roles", controllers.CreateRole) - admin.POST("/users", controllers.CreateUser) - admin.POST("/permissions", controllers.CreatePermission) - admin.POST("/user-groups", controllers.CreateUserGroup) - admin.POST("/assign-role", controllers.AssignRoleToUser) - admin.POST("/assign-permission", controllers.AssignPermissionToRole) + // 用户管理 + users := api.Group("/users") + { + users.GET("/", controllers.GetUsers) + users.GET("/:id", controllers.GetUser) + users.PUT("/:id", controllers.UpdateUser) + users.DELETE("/:id", controllers.DeleteUser) + } + + // 角色管理 + roles := api.Group("/roles") + { + roles.POST("/", controllers.CreateRole) + roles.GET("/", controllers.GetRoles) + roles.GET("/:id", controllers.GetRole) + roles.PUT("/:id", controllers.UpdateRole) + roles.DELETE("/:id", controllers.DeleteRole) + } + + // 权限管理 + permissions := api.Group("/permissions") + { + permissions.POST("/", controllers.CreatePermission) + permissions.GET("/", controllers.GetPermissions) + permissions.GET("/:id", controllers.GetPermission) + permissions.PUT("/:id", controllers.UpdatePermission) + permissions.DELETE("/:id", controllers.DeletePermission) + } } } diff --git a/config/config.go b/config/config.go index a7c9860..2e4f8b0 100644 --- a/config/config.go +++ b/config/config.go @@ -1,57 +1,49 @@ -package controllers +package config import ( - "gitea.zjmud.xyz/phyer/rbac/models" - "gitea.zjmud.xyz/phyer/rbac/services" - "net/http" - "time" - - "github.com/gin-gonic/gin" - "github.com/golang-jwt/jwt/v5" + "os" + "strconv" ) -func Login(c *gin.Context) { - var loginData struct { - Username string `json:"username"` - Password string `json:"password"` - } - - if err := c.ShouldBindJSON(&loginData); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - user, err := services.Authenticate(loginData.Username, loginData.Password) - if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"}) - return - } - - token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "sub": user.ID, - "exp": time.Now().Add(time.Hour * 24).Unix(), - }) - - tokenString, err := token.SignedString([]byte("your-256-bit-secret")) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"}) - return - } - - c.JSON(http.StatusOK, gin.H{"token": tokenString}) +type Config struct { + DBHost string + DBPort int + DBUser string + DBPassword string + DBName string + JWTSecret string + RedisHost string + RedisPort int + RedisPassword string } -func Register(c *gin.Context) { - var user models.User - if err := c.ShouldBindJSON(&user); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } +var AppConfig Config - if err := services.CreateUser(&user); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return +func Init() { + AppConfig = Config{ + DBHost: getEnv("DB_HOST", "localhost"), + DBPort: getEnvAsInt("DB_PORT", 3306), + DBUser: getEnv("DB_USER", "root"), + DBPassword: getEnv("DB_PASSWORD", ""), + DBName: getEnv("DB_NAME", "rbac"), + JWTSecret: getEnv("JWT_SECRET", "secret"), + RedisHost: getEnv("REDIS_HOST", "localhost"), + RedisPort: getEnvAsInt("REDIS_PORT", 6379), + RedisPassword: getEnv("REDIS_PASSWORD", ""), } - - c.JSON(http.StatusCreated, user) +} + +func getEnv(key, defaultValue string) string { + if value, exists := os.LookupEnv(key); exists { + return value + } + return defaultValue +} + +func getEnvAsInt(key string, defaultValue int) int { + valueStr := getEnv(key, "") + if value, err := strconv.Atoi(valueStr); err == nil { + return value + } + return defaultValue } diff --git a/controllers/auth.go b/controllers/auth.go index a7c9860..f16bdf0 100644 --- a/controllers/auth.go +++ b/controllers/auth.go @@ -1,57 +1,41 @@ package controllers import ( - "gitea.zjmud.xyz/phyer/rbac/models" "gitea.zjmud.xyz/phyer/rbac/services" - "net/http" - "time" - "github.com/gin-gonic/gin" - "github.com/golang-jwt/jwt/v5" ) +func Register(c *gin.Context) { + var registerData struct { + Username string `json:"username"` + Password string `json:"password"` + Email string `json:"email"` + } + if err := c.ShouldBindJSON(®isterData); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + user, err := services.RegisterUser(registerData.Username, registerData.Password, registerData.Email) + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + c.JSON(201, user) +} + func Login(c *gin.Context) { var loginData struct { Username string `json:"username"` Password string `json:"password"` } - if err := c.ShouldBindJSON(&loginData); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + c.JSON(400, gin.H{"error": err.Error()}) return } - - user, err := services.Authenticate(loginData.Username, loginData.Password) + token, err := services.Login(loginData.Username, loginData.Password) if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"}) + c.JSON(401, gin.H{"error": err.Error()}) return } - - token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "sub": user.ID, - "exp": time.Now().Add(time.Hour * 24).Unix(), - }) - - tokenString, err := token.SignedString([]byte("your-256-bit-secret")) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"}) - return - } - - c.JSON(http.StatusOK, gin.H{"token": tokenString}) -} - -func Register(c *gin.Context) { - var user models.User - if err := c.ShouldBindJSON(&user); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if err := services.CreateUser(&user); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusCreated, user) + c.JSON(200, gin.H{"token": token}) } diff --git a/controllers/rbac.go b/controllers/rbac.go index eddb833..e69de29 100644 --- a/controllers/rbac.go +++ b/controllers/rbac.go @@ -1,24 +0,0 @@ -package controllers - -import ( - "gitea.zjmud.xyz/phyer/rbac/models" - "gitea.zjmud.xyz/phyer/rbac/services" - "net/http" - - "github.com/gin-gonic/gin" -) - -func CreateRole(c *gin.Context) { - var role models.Role - if err := c.ShouldBindJSON(&role); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if err := services.CreateRole(&role); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusCreated, role) -} diff --git a/controllers/user.go b/controllers/user.go index 6c477dc..f3f3ef6 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -1,24 +1,50 @@ package controllers import ( - "gitea.zjmud.xyz/phyer/rbac/models" "gitea.zjmud.xyz/phyer/rbac/services" - "net/http" - "github.com/gin-gonic/gin" ) -func CreateUser(c *gin.Context) { - var user models.User - if err := c.ShouldBindJSON(&user); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) +func GetUsers(c *gin.Context) { + users, err := services.GetAllUsers() + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) return } - - if err := services.CreateUser(&user); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusCreated, user) + c.JSON(200, users) +} + +func GetUser(c *gin.Context) { + id := c.Param("id") + user, err := services.GetUserByID(id) + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + c.JSON(200, user) +} + +func UpdateUser(c *gin.Context) { + id := c.Param("id") + var updateData map[string]interface{} + if err := c.ShouldBindJSON(&updateData); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + user, err := services.UpdateUser(id, updateData) + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + c.JSON(200, user) +} + +func DeleteUser(c *gin.Context) { + id := c.Param("id") + err := services.DeleteUser(id) + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + c.JSON(200, gin.H{"message": "User deleted successfully"}) } diff --git a/go.mod b/go.mod index 81773a0..e69112d 100644 --- a/go.mod +++ b/go.mod @@ -4,39 +4,8 @@ go 1.21 require ( github.com/gin-gonic/gin v1.9.1 + github.com/go-redis/redis/v8 v8.11.5 github.com/golang-jwt/jwt/v5 v5.0.0 - gorm.io/driver/sqlite v1.5.4 - gorm.io/gorm v1.25.5 - github.com/caarlos0/env/v10 v10.0.0 -) - -require ( - github.com/bytedance/sonic v1.11.6 // indirect - github.com/bytedance/sonic/loader v0.1.1 // indirect - github.com/cloudwego/base64x v0.1.4 // indirect - github.com/cloudwego/iasm v0.2.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.20.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/jinzhu/now v1.1.5 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect - github.com/leodido/go-urn v1.4.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.12 // indirect - golang.org/x/arch v0.8.0 // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect - google.golang.org/protobuf v1.34.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/mysql v1.5.1 + gorm.io/gorm v1.25.2 ) diff --git a/go.sum b/go.sum index fdbffb5..e69de29 100644 --- a/go.sum +++ b/go.sum @@ -1,97 +0,0 @@ -github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= -github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= -github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= -github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= -github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= -github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= -github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= -github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= -golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= -gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/main.go b/main.go index 18c1daf..f4ff213 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,34 @@ package main -import "gitea.zjmud.xyz/phyer/rbac/server" +import ( + "gitea.zjmud.xyz/phyer/rbac/api" + "gitea.zjmud.xyz/phyer/rbac/config" + "gitea.zjmud.xyz/phyer/rbac/server" +) func main() { - server.Start() + config.Init() + server.InitDB() + r := server.NewServer() + api.SetupRoutes(r) + r.Run(":8080") } + +// package main +// +// import ( +// "gitea.zjmud.xyz/phyer/rbac/config" +// "gitea.zjmud.xyz/phyer/rbac/server" +// "gitea.zjmud.xyz/phyer/rbac/utils" +// ) +// +// func main() { +// // 初始化配置 +// config.Init() +// +// // 初始化 Redis +// utils.InitRedis() +// +// // 启动服务器 +// server.Start() +// } diff --git a/middleware/auth.go b/middleware/auth.go index b37adc1..f12bd72 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -1,42 +1,26 @@ package middleware import ( - "fmt" - "gitea.zjmud.xyz/phyer/rbac/config" - "net/http" - + "gitea.zjmud.xyz/phyer/rbac/utils" "github.com/gin-gonic/gin" - "github.com/golang-jwt/jwt/v5" ) -func JWTAuthMiddleware() gin.HandlerFunc { - cfg := config.LoadConfig() +func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { - tokenString := c.GetHeader("Authorization") - if tokenString == "" { - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Authorization header missing"}) + token := c.GetHeader("Authorization") + if token == "" { + c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"}) return } - 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: %v", token.Header["alg"]) - } - return []byte(cfg.JWTSecret), nil - }) - - if err != nil || !token.Valid { - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"}) + claims, err := utils.ParseJWT(token) + if err != nil { + c.AbortWithStatusJSON(401, gin.H{"error": "Invalid token"}) return } - claims, ok := token.Claims.(jwt.MapClaims) - if !ok { - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token claims"}) - return - } - - c.Set("userID", claims["sub"]) + userID := claims["user_id"].(string) + c.Set("userID", userID) c.Next() } } diff --git a/middleware/rbac.go b/middleware/rbac.go new file mode 100644 index 0000000..c080df7 --- /dev/null +++ b/middleware/rbac.go @@ -0,0 +1,24 @@ +package middleware + +import ( + "gitea.zjmud.xyz/phyer/rbac/repositories" + "github.com/gin-gonic/gin" +) + +func RBACMiddleware(permission string) gin.HandlerFunc { + return func(c *gin.Context) { + userID := c.GetString("userID") + if userID == "" { + c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"}) + return + } + + hasPermission, err := repositories.CheckUserPermission(userID, permission) + if err != nil || !hasPermission { + c.AbortWithStatusJSON(403, gin.H{"error": "Forbidden"}) + return + } + + c.Next() + } +} diff --git a/models/models.go b/models/models.go deleted file mode 100644 index 2771834..0000000 --- a/models/models.go +++ /dev/null @@ -1,28 +0,0 @@ -package models - -import "gorm.io/gorm" - -type User struct { - gorm.Model - Username string `gorm:"unique"` - Password string - Roles []Role `gorm:"many2many:user_roles;"` -} - -type Role struct { - gorm.Model - Name string `gorm:"unique"` - Permissions []Permission `gorm:"many2many:role_permissions;"` -} - -type Permission struct { - gorm.Model - Name string `gorm:"unique"` -} - -type UserGroup struct { - gorm.Model - Name string `gorm:"unique"` - Users []User `gorm:"many2many:user_group_users;"` -} -` diff --git a/models/permission.go b/models/permission.go new file mode 100644 index 0000000..e69de29 diff --git a/models/role.go b/models/role.go new file mode 100644 index 0000000..e69de29 diff --git a/models/user.go b/models/user.go new file mode 100644 index 0000000..d0566ab --- /dev/null +++ b/models/user.go @@ -0,0 +1,15 @@ +package models + +import ( + "time" +) + +type User struct { + ID string `json:"id" gorm:"primaryKey"` + Username string `json:"username" gorm:"uniqueIndex;not null"` + Password string `json:"-" gorm:"not null"` + Email string `json:"email" gorm:"uniqueIndex;not null"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Roles []Role `json:"roles" gorm:"many2many:user_roles;"` +} diff --git a/models/user_group.go b/models/user_group.go new file mode 100644 index 0000000..e69de29 diff --git a/repositories/permission.go b/repositories/permission.go index 67b2b9d..e69de29 100644 --- a/repositories/permission.go +++ b/repositories/permission.go @@ -1,10 +0,0 @@ -package repositories - -import ( - "gitea.zjmud.xyz/phyer/rbac/models" - "gorm.io/gorm" -) - -func CreatePermission(permission *models.Permission) error { - return db.Create(permission).Error -} diff --git a/repositories/role.go b/repositories/role.go index 1f5b51e..e69de29 100644 --- a/repositories/role.go +++ b/repositories/role.go @@ -1,10 +0,0 @@ -package repositories - -import ( - "gitea.zjmud.xyz/phyer/rbac/models" - "gorm.io/gorm" -) - -func CreateRole(role *models.Role) error { - return db.Create(role).Error -} diff --git a/repositories/user.go b/repositories/user.go index 8e569eb..5a8cf7f 100644 --- a/repositories/user.go +++ b/repositories/user.go @@ -1,16 +1,55 @@ package repositories import ( + "gitea.zjmud.xyz/phyer/rbac/config" "gitea.zjmud.xyz/phyer/rbac/models" "gorm.io/gorm" ) -func GetUserByUsername(username string) (*models.User, error) { +var db *gorm.DB + +func InitDB() error { + var err error + db, err = gorm.Open(mysql.Open(config.GetDSN()), &gorm.Config{}) + if err != nil { + return err + } + return db.AutoMigrate(&models.User{}) +} + +func GetAllUsers() ([]models.User, error) { + var users []models.User + result := db.Find(&users) + return users, result.Error +} + +func GetUserByID(id string) (*models.User, error) { var user models.User - result := db.Where("username = ?", username).First(&user) + result := db.First(&user, "id = ?", id) return &user, result.Error } -func CreateUser(user *models.User) error { - return db.Create(user).Error +func GetUserByUsername(username string) (*models.User, error) { + var user models.User + result := db.First(&user, "username = ?", username) + return &user, result.Error +} + +func CreateUser(user *models.User) (*models.User, error) { + result := db.Create(user) + return user, result.Error +} + +func UpdateUser(id string, updateData map[string]interface{}) (*models.User, error) { + var user models.User + result := db.Model(&user).Where("id = ?", id).Updates(updateData) + if result.Error != nil { + return nil, result.Error + } + return GetUserByID(id) +} + +func DeleteUser(id string) error { + result := db.Delete(&models.User{}, "id = ?", id) + return result.Error } diff --git a/repositories/user_group.go b/repositories/user_group.go index 4c9fafb..e69de29 100644 --- a/repositories/user_group.go +++ b/repositories/user_group.go @@ -1,10 +0,0 @@ -package repositories - -import ( - "gitea.zjmud.xyz/phyer/rbac/models" - "gorm.io/gorm" -) - -func CreateUserGroup(group *models.UserGroup) error { - return db.Create(group).Error -} diff --git a/server/server.go b/server/server.go index d016a4d..f5ad1db 100644 --- a/server/server.go +++ b/server/server.go @@ -2,17 +2,15 @@ package server import ( "gitea.zjmud.xyz/phyer/rbac/api" - "gitea.zjmud.xyz/phyer/rbac/config" - "github.com/gin-gonic/gin" ) func Start() { - cfg := config.LoadConfig() - r := gin.Default() + // 初始化路由 api.SetupRoutes(r) - r.Run(":" + cfg.ServerPort) + // 启动服务器 + r.Run(":8080") } diff --git a/services/auth.go b/services/auth.go index e2e1c49..22ca269 100644 --- a/services/auth.go +++ b/services/auth.go @@ -4,29 +4,22 @@ import ( "errors" "gitea.zjmud.xyz/phyer/rbac/models" "gitea.zjmud.xyz/phyer/rbac/repositories" - + "gitea.zjmud.xyz/phyer/rbac/utils" "golang.org/x/crypto/bcrypt" ) -func Authenticate(username, password string) (*models.User, error) { +func Login(username, password string) (string, error) { user, err := repositories.GetUserByUsername(username) if err != nil { - return nil, errors.New("user not found") + return "", errors.New("invalid credentials") } - - if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { - return nil, errors.New("invalid password") - } - - return user, nil -} - -func CreateUser(user *models.User) error { - hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost) + err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) if err != nil { - return err + return "", errors.New("invalid credentials") } - - user.Password = string(hashedPassword) - return repositories.CreateUser(user) + token, err := utils.GenerateJWT(user.ID) + if err != nil { + return "", err + } + return token, nil } diff --git a/services/rbac.go b/services/rbac.go index e73e174..e69de29 100644 --- a/services/rbac.go +++ b/services/rbac.go @@ -1,12 +0,0 @@ -package services - -import "gitea.zjmud.xyz/phyer/rbac/models" - -func CreateRole(role *models.Role) error { - return repositories.CreateRole(role) -} - -func AssignRoleToUser(userID uint, roleID uint) error { - // 实现角色分配逻辑 - return nil -} diff --git a/services/user.go b/services/user.go index bcfa100..58220bf 100644 --- a/services/user.go +++ b/services/user.go @@ -1,7 +1,44 @@ package services -import "gitea.zjmud.xyz/phyer/rbac/models" +import ( + "gitea.zjmud.xyz/phyer/rbac/models" + "gitea.zjmud.xyz/phyer/rbac/repositories" + "gitea.zjmud.xyz/phyer/rbac/utils" + "golang.org/x/crypto/bcrypt" +) -func CreateUser(user *models.User) error { +func GetAllUsers() ([]models.User, error) { + return repositories.GetAllUsers() +} + +func GetUserByID(id string) (*models.User, error) { + return repositories.GetUserByID(id) +} + +func UpdateUser(id string, updateData map[string]interface{}) (*models.User, error) { + if password, ok := updateData["password"]; ok { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password.(string)), bcrypt.DefaultCost) + if err != nil { + return nil, err + } + updateData["password"] = string(hashedPassword) + } + return repositories.UpdateUser(id, updateData) +} + +func DeleteUser(id string) error { + return repositories.DeleteUser(id) +} + +func RegisterUser(username, password, email string) (*models.User, error) { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return nil, err + } + user := &models.User{ + Username: username, + Password: string(hashedPassword), + Email: email, + } return repositories.CreateUser(user) } diff --git a/sug.md b/sug.md index 30128c0..e69de29 100644 --- a/sug.md +++ b/sug.md @@ -1,475 +0,0 @@ -Filepath: api/admin.go - ---- - -- Datetime: 2025-02-16 02:30:16 - -- Model: ollamav3/chsword/DeepSeek-V3 - -- Selected files: - - main.go - - api/routes.go - - middleware/auth.go - - config/config.go - - models/models.go - -> router里动作对应的service,请给出实现 -> - -To implement the service for the router's actions, we need to create a new file `services.py` in the project root directory `/Users/zhangkun/data/go/test/testai`. - -Here are the *SEARCH/REPLACE* blocks: - -Filepath: main.go -Replace lines: 0-0 -```go -import "services" - -router.HandleFunc("/action1", services.action1Handler) -router.HandleFunc("/action2", services.action2Handler) -``` - -Filepath: services.go - ---- - -- Datetime: 2025-02-16 02:32:35 - -- Model: ollamav3/chsword/DeepSeek-V3 - -- Selected files: - - main.go - - api/routes.go - - middleware/auth.go - - config/config.go - - models/models.go - -> admin.POST("/roles", CreateRole) -> admin.POST("/users", CreateUser) -> admin.POST("/permissions", CreatePermission) -> admin.POST("/user-groups", CreateUserGroup) -> admin.POST("/assign-role", AssignRoleToUser) -> admin.POST("/assign-permission", AssignPermissionToRole) -> 这里的 动作都是未定义的,需要你给出定义 - -To make these actions defined, we need to add the necessary functions and routes. Here's an example of how you can define them: - -```markdown -# Define the CreateRole function -def CreateRole(request): - # Get the role data from the request body - role_data = request.get_json() - - # Create a new Role object with the provided data - role = Role(name=role_data['name'], description=role_data['description']) - - # Save the role to the database - db.session.add(role) - db.session.commit() - - # Return the created role as JSON - return jsonify({'id': role.id, 'name': role.name, 'description': role.description}) - -# Define the CreateUser function -def CreateUser(request): - # Get the user data from the request body - user_data = request.get_json() - - # Create a new User object with the provided data - user = User(username=user_data['username'], email=user_data['email']) - - # Hash the password using bcrypt - user.password = bcrypt.hashpw(user.password.encode('utf-8'), bcrypt.gensalt()) - - # Save the user to the database - db.session.add(user) - db.session.commit() - - # Return the created user as JSON - return jsonify({'id': user.id, 'username': user.username, 'email': user.email}) - -# Define the CreatePermission function -def CreatePermission(request): - # Get the permission data from the request body - permission_data = request.get_json() - - # Create a new Permission object with the provided data - permission = Permission(name=permission_data['name'], description=permission_data['description']) - - # Save the permission to the database - db.session.add(permission) - db.session.commit() - - # Return the created permission as JSON - return jsonify({'id': permission.id, 'name': permission.name, 'description': permission.description}) - -# Define the CreateUserGroup function -def CreateUserGroup(request): - # Get the group data from the request body - group_data = request.get_json() - - # Create a new Group object with the provided data - group = Group(name=group_data['name'], description=group_data['description']) - - # Save the group to the database - db.session.add(group) - db.session.commit() - - # Return the created group as JSON - return jsonify({'id': group.id, 'name': group.name, 'description': group.description}) - -# Define the AssignRoleToUser function -def AssignRoleToUser(request): - # Get the user ID and role ID from the request body - user_id = request.get_json()['user_id'] - role_id = request.get_json()['role_id'] - - # Find the user and role objects in the database - user = User.query.get(user_id) - role = Role.query.get(role_id) - - # Assign the role to the user - user.roles.append(role) - - # Save the changes to the database - db.session.commit() - - # Return a success message as JSON - return jsonify({'message': 'Role assigned successfully'}) - -# Define the AssignPermissionToRole function -def AssignPermissionToRole(request): - # Get the role ID and permission ID from the request body - role_id = request.get_json()['role_id'] - permission_id = request.get_json()['permission_id'] - - # Find the role and permission objects in the database - role = Role.query.get(role_id) - permission = Permission.query.get(permission_id) - - # Assign the permission to the role - role.permissions.append(permission) - - # Save the changes to the database - db.session.commit() - - # Return a success message as JSON - return jsonify({'message': 'Permission assigned successfully'}) -``` - -Note that these functions assume you have already defined the necessary models (e.g. `User`, `Role`, `Group`, etc.) and database sessions. - -Also, I've used Flask's built-in support for JSON data in request bodies to simplify the code. If your application uses a different framework or requires more complex handling of request bodies, you may need to modify these functions accordingly. - -To implement these routes, you would add them to your Flask app like so: - -```python -from flask import Flask, jsonify, request - -app = Flask(__name__) - -# ... (other routes and code) - -admin = Blueprint('admin', __name__, url_prefix='/admin') - -@admin.route('/roles', methods=['POST']) -def CreateRole(): - # Call the CreateRole function defined above - return CreateRole(request) - -@admin.route('/users', methods=['POST']) -def CreateUser(): - # Call the CreateUser function defined above - return CreateUser(request) - -@admin.route('/permissions', methods=['POST']) -def CreatePermission(): - # Call the CreatePermission function defined above - return CreatePermission(request) - -@admin.route('/user-groups', methods=['POST']) -def CreateUserGroup(): - # Call the CreateUserGroup function defined above - return CreateUserGroup(request) - -@admin.route('/assign-role', methods=['POST']) -def AssignRoleToUser(): - # Call the AssignRoleToUser function defined above - return AssignRoleToUser(request) - -@admin.route('/assign-permission', methods=['POST']) -def AssignPermissionToRole(): - # Call the AssignPermissionToRole function defined above - return AssignPermissionToRole(request) -``` - -This assumes you have already created a Blueprint for your admin interface and registered these routes with it. - ---- - -- Datetime: 2025-02-16 02:34:18 - -- Model: ollamav3/chsword/DeepSeek-V3 - -- Selected files: - - main.go - - api/routes.go - - middleware/auth.go - - config/config.go - - models/models.go - -> 这不是python,这是go的gin框架 -> - -I'll make sure to pay attention to the framework. - -So, you want me to modify the `mathweb/flask/app.go` file in the Go Gin framework instead of Python Flask. - -Please confirm if I should proceed with the changes. If so, please provide more details about what changes are needed. - ---- - -- Datetime: 2025-02-16 02:38:40 - -- Model: alidsv3/deepseek-v3 - -- Selected files: - - tree.txt - - api/routes.go - - config/config.go - - middleware/auth.go - - models/models.go - - server/server.go - - main.go - -> 我在一个空目录下, 我需要创建一饿go语言gin框架的api后端项目,项目名字叫 zjmud.xyz/phyer/rbac, 需要帮我生成 go.mod的内容, -> > 然后需要创建一个用户账号系统, 包括基础的用户验证功能和鉴权,鉴权依照rbac模式,管理员可以创建角色、用户、定义资源、资源组、和动作,管理员可以委派某个用户,对特定资源组和用户组进行动作定义, 请把go文件根据逻辑,放置到不同的模块中,不要都放到main 模块下 -> -> 那么当前目录结构是否在正确的方向上, 每个文件是否有需要改动的地方 - -根据你的需求,当前目录结构已经在一个很好的方向上,但还需要做一些调整和补充。让我们一步一步来: - -1. 首先,创建 `go.mod` 文件: - -Filepath: go.mod -Replace lines: 1-0 -```unknown -module zjmud.xyz/phyer/rbac - -go 1.21 - -require ( - github.com/gin-gonic/gin v1.9.1 - github.com/golang-jwt/jwt/v5 v5.0.0 - gorm.io/driver/sqlite v1.5.4 - gorm.io/gorm v1.25.5 - github.com/caarlos0/env/v10 v10.0.0 -) -``` - -2. 当前目录结构基本合理,但建议做一些调整: - -``` -. -├── api -│ └── routes.go -├── config -│ └── config.go -├── controllers -│ ├── auth.go -│ ├── rbac.go -│ └── user.go -├── go.mod -├── go.sum -├── main.go -├── middleware -│ └── auth.go -├── models -│ └── models.go -├── repositories -│ ├── permission.go -│ ├── role.go -│ ├── user.go -│ └── user_group.go -├── server -│ └── server.go -└── services - ├── auth.go - ├── rbac.go - └── user.go -``` - -3. 需要修改和补充的文件: - -Filepath: api/routes.go -Replace lines: 9-26 -```go -func SetupRoutes(r *gin.Engine) { - auth := r.Group("/auth") - { - auth.POST("/login", controllers.Login) - auth.POST("/register", controllers.Register) - } - - admin := r.Group("/admin") - admin.Use(middleware.RBACMiddleware("admin", "full-access")) - { - admin.POST("/roles", controllers.CreateRole) - admin.POST("/users", controllers.CreateUser) - admin.POST("/permissions", controllers.CreatePermission) - admin.POST("/user-groups", controllers.CreateUserGroup) - admin.POST("/assign-role", controllers.AssignRoleToUser) - admin.POST("/assign-permission", controllers.AssignPermissionToRole) - } -} -``` - -Filepath: config/config.go -Replace lines: 15-19 -```go -func LoadConfig() *Config { - cfg := &Config{} - if err := env.Parse(cfg); err != nil { - panic("Failed to load config: " + err.Error()) - } - return cfg -} -``` - -Filepath: middleware/auth.go -Replace lines: 8-13 -```go -func JWTAuthMiddleware(secret string) gin.HandlerFunc { - return func(c *gin.Context) { - tokenString := c.GetHeader("Authorization") - if tokenString == "" { - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Authorization header missing"}) - return - } - - 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: %v", token.Header["alg"]) - } - return []byte(secret), nil - }) - - if err != nil || !token.Valid { - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"}) - return - } - - claims, ok := token.Claims.(jwt.MapClaims) - if !ok { - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token claims"}) - return - } - - c.Set("userID", claims["sub"]) - c.Next() - } -} -``` - -4. 需要创建的新文件: - -Filepath: controllers/auth.go - -package controllers - -import ( - "net/http" - "time" - "zjmud.xyz/phyer/rbac/models" - "zjmud.xyz/phyer/rbac/services" - - "github.com/gin-gonic/gin" - "github.com/golang-jwt/jwt/v5" -) - -func Login(c *gin.Context) { - var loginData struct { - Username string `json:"username"` - Password string `json:"password"` - } - - if err := c.ShouldBindJSON(&loginData); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - user, err := services.Authenticate(loginData.Username, loginData.Password) - if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"}) - return - } - - token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "sub": user.ID, - "exp": time.Now().Add(time.Hour * 24).Unix(), - }) - - tokenString, err := token.SignedString([]byte("your-256-bit-secret")) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"}) - return - } - - c.JSON(http.StatusOK, gin.H{"token": tokenString}) -} - -func Register(c *gin.Context) { - var user models.User - if err := c.ShouldBindJSON(&user); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - if err := services.CreateUser(&user); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - c.JSON(http.StatusCreated, user) -} -``` - -Filepath: services/auth.go - -package services - -import ( - "errors" - "zjmud.xyz/phyer/rbac/models" - "zjmud.xyz/phyer/rbac/repositories" - - "golang.org/x/crypto/bcrypt" -) - -func Authenticate(username, password string) (*models.User, error) { - user, err := repositories.GetUserByUsername(username) - if err != nil { - return nil, errors.New("user not found") - } - - if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { - return nil, errors.New("invalid password") - } - - return user, nil -} - -func CreateUser(user *models.User) error { - hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost) - if err != nil { - return err - } - - user.Password = string(hashedPassword) - return repositories.CreateUser(user) -} -``` - -Filepath: repositories/user.go - - diff --git a/tree.txt b/tree.txt index 0900f19..2b23906 100644 --- a/tree.txt +++ b/tree.txt @@ -1,17 +1,37 @@ . ├── api -│   └── routes.go +│ └── routes.go ├── config -│   └── config.go +│ └── config.go +├── controllers +│ ├── auth.go +│ ├── rbac.go +│ └── user.go ├── go.mod ├── go.sum ├── main.go ├── middleware -│   └── auth.go +│ ├── auth.go +│ └── rbac.go ├── models -│   └── models.go +│ ├── permission.go +│ ├── role.go +│ ├── user.go +│ └── user_group.go +├── repositories +│ ├── permission.go +│ ├── role.go +│ ├── user.go +│ └── user_group.go ├── server -│   └── server.go +│ └── server.go +├── services +│ ├── auth.go +│ ├── rbac.go +│ └── user.go +├── utils +│ ├── jwt.go +│ ├── redis.go +│ └── response.go +├── sug.md └── tree.txt - -6 directories, 9 files diff --git a/utils/jwt.go b/utils/jwt.go new file mode 100644 index 0000000..9e749ab --- /dev/null +++ b/utils/jwt.go @@ -0,0 +1,29 @@ +package utils + +import ( + "gitea.zjmud.xyz/phyer/rbac/config" + "github.com/golang-jwt/jwt/v5" + "time" +) + +func GenerateJWT(userID string) (string, error) { + claims := jwt.MapClaims{ + "user_id": userID, + "exp": time.Now().Add(time.Hour * 24).Unix(), + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString([]byte(config.AppConfig.JWTSecret)) +} + +func ParseJWT(tokenString string) (jwt.MapClaims, error) { + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + return []byte(config.AppConfig.JWTSecret), nil + }) + if err != nil { + return nil, err + } + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + return claims, nil + } + return nil, errors.New("invalid token") +} diff --git a/utils/redis.go b/utils/redis.go new file mode 100644 index 0000000..e69de29 diff --git a/utils/response.go b/utils/response.go new file mode 100644 index 0000000..e69de29