Refactor: Rename NanoKVM to BatchuKVM and update server URL

This commit is contained in:
2025-12-09 20:35:38 +09:00
commit 8cf674c9e5
396 changed files with 54380 additions and 0 deletions

View File

@@ -0,0 +1,113 @@
package auth
import (
"NanoKVM-Server/utils"
"encoding/json"
"errors"
"os"
"path/filepath"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/bcrypt"
)
const AccountFile = "/etc/kvm/pwd"
type Account struct {
Username string `json:"username"`
Password string `json:"password"` // should be named HashedPassword for clarity
}
func GetAccount() (*Account, error) {
if _, err := os.Stat(AccountFile); err != nil {
if errors.Is(err, os.ErrNotExist) {
return getDefaultAccount(), nil
}
return nil, err
}
content, err := os.ReadFile(AccountFile)
if err != nil {
return nil, err
}
var account Account
if err = json.Unmarshal(content, &account); err != nil {
log.Errorf("unmarshal account failed: %s", err)
return nil, err
}
return &account, nil
}
func SetAccount(username string, hashedPassword string) error {
account, err := json.Marshal(&Account{
Username: username,
Password: hashedPassword,
})
if err != nil {
log.Errorf("failed to marshal account information to json: %s", err)
return err
}
err = os.MkdirAll(filepath.Dir(AccountFile), 0o644)
if err != nil {
log.Errorf("create directory %s failed: %s", AccountFile, err)
return err
}
err = os.WriteFile(AccountFile, account, 0o644)
if err != nil {
log.Errorf("write password failed: %s", err)
return err
}
return nil
}
func CompareAccount(username string, plainPassword string) bool {
account, err := GetAccount()
if err != nil {
return false
}
if username != account.Username {
return false
}
hashedPassword, err := utils.DecodeDecrypt(plainPassword)
if err != nil || hashedPassword == "" {
return false
}
err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte(hashedPassword))
if err != nil {
// Compatible with old versions
accountHashedPassword, _ := utils.DecodeDecrypt(account.Password)
if accountHashedPassword == hashedPassword {
return true
}
return false
}
return true
}
func DelAccount() error {
if err := os.Remove(AccountFile); err != nil {
log.Errorf("failed to delete password: %s", err)
return err
}
return nil
}
func getDefaultAccount() *Account {
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("admin"), bcrypt.DefaultCost)
return &Account{
Username: "admin",
Password: string(hashedPassword),
}
}

View File

@@ -0,0 +1,76 @@
package auth
import (
"NanoKVM-Server/config"
"NanoKVM-Server/middleware"
"NanoKVM-Server/proto"
"time"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
func (s *Service) Login(c *gin.Context) {
var req proto.LoginReq
var rsp proto.Response
// authentication disabled
conf := config.GetInstance()
if conf.Authentication == "disable" {
rsp.OkRspWithData(c, &proto.LoginRsp{
Token: "disabled",
})
return
}
if err := proto.ParseFormRequest(c, &req); err != nil {
time.Sleep(3 * time.Second)
rsp.ErrRsp(c, -1, "invalid parameters")
return
}
if ok := CompareAccount(req.Username, req.Password); !ok {
time.Sleep(2 * time.Second)
rsp.ErrRsp(c, -2, "invalid username or password")
return
}
token, err := middleware.GenerateJWT(req.Username)
if err != nil {
time.Sleep(1 * time.Second)
rsp.ErrRsp(c, -3, "generate token failed")
return
}
rsp.OkRspWithData(c, &proto.LoginRsp{
Token: token,
})
log.Debugf("login success, username: %s", req.Username)
}
func (s *Service) Logout(c *gin.Context) {
conf := config.GetInstance()
if conf.JWT.RevokeTokensOnLogout {
config.RegenerateSecretKey()
}
var rsp proto.Response
rsp.OkRsp(c)
}
func (s *Service) GetAccount(c *gin.Context) {
var rsp proto.Response
account, err := GetAccount()
if err != nil {
rsp.ErrRsp(c, -1, "get account failed")
return
}
rsp.OkRspWithData(c, &proto.GetAccountRsp{
Username: account.Username,
})
log.Debugf("get account successful")
}

View File

@@ -0,0 +1,124 @@
package auth
import (
"NanoKVM-Server/proto"
"NanoKVM-Server/utils"
"errors"
"io"
"os"
"os/exec"
"time"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/bcrypt"
)
func (s *Service) ChangePassword(c *gin.Context) {
var req proto.ChangePasswordReq
var rsp proto.Response
if err := proto.ParseFormRequest(c, &req); err != nil {
rsp.ErrRsp(c, -1, "invalid parameters")
return
}
password, err := utils.DecodeDecrypt(req.Password)
if err != nil || password == "" {
rsp.ErrRsp(c, -2, "invalid password")
return
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
rsp.ErrRsp(c, -3, "failed to hash password")
return
}
if err = SetAccount(req.Username, string(hashedPassword)); err != nil {
rsp.ErrRsp(c, -4, "failed to save password")
return
}
// change root password
err = changeRootPassword(password)
if err != nil {
_ = DelAccount()
rsp.ErrRsp(c, -5, "failed to change password")
return
}
rsp.OkRsp(c)
log.Debugf("change password success, username: %s", req.Username)
}
func (s *Service) IsPasswordUpdated(c *gin.Context) {
var rsp proto.Response
if _, err := os.Stat(AccountFile); err != nil {
rsp.OkRspWithData(c, &proto.IsPasswordUpdatedRsp{
IsUpdated: false,
})
return
}
account, err := GetAccount()
if err != nil || account == nil {
rsp.ErrRsp(c, -1, "failed to get password")
return
}
err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte("admin"))
rsp.OkRspWithData(c, &proto.IsPasswordUpdatedRsp{
// If the hash is not valid, still assume it's not updated
// The error we want to see is password and hash not matching
IsUpdated: errors.Is(err, bcrypt.ErrMismatchedHashAndPassword),
})
}
func changeRootPassword(password string) error {
err := passwd(password)
if err != nil {
log.Errorf("failed to change root password: %s", err)
return err
}
log.Debugf("change root password successful.")
return nil
}
func passwd(password string) error {
cmd := exec.Command("passwd", "root")
stdin, err := cmd.StdinPipe()
if err != nil {
return err
}
defer func() {
_ = stdin.Close()
}()
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err = cmd.Start(); err != nil {
return err
}
if _, err = io.WriteString(stdin, password+"\n"); err != nil {
return err
}
time.Sleep(100 * time.Millisecond)
if _, err = io.WriteString(stdin, password+"\n"); err != nil {
return err
}
if err = cmd.Wait(); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,7 @@
package auth
type Service struct{}
func NewService() *Service {
return &Service{}
}