95 lines
3.0 KiB
Go
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
|
|
}
|