Files

95 lines
3.0 KiB
Go

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
}