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 }