From 0411aa37c546928b2d6ec7d8e41285a4dc0d9ebd Mon Sep 17 00:00:00 2001 From: QiuSW <105186638@qq.com> Date: Fri, 14 Aug 2026 12:03:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E5=BB=BA=20Sense=20=E8=AE=A4?= =?UTF-8?q?=E8=AF=81=E6=9D=83=E9=99=90=E4=B8=8E=E5=AE=A1=E8=AE=A1=20(#64)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sense/server/app/admin/apis/captcha.go | 3 +- .../app/admin/apis/identity_bootstrap.go | 94 +++++++++++++ .../app/admin/apis/identity_bootstrap_test.go | 33 +++++ Sense/server/app/admin/apis/sys_user.go | 24 +++- Sense/server/app/admin/router/init_router.go | 2 +- Sense/server/app/admin/router/sys_api.go | 1 - Sense/server/app/admin/router/sys_config.go | 1 - Sense/server/app/admin/router/sys_dept.go | 8 +- Sense/server/app/admin/router/sys_dict.go | 4 +- .../server/app/admin/router/sys_login_log.go | 6 +- Sense/server/app/admin/router/sys_menu.go | 6 +- .../server/app/admin/router/sys_opera_log.go | 6 +- Sense/server/app/admin/router/sys_post.go | 6 +- Sense/server/app/admin/router/sys_role.go | 2 +- Sense/server/app/admin/router/sys_router.go | 5 +- Sense/server/app/admin/router/sys_user.go | 15 ++- .../app/admin/service/password_policy.go | 23 ++++ .../app/admin/service/password_policy_test.go | 26 ++++ Sense/server/app/admin/service/sys_role.go | 14 +- Sense/server/app/admin/service/sys_user.go | 7 +- .../version/2026081412000_identity.go | 124 ++++++++++++++++++ Sense/server/common/middleware/auth.go | 8 +- .../server/common/middleware/handler/auth.go | 65 +++++---- .../common/middleware/handler/auth_test.go | 12 ++ Sense/server/common/middleware/logger.go | 45 ++++++- Sense/server/common/middleware/logger_test.go | 25 ++++ Sense/server/common/middleware/permission.go | 9 ++ Sense/server/common/middleware/settings.go | 36 +---- Sense/server/config/identity.env.example | 3 + Sense/server/config/settings.yml | 2 +- Sense/ui/src/utils/auth.js | 2 +- Sense/ui/src/views/admin/sys-user/index.vue | 15 +-- 32 files changed, 509 insertions(+), 123 deletions(-) create mode 100644 Sense/server/app/admin/apis/identity_bootstrap.go create mode 100644 Sense/server/app/admin/apis/identity_bootstrap_test.go create mode 100644 Sense/server/app/admin/service/password_policy.go create mode 100644 Sense/server/app/admin/service/password_policy_test.go create mode 100644 Sense/server/cmd/migrate/migration/version/2026081412000_identity.go create mode 100644 Sense/server/common/middleware/handler/auth_test.go create mode 100644 Sense/server/common/middleware/logger_test.go create mode 100644 Sense/server/config/identity.env.example diff --git a/Sense/server/app/admin/apis/captcha.go b/Sense/server/app/admin/apis/captcha.go index e884e24..0c6c7a8 100644 --- a/Sense/server/app/admin/apis/captcha.go +++ b/Sense/server/app/admin/apis/captcha.go @@ -21,13 +21,12 @@ func (e System) GenerateCaptchaHandler(c *gin.Context) { e.Error(500, err, "服务初始化失败!") return } - id, b64s, answer, err := captcha.DriverDigitFunc() + id, b64s, _, err := captcha.DriverDigitFunc() if err != nil { e.Logger.Errorf("DriverDigitFunc error, %s", err.Error()) e.Error(500, err, "验证码获取失败") return } - e.Logger.Infof("DriverDigitFunc answer: %s", answer) e.Custom(gin.H{ "code": 200, "data": b64s, diff --git a/Sense/server/app/admin/apis/identity_bootstrap.go b/Sense/server/app/admin/apis/identity_bootstrap.go new file mode 100644 index 0000000..61ab225 --- /dev/null +++ b/Sense/server/app/admin/apis/identity_bootstrap.go @@ -0,0 +1,94 @@ +package apis + +import ( + "crypto/subtle" + "errors" + "net/http" + "os" + "strings" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" + + "git.ilapage.cn/ila/yovision/Sense/server/app/admin/models" + "git.ilapage.cn/ila/yovision/Sense/server/app/admin/service" + "git.ilapage.cn/ila/yovision/Sense/server/common/middleware/handler" +) + +const bootstrapTokenHeader = "X-Sense-Bootstrap-Token" + +type bootstrapRequest struct { + Username string `json:"username" binding:"required"` + Password string `json:"password" binding:"required"` + NickName string `json:"nickName"` +} + +// Bootstrap creates the first Sense administrator. The one-time authorization +// token is supplied out of band and never accepted in the JSON request body. +func (e System) Bootstrap(c *gin.Context) { + if err := e.MakeContext(c).MakeOrm().Errors; err != nil { + e.Error(http.StatusInternalServerError, err, "安全初始化失败") + return + } + db := e.Orm + var req bootstrapRequest + if err := c.ShouldBindJSON(&req); err != nil { + e.Custom(gin.H{"code": http.StatusBadRequest, "msg": "安全初始化不可用或输入不符合要求"}) + return + } + req.Username = strings.TrimSpace(req.Username) + if req.NickName == "" { + req.NickName = req.Username + } + if err := validateBootstrapInput(req); err != nil || !validBootstrapToken(c.GetHeader(bootstrapTokenHeader)) { + handler.WriteIdentityAudit(c, "1", "安全初始化被拒绝", req.Username) + e.Custom(gin.H{"code": http.StatusForbidden, "msg": "安全初始化不可用或输入不符合要求"}) + return + } + + err := db.Transaction(func(tx *gorm.DB) error { + if err := tx.Exec("LOCK TABLE sys_user IN EXCLUSIVE MODE").Error; err != nil { + return err + } + var count int64 + if err := tx.Model(&models.SysUser{}).Count(&count).Error; err != nil { + return err + } + if count != 0 { + return errors.New("identity already initialized") + } + var role models.SysRole + if err := tx.Where("role_key = ? AND status = ?", "admin", "2").First(&role).Error; err != nil { + return err + } + return tx.Create(&models.SysUser{ + Username: req.Username, + Password: req.Password, + NickName: req.NickName, + RoleId: role.RoleId, + Status: "2", + }).Error + }) + if err != nil { + handler.WriteIdentityAudit(c, "1", "安全初始化被拒绝", req.Username) + e.Custom(gin.H{"code": http.StatusForbidden, "msg": "安全初始化不可用或输入不符合要求"}) + return + } + handler.WriteIdentityAudit(c, "2", "首个管理员创建成功", req.Username) + e.Custom(gin.H{"code": http.StatusOK, "msg": "管理员创建成功"}) +} + +func validateBootstrapInput(req bootstrapRequest) error { + if len(req.Username) < 3 || len(req.Username) > 64 || strings.ContainsAny(req.Username, " \t\r\n") { + return errors.New("invalid username") + } + return service.ValidatePassword(req.Password) +} + +func validBootstrapToken(candidate string) bool { + expected := os.Getenv("SENSE_BOOTSTRAP_TOKEN") + if len(expected) < 32 || len(candidate) != len(expected) { + return false + } + return subtle.ConstantTimeCompare([]byte(candidate), []byte(expected)) == 1 +} diff --git a/Sense/server/app/admin/apis/identity_bootstrap_test.go b/Sense/server/app/admin/apis/identity_bootstrap_test.go new file mode 100644 index 0000000..f876e1f --- /dev/null +++ b/Sense/server/app/admin/apis/identity_bootstrap_test.go @@ -0,0 +1,33 @@ +package apis + +import "testing" + +func TestValidateBootstrapInput(t *testing.T) { + if err := validateBootstrapInput(bootstrapRequest{Username: "admin", Password: "abcdef"}); err != nil { + t.Fatalf("expected valid bootstrap input: %v", err) + } + for _, req := range []bootstrapRequest{ + {Username: "ab", Password: "abcdef"}, + {Username: "bad name", Password: "abcdef"}, + {Username: "admin", Password: "abcde"}, + } { + if err := validateBootstrapInput(req); err == nil { + t.Fatalf("expected invalid bootstrap input: %+v", req) + } + } +} + +func TestValidBootstrapToken(t *testing.T) { + const token = "0123456789abcdef0123456789abcdef" + t.Setenv("SENSE_BOOTSTRAP_TOKEN", token) + if !validBootstrapToken(token) { + t.Fatal("expected configured token to be accepted") + } + if validBootstrapToken("wrong") { + t.Fatal("expected wrong token to be rejected") + } + t.Setenv("SENSE_BOOTSTRAP_TOKEN", "short") + if validBootstrapToken("short") { + t.Fatal("expected short configured token to be rejected") + } +} diff --git a/Sense/server/app/admin/apis/sys_user.go b/Sense/server/app/admin/apis/sys_user.go index 1165a9b..c0abb2a 100644 --- a/Sense/server/app/admin/apis/sys_user.go +++ b/Sense/server/app/admin/apis/sys_user.go @@ -1,9 +1,8 @@ package apis import ( - "github.com/gin-gonic/gin/binding" "git.ilapage.cn/ila/yovision/Sense/server/app/admin/models" - "golang.org/x/crypto/bcrypt" + "github.com/gin-gonic/gin/binding" "net/http" "github.com/gin-gonic/gin" @@ -15,6 +14,7 @@ import ( "git.ilapage.cn/ila/yovision/Sense/server/app/admin/service" "git.ilapage.cn/ila/yovision/Sense/server/app/admin/service/dto" "git.ilapage.cn/ila/yovision/Sense/server/common/actions" + "git.ilapage.cn/ila/yovision/Sense/server/common/middleware/handler" ) type SysUser struct { @@ -113,6 +113,10 @@ func (e SysUser) Insert(c *gin.Context) { e.Error(500, err, err.Error()) return } + if err = service.ValidatePassword(req.Password); err != nil { + e.Error(http.StatusBadRequest, err, err.Error()) + return + } // 设置创建人 req.SetCreateBy(user.GetUserId(c)) err = s.Insert(&req) @@ -306,6 +310,11 @@ func (e SysUser) ResetPwd(c *gin.Context) { e.Error(500, err, err.Error()) return } + if err = service.ValidatePassword(req.Password); err != nil { + handler.WriteIdentityAudit(c, "1", "管理员重置密码失败", user.GetUserName(c)) + e.Error(http.StatusBadRequest, err, err.Error()) + return + } req.SetUpdateBy(user.GetUserId(c)) @@ -314,9 +323,11 @@ func (e SysUser) ResetPwd(c *gin.Context) { err = s.ResetPwd(&req, p) if err != nil { + handler.WriteIdentityAudit(c, "1", "管理员重置密码失败", user.GetUserName(c)) e.Logger.Error(err) return } + handler.WriteIdentityAudit(c, "2", "管理员重置密码成功", user.GetUserName(c)) e.OK(req.GetId(), "更新成功") } @@ -346,18 +357,21 @@ func (e SysUser) UpdatePwd(c *gin.Context) { // 数据权限检查 p := actions.GetPermissionFromContext(c) - var hash []byte - if hash, err = bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost); err != nil { - req.NewPassword = string(hash) + if err = service.ValidatePassword(req.NewPassword); err != nil { + handler.WriteIdentityAudit(c, "1", "修改密码失败", user.GetUserName(c)) + e.Error(http.StatusBadRequest, err, err.Error()) + return } err = s.UpdatePwd(user.GetUserId(c), req.OldPassword, req.NewPassword, p) if err != nil { + handler.WriteIdentityAudit(c, "1", "修改密码失败", user.GetUserName(c)) e.Logger.Error(err) e.Error(http.StatusForbidden, err, "密码修改失败") return } + handler.WriteIdentityAudit(c, "2", "修改密码成功", user.GetUserName(c)) e.OK(nil, "密码修改成功") } diff --git a/Sense/server/app/admin/router/init_router.go b/Sense/server/app/admin/router/init_router.go index 681be2e..f874d9d 100644 --- a/Sense/server/app/admin/router/init_router.go +++ b/Sense/server/app/admin/router/init_router.go @@ -3,10 +3,10 @@ package router import ( "os" + common "git.ilapage.cn/ila/yovision/Sense/server/common/middleware" "github.com/gin-gonic/gin" log "github.com/go-admin-team/go-admin-core/logger" "github.com/go-admin-team/go-admin-core/sdk" - common "git.ilapage.cn/ila/yovision/Sense/server/common/middleware" ) // InitRouter 路由初始化,不要怀疑,这里用到了 diff --git a/Sense/server/app/admin/router/sys_api.go b/Sense/server/app/admin/router/sys_api.go index dba06ea..0eb2230 100644 --- a/Sense/server/app/admin/router/sys_api.go +++ b/Sense/server/app/admin/router/sys_api.go @@ -9,7 +9,6 @@ import ( ) func init() { - routerCheckRole = append(routerCheckRole, registerSysApiRouter) } // registerSysApiRouter diff --git a/Sense/server/app/admin/router/sys_config.go b/Sense/server/app/admin/router/sys_config.go index 0600305..171c944 100644 --- a/Sense/server/app/admin/router/sys_config.go +++ b/Sense/server/app/admin/router/sys_config.go @@ -9,7 +9,6 @@ import ( ) func init() { - routerCheckRole = append(routerCheckRole, registerSysConfigRouter) } // 需认证的路由代码 diff --git a/Sense/server/app/admin/router/sys_dept.go b/Sense/server/app/admin/router/sys_dept.go index 929ae52..116d52d 100644 --- a/Sense/server/app/admin/router/sys_dept.go +++ b/Sense/server/app/admin/router/sys_dept.go @@ -1,10 +1,10 @@ package router import ( - "github.com/gin-gonic/gin" - jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" "git.ilapage.cn/ila/yovision/Sense/server/app/admin/apis" "git.ilapage.cn/ila/yovision/Sense/server/common/middleware" + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" ) func init() { @@ -24,9 +24,9 @@ func registerSysDeptRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddle r.DELETE("", api.Delete) } - r1 := v1.Group("").Use(authMiddleware.MiddlewareFunc()) + r1 := v1.Group("").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) { r1.GET("/deptTree", api.Get2Tree) } -} \ No newline at end of file +} diff --git a/Sense/server/app/admin/router/sys_dict.go b/Sense/server/app/admin/router/sys_dict.go index 89b0d64..f818cb9 100644 --- a/Sense/server/app/admin/router/sys_dict.go +++ b/Sense/server/app/admin/router/sys_dict.go @@ -1,10 +1,10 @@ package router import ( - "github.com/gin-gonic/gin" - jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" "git.ilapage.cn/ila/yovision/Sense/server/app/admin/apis" "git.ilapage.cn/ila/yovision/Sense/server/common/middleware" + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" ) func init() { diff --git a/Sense/server/app/admin/router/sys_login_log.go b/Sense/server/app/admin/router/sys_login_log.go index 4c3487c..3bf58fd 100644 --- a/Sense/server/app/admin/router/sys_login_log.go +++ b/Sense/server/app/admin/router/sys_login_log.go @@ -1,10 +1,10 @@ package router import ( - "github.com/gin-gonic/gin" - jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" "git.ilapage.cn/ila/yovision/Sense/server/app/admin/apis" "git.ilapage.cn/ila/yovision/Sense/server/common/middleware" + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" ) func init() { @@ -21,4 +21,4 @@ func registerSysLoginLogRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMi r.GET("/:id", api.Get) r.DELETE("", api.Delete) } -} \ No newline at end of file +} diff --git a/Sense/server/app/admin/router/sys_menu.go b/Sense/server/app/admin/router/sys_menu.go index b9d3dda..3975a44 100644 --- a/Sense/server/app/admin/router/sys_menu.go +++ b/Sense/server/app/admin/router/sys_menu.go @@ -1,10 +1,10 @@ package router import ( - "github.com/gin-gonic/gin" - jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" "git.ilapage.cn/ila/yovision/Sense/server/app/admin/apis" "git.ilapage.cn/ila/yovision/Sense/server/common/middleware" + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" ) func init() { @@ -30,4 +30,4 @@ func registerSysMenuRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddle //r1.GET("/menuids", api.GetMenuIDS) } -} \ No newline at end of file +} diff --git a/Sense/server/app/admin/router/sys_opera_log.go b/Sense/server/app/admin/router/sys_opera_log.go index 29d391e..47aec8e 100644 --- a/Sense/server/app/admin/router/sys_opera_log.go +++ b/Sense/server/app/admin/router/sys_opera_log.go @@ -1,10 +1,10 @@ package router import ( - "github.com/gin-gonic/gin" - jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" "git.ilapage.cn/ila/yovision/Sense/server/app/admin/apis" "git.ilapage.cn/ila/yovision/Sense/server/common/middleware" + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" ) func init() { @@ -20,4 +20,4 @@ func registerSysOperaLogRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMi r.GET("/:id", api.Get) r.DELETE("", api.Delete) } -} \ No newline at end of file +} diff --git a/Sense/server/app/admin/router/sys_post.go b/Sense/server/app/admin/router/sys_post.go index 8cb2579..f0ad3cc 100644 --- a/Sense/server/app/admin/router/sys_post.go +++ b/Sense/server/app/admin/router/sys_post.go @@ -1,10 +1,10 @@ package router import ( - "github.com/gin-gonic/gin" - jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" "git.ilapage.cn/ila/yovision/Sense/server/app/admin/apis" "git.ilapage.cn/ila/yovision/Sense/server/common/middleware" + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" ) func init() { @@ -22,4 +22,4 @@ func registerSyPostRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddlew r.PUT("/:id", api.Update) r.DELETE("", api.Delete) } -} \ No newline at end of file +} diff --git a/Sense/server/app/admin/router/sys_role.go b/Sense/server/app/admin/router/sys_role.go index 126737f..85beede 100644 --- a/Sense/server/app/admin/router/sys_role.go +++ b/Sense/server/app/admin/router/sys_role.go @@ -23,7 +23,7 @@ func registerSysRoleRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddle r.PUT("/:id", api.Update) r.DELETE("", api.Delete) } - r1 := v1.Group("").Use(authMiddleware.MiddlewareFunc()) + r1 := v1.Group("").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) { r1.PUT("/role-status", api.Update2Status) r1.PUT("/roledatascope", api.Update2DataScope) diff --git a/Sense/server/app/admin/router/sys_router.go b/Sense/server/app/admin/router/sys_router.go index d561b61..ffab703 100644 --- a/Sense/server/app/admin/router/sys_router.go +++ b/Sense/server/app/admin/router/sys_router.go @@ -68,6 +68,8 @@ func sysCheckRoleRouterInit(r *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddle v1 := r.Group("/api/v1") { + v1.GET("/captcha", apis.System{}.GenerateCaptchaHandler) + v1.POST("/bootstrap", apis.System{}.Bootstrap) v1.POST("/login", authMiddleware.LoginHandler) // Refresh time can be longer than token timeout v1.GET("/refresh_token", authMiddleware.RefreshHandler) @@ -81,8 +83,7 @@ func registerBaseRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddlewar v1auth := v1.Group("").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) { v1auth.GET("/roleMenuTreeselect/:roleId", api.GetMenuTreeSelect) - //v1.GET("/menuTreeselect", api.GetMenuTreeSelect) v1auth.GET("/roleDeptTreeselect/:roleId", api2.GetDeptTreeRoleSelect) - v1auth.POST("/logout", handler.LogOut) } + v1.Group("").Use(authMiddleware.MiddlewareFunc()).POST("/logout", handler.LogOut) } diff --git a/Sense/server/app/admin/router/sys_user.go b/Sense/server/app/admin/router/sys_user.go index 5f75321..76e8fe2 100644 --- a/Sense/server/app/admin/router/sys_user.go +++ b/Sense/server/app/admin/router/sys_user.go @@ -1,11 +1,11 @@ package router import ( - "github.com/gin-gonic/gin" - jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" "git.ilapage.cn/ila/yovision/Sense/server/app/admin/apis" "git.ilapage.cn/ila/yovision/Sense/server/common/actions" "git.ilapage.cn/ila/yovision/Sense/server/common/middleware" + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" ) func init() { @@ -24,16 +24,19 @@ func registerSysUserRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddle r.DELETE("", api.Delete) } - user := v1.Group("/user").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()).Use(actions.PermissionAction()) + user := v1.Group("/user").Use(authMiddleware.MiddlewareFunc()).Use(actions.PermissionAction()) { user.GET("/profile", api.GetProfile) user.POST("/avatar", api.InsetAvatar) user.PUT("/pwd/set", api.UpdatePwd) - user.PUT("/pwd/reset", api.ResetPwd) - user.PUT("/status", api.UpdateStatus) + } + userAdmin := v1.Group("/user").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()).Use(actions.PermissionAction()) + { + userAdmin.PUT("/pwd/reset", api.ResetPwd) + userAdmin.PUT("/status", api.UpdateStatus) } v1auth := v1.Group("").Use(authMiddleware.MiddlewareFunc()) { v1auth.GET("/getinfo", api.GetInfo) } -} \ No newline at end of file +} diff --git a/Sense/server/app/admin/service/password_policy.go b/Sense/server/app/admin/service/password_policy.go new file mode 100644 index 0000000..035a712 --- /dev/null +++ b/Sense/server/app/admin/service/password_policy.go @@ -0,0 +1,23 @@ +package service + +import ( + "errors" + "unicode/utf8" +) + +const ( + MinPasswordLength = 6 + MaxPasswordBytes = 72 +) + +// ValidatePassword enforces the frozen Sense policy. Lowercase-only passwords +// are valid; complexity rules are intentionally not added here. +func ValidatePassword(password string) error { + if utf8.RuneCountInString(password) < MinPasswordLength { + return errors.New("密码至少需要 6 个字符") + } + if len(password) > MaxPasswordBytes { + return errors.New("密码不能超过 72 个字节") + } + return nil +} diff --git a/Sense/server/app/admin/service/password_policy_test.go b/Sense/server/app/admin/service/password_policy_test.go new file mode 100644 index 0000000..b81eaa2 --- /dev/null +++ b/Sense/server/app/admin/service/password_policy_test.go @@ -0,0 +1,26 @@ +package service + +import ( + "strings" + "testing" +) + +func TestValidatePassword(t *testing.T) { + tests := []struct { + name string + value string + wantErr bool + }{ + {name: "lowercase accepted", value: "chengma", wantErr: false}, + {name: "six characters accepted", value: "abcdef", wantErr: false}, + {name: "too short", value: "abcde", wantErr: true}, + {name: "bcrypt byte limit", value: strings.Repeat("a", MaxPasswordBytes+1), wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ValidatePassword(tt.value); (got != nil) != tt.wantErr { + t.Fatalf("ValidatePassword() error = %v, wantErr %v", got, tt.wantErr) + } + }) + } +} diff --git a/Sense/server/app/admin/service/sys_role.go b/Sense/server/app/admin/service/sys_role.go index bd63819..5b9c4c3 100644 --- a/Sense/server/app/admin/service/sys_role.go +++ b/Sense/server/app/admin/service/sys_role.go @@ -103,12 +103,13 @@ func (e *SysRole) Insert(c *dto.SysRoleInsertReq, cb *casbin.SyncedEnforcer) err return err } - mp := make(map[string]interface{}, 0) + mp := make(map[string]struct{}) polices := make([][]string, 0) for _, menu := range dataMenu { for _, api := range menu.SysApi { - if mp[data.RoleKey+"-"+api.Path+"-"+api.Action] != "" { - mp[data.RoleKey+"-"+api.Path+"-"+api.Action] = "" + key := data.RoleKey + "-" + api.Path + "-" + api.Action + if _, exists := mp[key]; !exists { + mp[key] = struct{}{} polices = append(polices, []string{data.RoleKey, api.Path, api.Action}) } } @@ -169,12 +170,13 @@ func (e *SysRole) Update(c *dto.SysRoleUpdateReq, cb *casbin.SyncedEnforcer) err e.Log.Errorf("delete policy error:%s", err) return err } - mp := make(map[string]interface{}, 0) + mp := make(map[string]struct{}) polices := make([][]string, 0) for _, menu := range mlist { for _, api := range menu.SysApi { - if mp[model.RoleKey+"-"+api.Path+"-"+api.Action] != "" { - mp[model.RoleKey+"-"+api.Path+"-"+api.Action] = "" + key := model.RoleKey + "-" + api.Path + "-" + api.Action + if _, exists := mp[key]; !exists { + mp[key] = struct{}{} //_, err = cb.AddNamedPolicy("p", model.RoleKey, api.Path, api.Action) polices = append(polices, []string{model.RoleKey, api.Path, api.Action}) } diff --git a/Sense/server/app/admin/service/sys_user.go b/Sense/server/app/admin/service/sys_user.go index f90b1ad..7a16bfc 100644 --- a/Sense/server/app/admin/service/sys_user.go +++ b/Sense/server/app/admin/service/sys_user.go @@ -161,6 +161,9 @@ func (e *SysUser) UpdateStatus(c *dto.UpdateSysUserStatusReq, p *actions.DataPer // ResetPwd 重置用户密码 func (e *SysUser) ResetPwd(c *dto.ResetSysUserPwdReq, p *actions.DataPermission) error { var err error + if err = ValidatePassword(c.Password); err != nil { + return err + } var model models.SysUser db := e.Orm.Scopes( actions.Permission(model.TableName(), p), @@ -204,8 +207,8 @@ func (e *SysUser) Remove(c *dto.SysUserById, p *actions.DataPermission) error { func (e *SysUser) UpdatePwd(id int, oldPassword, newPassword string, p *actions.DataPermission) error { var err error - if newPassword == "" { - return nil + if err = ValidatePassword(newPassword); err != nil { + return err } c := &models.SysUser{} diff --git a/Sense/server/cmd/migrate/migration/version/2026081412000_identity.go b/Sense/server/cmd/migrate/migration/version/2026081412000_identity.go new file mode 100644 index 0000000..294cd4c --- /dev/null +++ b/Sense/server/cmd/migrate/migration/version/2026081412000_identity.go @@ -0,0 +1,124 @@ +package version + +import ( + "runtime" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + + "git.ilapage.cn/ila/yovision/Sense/server/cmd/migrate/migration" + "git.ilapage.cn/ila/yovision/Sense/server/cmd/migrate/migration/models" + common "git.ilapage.cn/ila/yovision/Sense/server/common/models" +) + +type identityRoleDefinition struct { + name string + key string + sort int + menuIDs []int + policies [][2]string +} + +type identityCasbinRule struct { + ID uint `gorm:"primaryKey;autoIncrement"` + Ptype string `gorm:"size:512;uniqueIndex:unique_index"` + V0 string `gorm:"size:512;uniqueIndex:unique_index"` + V1 string `gorm:"size:512;uniqueIndex:unique_index"` + V2 string `gorm:"size:512;uniqueIndex:unique_index"` + V3 string `gorm:"size:512;uniqueIndex:unique_index"` + V4 string `gorm:"size:512;uniqueIndex:unique_index"` + V5 string `gorm:"size:512;uniqueIndex:unique_index"` +} + +func (identityCasbinRule) TableName() string { return "casbin_rule" } + +func init() { + _, fileName, _, _ := runtime.Caller(0) + migration.Migrate.SetVersion(migration.GetFilename(fileName), migrateSenseIdentityBaseline) +} + +func migrateSenseIdentityBaseline(db *gorm.DB, version string) error { + return db.Transaction(func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&identityCasbinRule{}); err != nil { + return err + } + resetPasswordMenu, err := ensureIdentityButton(tx, "SenseResetPassword", "重置用户密码", "admin:sysUser:resetPassword", 154) + if err != nil { + return err + } + changeStatusMenu, err := ensureIdentityButton(tx, "SenseChangeUserStatus", "启停用户", "admin:sysUser:edit", 155) + if err != nil { + return err + } + + definitions := []identityRoleDefinition{ + {name: "系统管理员", key: "admin", sort: 1}, + { + name: "实施运维", key: "implementation_operator", sort: 10, + menuIDs: []int{2, 211, 212, 216, 248, 250}, + policies: [][2]string{{"/api/v1/sys-login-log", "GET"}, {"/api/v1/sys-login-log/:id", "GET"}, {"/api/v1/sys-opera-log", "GET"}, {"/api/v1/sys-opera-log/:id", "GET"}, {"/api/v1/dict-data/option-select", "GET"}}, + }, + { + name: "站点管理员", key: "site_admin", sort: 20, + menuIDs: []int{2, 3, 43, 44, 45, resetPasswordMenu.MenuId, changeStatusMenu.MenuId}, + policies: [][2]string{{"/api/v1/sys-user", "GET"}, {"/api/v1/sys-user/:id", "GET"}, {"/api/v1/sys-user", "POST"}, {"/api/v1/sys-user", "PUT"}, {"/api/v1/user/pwd/reset", "PUT"}, {"/api/v1/user/status", "PUT"}, {"/api/v1/deptTree", "GET"}, {"/api/v1/post", "GET"}, {"/api/v1/role", "GET"}, {"/api/v1/dict-data/option-select", "GET"}}, + }, + {name: "只读用户", key: "viewer", sort: 30}, + } + + for _, definition := range definitions { + role, err := ensureIdentityRole(tx, definition) + if err != nil { + return err + } + if err = attachIdentityMenus(tx, &role, definition.menuIDs); err != nil { + return err + } + for _, policy := range definition.policies { + rule := identityCasbinRule{Ptype: "p", V0: definition.key, V1: policy[0], V2: policy[1]} + if err = tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&rule).Error; err != nil { + return err + } + } + } + + return tx.Create(&common.Migration{Version: version}).Error + }) +} + +func ensureIdentityRole(tx *gorm.DB, definition identityRoleDefinition) (models.SysRole, error) { + var role models.SysRole + err := tx.Where("role_key = ?", definition.key).First(&role).Error + if err != nil && err != gorm.ErrRecordNotFound { + return role, err + } + if err == gorm.ErrRecordNotFound { + role = models.SysRole{RoleName: definition.name, RoleKey: definition.key, RoleSort: definition.sort, Status: "2", Admin: definition.key == "admin", DataScope: "1", Remark: "Sense 内置最小权限角色"} + return role, tx.Create(&role).Error + } + err = tx.Model(&role).Updates(map[string]interface{}{"role_name": definition.name, "status": "2", "role_sort": definition.sort, "admin": definition.key == "admin"}).Error + return role, err +} + +func ensureIdentityButton(tx *gorm.DB, name, title, permission string, apiID int) (models.SysMenu, error) { + menu := models.SysMenu{MenuName: name, Title: title, MenuType: "F", Action: "PUT", Permission: permission, ParentId: 3, Paths: "/0/2/3", Sort: 25, Visible: "1", IsFrame: "1"} + if err := tx.Where("menu_name = ?", name).FirstOrCreate(&menu).Error; err != nil { + return menu, err + } + var api models.SysApi + if err := tx.First(&api, apiID).Error; err != nil { + return menu, err + } + return menu, tx.Model(&menu).Association("SysApi").Append(&api) +} + +func attachIdentityMenus(tx *gorm.DB, role *models.SysRole, menuIDs []int) error { + if len(menuIDs) == 0 { + return nil + } + var menus []models.SysMenu + if err := tx.Where("menu_id IN ?", menuIDs).Find(&menus).Error; err != nil { + return err + } + return tx.Model(role).Association("SysMenu").Append(&menus) +} diff --git a/Sense/server/common/middleware/auth.go b/Sense/server/common/middleware/auth.go index 5fe198d..927743d 100644 --- a/Sense/server/common/middleware/auth.go +++ b/Sense/server/common/middleware/auth.go @@ -3,9 +3,9 @@ package middleware import ( "time" + "git.ilapage.cn/ila/yovision/Sense/server/common/middleware/handler" "github.com/go-admin-team/go-admin-core/sdk/config" jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" - "git.ilapage.cn/ila/yovision/Sense/server/common/middleware/handler" ) // AuthInit jwt验证new @@ -19,7 +19,7 @@ func AuthInit() (*jwt.GinJWTMiddleware, error) { } } return jwt.New(&jwt.GinJWTMiddleware{ - Realm: "test zone", + Realm: "Sense", Key: []byte(config.JwtConfig.Secret), Timeout: timeout, MaxRefresh: time.Hour, @@ -28,9 +28,9 @@ func AuthInit() (*jwt.GinJWTMiddleware, error) { Authenticator: handler.Authenticator, Authorizator: handler.Authorizator, Unauthorized: handler.Unauthorized, - TokenLookup: "header: Authorization, query: token, cookie: jwt", + TokenLookup: "header: Authorization, cookie: sense_session", TokenHeadName: "Bearer", TimeFunc: time.Now, }) -} \ No newline at end of file +} diff --git a/Sense/server/common/middleware/handler/auth.go b/Sense/server/common/middleware/handler/auth.go index c05297b..c5e3020 100644 --- a/Sense/server/common/middleware/handler/auth.go +++ b/Sense/server/common/middleware/handler/auth.go @@ -6,7 +6,6 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/go-admin-team/go-admin-core/sdk" "github.com/go-admin-team/go-admin-core/sdk/api" "github.com/go-admin-team/go-admin-core/sdk/config" "github.com/go-admin-team/go-admin-core/sdk/pkg" @@ -15,7 +14,6 @@ import ( "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" "github.com/go-admin-team/go-admin-core/sdk/pkg/response" "github.com/mssola/user_agent" - "git.ilapage.cn/ila/yovision/Sense/server/common/global" ) func PayloadFunc(data interface{}) jwt.MapClaims { @@ -28,7 +26,7 @@ func PayloadFunc(data interface{}) jwt.MapClaims { jwt.RoleKey: r.RoleKey, jwt.NiceKey: u.Username, jwt.DataScopeKey: r.DataScope, - jwt.RoleNameKey: r.RoleName, + jwt.RoleNameKey: r.RoleKey, } } return jwt.MapClaims{} @@ -108,36 +106,44 @@ func Authenticator(c *gin.Context) (interface{}, error) { // LoginLogToDB Write log to database func LoginLogToDB(c *gin.Context, status string, msg string, username string) { - if !config.LoggerConfig.EnabledDB { + WriteIdentityAudit(c, status, msg, username) +} + +// WriteIdentityAudit persists identity/security events synchronously. It never +// records request bodies, credentials, tokens, captcha answers, or cookies. +func WriteIdentityAudit(c *gin.Context, status string, msg string, username string) { + log := api.GetRequestLogger(c) + db, err := pkg.GetOrm(c) + if err != nil { + log.Errorf("identity audit database unavailable: %s", err.Error()) return } - log := api.GetRequestLogger(c) - l := make(map[string]interface{}) ua := user_agent.New(c.Request.UserAgent()) - l["ipaddr"] = common.GetClientIP(c) - l["loginLocation"] = "" // pkg.GetLocation(common.GetClientIP(c),gaConfig.ExtConfig.AMap.Key) - l["loginTime"] = pkg.GetCurrentTime() - l["status"] = status - l["remark"] = c.Request.UserAgent() browserName, browserVersion := ua.Browser() - l["browser"] = browserName + " " + browserVersion - l["os"] = ua.OS() - l["platform"] = ua.Platform() - l["username"] = username - l["msg"] = msg - - q := sdk.Runtime.GetMemoryQueue(c.Request.Host) - message, err := sdk.Runtime.GetStreamMessage("", global.LoginLog, l) - if err != nil { - log.Errorf("GetStreamMessage error, %s", err.Error()) - //日志报错错误,不中断请求 - } else { - err = q.Append(message) - if err != nil { - log.Errorf("Append message error, %s", err.Error()) - } + l := models.SysLoginLog{ + Username: truncateAuditText(username, 128), + Status: status, + Ipaddr: truncateAuditText(common.GetClientIP(c), 255), + LoginLocation: "", + Browser: truncateAuditText(browserName+" "+browserVersion, 255), + Os: truncateAuditText(ua.OS(), 255), + Platform: truncateAuditText(ua.Platform(), 255), + LoginTime: pkg.GetCurrentTime(), + Remark: truncateAuditText(c.Request.Method+" "+c.FullPath(), 255), + Msg: truncateAuditText(msg, 255), } + if err = db.Create(&l).Error; err != nil { + log.Errorf("identity audit write failed: %s", err.Error()) + } +} + +func truncateAuditText(value string, limit int) string { + runes := []rune(value) + if len(runes) <= limit { + return value + } + return string(runes[:limit]) } // LogOut @@ -164,7 +170,7 @@ func Authorizator(data interface{}, c *gin.Context) bool { if v, ok := data.(map[string]interface{}); ok { u, _ := v["user"].(models.SysUser) r, _ := v["role"].(models.SysRole) - c.Set("role", r.RoleName) + c.Set("role", r.RoleKey) c.Set("roleIds", r.RoleId) c.Set("userId", u.UserId) c.Set("userName", u.Username) @@ -175,6 +181,9 @@ func Authorizator(data interface{}, c *gin.Context) bool { } func Unauthorized(c *gin.Context, code int, message string) { + if c.FullPath() != "/api/v1/login" { + WriteIdentityAudit(c, "1", "未认证访问被拒绝", "") + } c.JSON(http.StatusOK, gin.H{ "code": code, "msg": message, diff --git a/Sense/server/common/middleware/handler/auth_test.go b/Sense/server/common/middleware/handler/auth_test.go new file mode 100644 index 0000000..59dbcf7 --- /dev/null +++ b/Sense/server/common/middleware/handler/auth_test.go @@ -0,0 +1,12 @@ +package handler + +import "testing" + +func TestTruncateAuditText(t *testing.T) { + if got := truncateAuditText("中文审计记录", 3); got != "中文审" { + t.Fatalf("unexpected truncation: %q", got) + } + if got := truncateAuditText("short", 10); got != "short" { + t.Fatalf("unexpected unchanged value: %q", got) + } +} diff --git a/Sense/server/common/middleware/logger.go b/Sense/server/common/middleware/logger.go index e801357..92c9195 100644 --- a/Sense/server/common/middleware/logger.go +++ b/Sense/server/common/middleware/logger.go @@ -95,11 +95,54 @@ func LoggerToFile() gin.HandlerFunc { log.Fields(map[string]interface{}{}) }() if c.Request.Method != "OPTIONS" && config.LoggerConfig.EnabledDB && statusCode != 404 { - SetDBOperLog(c, clientIP, statusCode, reqUri, reqMethod, latencyTime, body, result, statusBus) + SetDBOperLog(c, clientIP, statusCode, reqUri, reqMethod, latencyTime, sanitizeAuditJSON(body), sanitizeAuditJSON(result), statusBus) } } } +func sanitizeAuditJSON(value string) string { + if strings.TrimSpace(value) == "" { + return "" + } + var decoded interface{} + if err := json.Unmarshal([]byte(value), &decoded); err != nil { + return "[内容已隐藏]" + } + redactAuditValue(decoded) + encoded, err := json.Marshal(decoded) + if err != nil { + return "[内容已隐藏]" + } + return string(encoded) +} + +func redactAuditValue(value interface{}) { + switch current := value.(type) { + case map[string]interface{}: + for key, item := range current { + if isSensitiveAuditKey(key) { + current[key] = "[已隐藏]" + continue + } + redactAuditValue(item) + } + case []interface{}: + for _, item := range current { + redactAuditValue(item) + } + } +} + +func isSensitiveAuditKey(key string) bool { + normalized := strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(key, "_", ""), "-", "")) + switch normalized { + case "password", "oldpassword", "newpassword", "token", "refreshtoken", "secret", "code", "uuid": + return true + default: + return false + } +} + // SetDBOperLog 写入操作日志表 fixme 该方法后续即将弃用 func SetDBOperLog(c *gin.Context, clientIP string, statusCode int, reqUri string, reqMethod string, latencyTime time.Duration, body string, result string, status int) { diff --git a/Sense/server/common/middleware/logger_test.go b/Sense/server/common/middleware/logger_test.go new file mode 100644 index 0000000..3a1eb38 --- /dev/null +++ b/Sense/server/common/middleware/logger_test.go @@ -0,0 +1,25 @@ +package middleware + +import ( + "strings" + "testing" +) + +func TestSanitizeAuditJSON(t *testing.T) { + input := `{"username":"operator","password":"secret-value","nested":{"newPassword":"another-secret"},"token":"jwt-value"}` + got := sanitizeAuditJSON(input) + for _, secret := range []string{"secret-value", "another-secret", "jwt-value"} { + if strings.Contains(got, secret) { + t.Fatalf("sensitive value leaked: %s", got) + } + } + if !strings.Contains(got, "operator") { + t.Fatalf("non-sensitive context was unexpectedly removed: %s", got) + } +} + +func TestSanitizeAuditJSONRejectsUnstructuredBodies(t *testing.T) { + if got := sanitizeAuditJSON("password=secret-value"); strings.Contains(got, "secret-value") { + t.Fatalf("unstructured body leaked: %s", got) + } +} diff --git a/Sense/server/common/middleware/permission.go b/Sense/server/common/middleware/permission.go index 00d1b1f..39c0fec 100644 --- a/Sense/server/common/middleware/permission.go +++ b/Sense/server/common/middleware/permission.go @@ -4,6 +4,7 @@ import ( "github.com/casbin/casbin/v2/util" "net/http" + "git.ilapage.cn/ila/yovision/Sense/server/common/middleware/handler" "github.com/gin-gonic/gin" "github.com/go-admin-team/go-admin-core/sdk" "github.com/go-admin-team/go-admin-core/sdk/api" @@ -49,6 +50,7 @@ func AuthCheckRole() gin.HandlerFunc { c.Next() } else { log.Warnf("isTrue: %v role: %s method: %s path: %s message: %s", res, v["rolekey"], c.Request.Method, c.Request.URL.Path, "当前request无权限,请管理员确认!") + handler.WriteIdentityAudit(c, "1", "拒绝访问 "+c.Request.Method+" "+c.FullPath(), userNameFromClaims(v)) c.JSON(http.StatusOK, gin.H{ "code": 403, "msg": "对不起,您没有该接口访问权限,请联系管理员", @@ -59,3 +61,10 @@ func AuthCheckRole() gin.HandlerFunc { } } + +func userNameFromClaims(claims jwtauth.MapClaims) string { + if value, ok := claims["nice"].(string); ok { + return value + } + return "" +} diff --git a/Sense/server/common/middleware/settings.go b/Sense/server/common/middleware/settings.go index daf8d30..c94e34f 100644 --- a/Sense/server/common/middleware/settings.go +++ b/Sense/server/common/middleware/settings.go @@ -6,38 +6,4 @@ type UrlInfo struct { } // CasbinExclude casbin 排除的路由列表 -var CasbinExclude = []UrlInfo{ - {Url: "/api/v1/dict/type-option-select", Method: "GET"}, - {Url: "/api/v1/dict-data/option-select", Method: "GET"}, - {Url: "/api/v1/deptTree", Method: "GET"}, - {Url: "/api/v1/db/tables/page", Method: "GET"}, - {Url: "/api/v1/db/columns/page", Method: "GET"}, - {Url: "/api/v1/gen/toproject/:tableId", Method: "GET"}, - {Url: "/api/v1/gen/todb/:tableId", Method: "GET"}, - {Url: "/api/v1/gen/tabletree", Method: "GET"}, - {Url: "/api/v1/gen/preview/:tableId", Method: "GET"}, - {Url: "/api/v1/gen/apitofile/:tableId", Method: "GET"}, - {Url: "/api/v1/getCaptcha", Method: "GET"}, - {Url: "/api/v1/getinfo", Method: "GET"}, - {Url: "/api/v1/menuTreeselect", Method: "GET"}, - {Url: "/api/v1/menurole", Method: "GET"}, - {Url: "/api/v1/menuids", Method: "GET"}, - {Url: "/api/v1/roleMenuTreeselect/:roleId", Method: "GET"}, - {Url: "/api/v1/roleDeptTreeselect/:roleId", Method: "GET"}, - {Url: "/api/v1/refresh_token", Method: "GET"}, - {Url: "/api/v1/configKey/:configKey", Method: "GET"}, - {Url: "/api/v1/app-config", Method: "GET"}, - {Url: "/api/v1/user/profile", Method: "GET"}, - {Url: "/info", Method: "GET"}, - {Url: "/api/v1/login", Method: "POST"}, - {Url: "/api/v1/logout", Method: "POST"}, - {Url: "/api/v1/user/avatar", Method: "POST"}, - {Url: "/api/v1/user/pwd", Method: "PUT"}, - {Url: "/api/v1/metrics", Method: "GET"}, - {Url: "/api/v1/health", Method: "GET"}, - {Url: "/", Method: "GET"}, - {Url: "/api/v1/server-monitor", Method: "GET"}, - {Url: "/api/v1/public/uploadFile", Method: "POST"}, - {Url: "/api/v1/user/pwd/set", Method: "PUT"}, - {Url: "/api/v1/sys-user", Method: "PUT"}, -} +var CasbinExclude = []UrlInfo{} diff --git a/Sense/server/config/identity.env.example b/Sense/server/config/identity.env.example new file mode 100644 index 0000000..82f3dca --- /dev/null +++ b/Sense/server/config/identity.env.example @@ -0,0 +1,3 @@ +# 首次创建管理员时临时设置为至少 32 个字符的随机值。 +# 初始化成功后应从运行环境移除;不要把真实值写入本文件或仓库。 +SENSE_BOOTSTRAP_TOKEN= diff --git a/Sense/server/config/settings.yml b/Sense/server/config/settings.yml index 813309b..b429001 100644 --- a/Sense/server/config/settings.yml +++ b/Sense/server/config/settings.yml @@ -19,7 +19,7 @@ settings: stdout: '' #控制台日志,启用后,不输出到文件 # 日志等级, trace, debug, info, warn, error, fatal level: info - # 数据库日志开关 + # 通用操作数据库日志开关;登录、退出、密码和拒绝访问等身份审计始终写入数据库。 enableddb: false jwt: # 必填。生产环境至少 32 个字符;不得提交真实值。 diff --git a/Sense/ui/src/utils/auth.js b/Sense/ui/src/utils/auth.js index 08a43d6..cbea20b 100644 --- a/Sense/ui/src/utils/auth.js +++ b/Sense/ui/src/utils/auth.js @@ -1,6 +1,6 @@ import Cookies from 'js-cookie' -const TokenKey = 'Admin-Token' +const TokenKey = 'Sense-Admin-Token' export function getToken() { return Cookies.get(TokenKey) diff --git a/Sense/ui/src/views/admin/sys-user/index.vue b/Sense/ui/src/views/admin/sys-user/index.vue index c847386..67702e8 100644 --- a/Sense/ui/src/views/admin/sys-user/index.vue +++ b/Sense/ui/src/views/admin/sys-user/index.vue @@ -353,8 +353,6 @@ export default { open: false, // 部门名称 deptName: undefined, - // 默认密码 - initPassword: undefined, // 日期范围 dateRange: [], // 状态数据字典 @@ -400,7 +398,10 @@ export default { username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }], nickName: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }], deptId: [{ required: true, message: '归属部门不能为空', trigger: 'blur' }], - password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }], + password: [ + { required: true, message: '用户密码不能为空', trigger: 'blur' }, + { min: 6, max: 72, message: '密码长度为 6 至 72 个字符', trigger: 'blur' } + ], email: [ { required: true, message: '邮箱地址不能为空', trigger: 'blur' }, { type: 'email', message: "'请输入正确的邮箱地址", trigger: ['blur', 'change'] } @@ -427,9 +428,6 @@ export default { this.getDicts('sys_user_sex').then(response => { this.sexOptions = response.data }) - this.getConfigKey('sys_user_initPassword').then(response => { - this.initPassword = response.data.configValue - }) }, methods: { /** 查询用户列表 */ @@ -556,7 +554,6 @@ export default { }) this.open = true this.title = '添加用户' - this.form.password = this.initPassword }, /** 修改按钮操作 */ handleUpdate(row) { @@ -580,7 +577,9 @@ export default { handleResetPwd(row) { this.$prompt('请输入"' + row.username + '"的新密码', '提示', { confirmButtonText: '确定', - cancelButtonText: '取消' + cancelButtonText: '取消', + inputPattern: /^.{6,72}$/, + inputErrorMessage: '密码长度为 6 至 72 个字符' }).then(({ value }) => { resetUserPwd(row.userId, value).then(response => { if (response.code === 200) {