Реализован production-hardening по fix1: env/reconfigure, IPv4-only, TLS, secrets, firewall, docs

This commit is contained in:
2026-04-26 07:27:06 +05:00
parent 2b4a45ad23
commit 3fccd5c442
109 changed files with 1773 additions and 569 deletions
-1
View File
@@ -48,4 +48,3 @@ func Execute() {
os.Exit(1)
}
}
+6 -3
View File
@@ -36,8 +36,12 @@ func runReset(cmd *cobra.Command, args []string) {
}
if err = dao.UpdateAccount([]int64{1}, map[string]interface{}{
"username": username,
"pass": util.SHA224String(password),
"con_pass": fmt.Sprintf("%s.%s", username, password)}); err != nil {
"pass": func() string {
hash, _ := util.HashPassword(password)
return hash
}(),
"force_password_change": 1,
"con_pass": fmt.Sprintf("%s.%s", username, password)}); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
@@ -49,4 +53,3 @@ func runReset(cmd *cobra.Command, args []string) {
fmt.Println(fmt.Sprintf("HY2XS admin Login Password: %s", password))
fmt.Println(fmt.Sprintf("HY2XS admin Connection Password: %s", fmt.Sprintf("%s.%s", username, password)))
}
+18 -8
View File
@@ -11,10 +11,23 @@ import (
"hy2xs-admin/router"
"hy2xs-admin/service"
"hy2xs-admin/util"
"net"
"net/http"
"os"
)
func resolveUiBindHost() (string, error) {
host := os.Getenv("HY2XS_UI_BIND_HOST")
if host == "" {
host = "127.0.0.1"
}
ip := net.ParseIP(host)
if ip == nil || ip.To4() == nil {
return "", errors.New("HY2XS_UI_BIND_HOST must be IPv4")
}
return host, nil
}
func runServer(port string) error {
defer releaseResource()
@@ -32,12 +45,6 @@ func runServer(port string) error {
if err := service.InitHysteria2(); err != nil {
return err
}
if err := service.InitTableAndChain(); err != nil {
logrus.Errorf(err.Error())
}
if err := service.InitPortHopping(); err != nil {
logrus.Errorf(err.Error())
}
config, err := dao.GetConfig("key = ?", constant.HUIWebContext)
if err != nil {
return err
@@ -50,8 +57,12 @@ func runServer(port string) error {
if err != nil {
return err
}
bindHost, err := resolveUiBindHost()
if err != nil {
return err
}
service.InitServer(fmt.Sprintf(":%d", serverPort), r)
service.InitServer(fmt.Sprintf("%s:%d", bindHost, serverPort), r)
if err := service.StartServer(crtPath, keyPath); err != nil && err != http.ErrServerClosed {
logrus.Errorf("start server err: %v", err)
return errors.New("start server err")
@@ -83,4 +94,3 @@ func initFile() error {
}
return nil
}
-1
View File
@@ -20,4 +20,3 @@ func init() {
func runVersion(cmd *cobra.Command, args []string) {
fmt.Println("HY2XS admin version", constant.Version)
}
+16 -8
View File
@@ -26,14 +26,15 @@ func Login(c *gin.Context) {
return
}
token, err := service.Login(*loginDto.Username, util.SHA224String(*loginDto.Pass))
token, forcePasswordChange, err := service.Login(*loginDto.Username, *loginDto.Pass)
if err != nil {
vo.Fail(err.Error(), c)
return
}
jwtVo := vo.JwtVo{
TokenType: constant.TokenType,
AccessToken: token,
TokenType: constant.TokenType,
AccessToken: token,
ForcePasswordChange: forcePasswordChange,
}
vo.Success(jwtVo, c)
}
@@ -100,7 +101,11 @@ func SaveAccount(c *gin.Context) {
return
}
passEncrypt := util.SHA224String(*accountSaveDto.Pass)
passEncrypt, err := util.HashPassword(*accountSaveDto.Pass)
if err != nil {
vo.Fail(err.Error(), c)
return
}
conPass := fmt.Sprintf("%s.%s", *accountSaveDto.Username, *accountSaveDto.ConPass)
account := entity.Account{
Username: accountSaveDto.Username,
@@ -167,8 +172,12 @@ func UpdateAccount(c *gin.Context) {
var passEncrypt *string
if accountUpdateDto.Pass != nil && *accountUpdateDto.Pass != "" {
passEncryptSha224 := util.SHA224String(*accountUpdateDto.Pass)
passEncrypt = &passEncryptSha224
passEncryptHash, hashErr := util.HashPassword(*accountUpdateDto.Pass)
if hashErr != nil {
vo.Fail(hashErr.Error(), c)
return
}
passEncrypt = &passEncryptHash
}
account := entity.Account{
@@ -331,6 +340,5 @@ func VerifyDefaultPass(c *gin.Context) {
vo.Fail(err.Error(), c)
return
}
vo.Success(account.Pass != nil && *account.Pass == "02f382b76ca1ab7aa06ab03345c7712fd5b971fb0c0f2aef98bac9cd", c)
vo.Success(account.Pass != nil && !util.IsBcryptHash(*account.Pass), c)
}
+16 -59
View File
@@ -17,7 +17,6 @@ import (
"io"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
@@ -35,7 +34,6 @@ func UpdateConfigs(c *gin.Context) {
return
}
needResetPortHopping := false
needRestart := false
for _, item := range configsUpdateDto.ConfigUpdateDtos {
@@ -81,19 +79,8 @@ func UpdateConfigs(c *gin.Context) {
}
if key == constant.Hysteria2ConfigPortHopping {
re := regexp.MustCompile(`^\d+(?:-\d+)?(?:,\d+(?:-\d+)?)*$`)
if value != "" && !re.MatchString(value) {
vo.Fail(fmt.Sprintf("port hopping: %s is invalid", value), c)
return
}
hysteria2ConfigPortHopping, err := service.GetConfig(constant.Hysteria2ConfigPortHopping)
if err != nil {
vo.Fail(err.Error(), c)
return
}
if *hysteria2ConfigPortHopping.Value != value {
needResetPortHopping = true
}
vo.Fail("port hopping out of scope in HY2XS production", c)
return
}
if key == constant.ResetTrafficCron {
@@ -113,13 +100,6 @@ func UpdateConfigs(c *gin.Context) {
}
}
if needResetPortHopping {
if err := service.InitPortHopping(); err != nil {
vo.Fail(err.Error(), c)
return
}
}
if needRestart {
go func() {
_ = service.StopServer()
@@ -197,33 +177,11 @@ func UpdateHysteria2Config(c *gin.Context) {
return
}
hysteria2Config, err := service.GetHysteria2Config()
if err != nil {
vo.Fail(err.Error(), c)
return
}
needResetPortHopping := false
if hysteria2Config.Listen != nil &&
*hysteria2Config.Listen != "" &&
hysteria2ServerConfig.Listen != nil &&
*hysteria2ServerConfig.Listen != "" &&
*hysteria2ServerConfig.Listen != *hysteria2Config.Listen {
needResetPortHopping = true
}
if err = service.UpdateHysteria2Config(hysteria2ServerConfig); err != nil {
vo.Fail(err.Error(), c)
return
}
if needResetPortHopping {
if err := service.InitPortHopping(); err != nil {
vo.Fail(err.Error(), c)
return
}
}
running := service.Hysteria2IsRunning()
if running {
if err = service.RestartHysteria2(); err != nil {
@@ -242,24 +200,24 @@ func ExportHysteria2Config(c *gin.Context) {
}
// Значения по умолчанию
config, err := dao.ListConfig("key in ?", []string{constant.HUIWebPort, constant.JwtSecret})
config, err := dao.ListConfig("key in ?", []string{constant.HUIWebPort, constant.Hysteria2TrafficStatsSecret})
if err != nil {
vo.Fail(err.Error(), c)
return
}
var hUIWebPort string
var jwtSecret string
var trafficStatsSecret string
for _, item := range config {
if *item.Key == constant.HUIWebPort {
hUIWebPort = *item.Value
} else if *item.Key == constant.JwtSecret {
jwtSecret = *item.Value
} else if *item.Key == constant.Hysteria2TrafficStatsSecret {
trafficStatsSecret = *item.Value
}
}
if hUIWebPort == "" || jwtSecret == "" {
logrus.Errorf("hUIWebPort or jwtSecret is nil")
if hUIWebPort == "" || trafficStatsSecret == "" {
logrus.Errorf("hUIWebPort or trafficStatsSecret is nil")
vo.Fail(constant.SysError, c)
return
}
@@ -279,7 +237,7 @@ func ExportHysteria2Config(c *gin.Context) {
http.Insecure = &authHttpInsecure
auth.HTTP = &http
hysteria2ServerConfig.Auth = &auth
hysteria2ServerConfig.TrafficStats.Secret = &jwtSecret
hysteria2ServerConfig.TrafficStats.Secret = &trafficStatsSecret
fileName := fmt.Sprintf("Hysteria2Config-%s.yaml", time.Now().Format("20060102150405"))
filePath := constant.ExportPathDir + fileName
@@ -325,24 +283,24 @@ func ImportHysteria2Config(c *gin.Context) {
}
// Значения по умолчанию
config, err := dao.ListConfig("key in ?", []string{constant.HUIWebPort, constant.JwtSecret})
config, err := dao.ListConfig("key in ?", []string{constant.HUIWebPort, constant.Hysteria2TrafficStatsSecret})
if err != nil {
vo.Fail(err.Error(), c)
return
}
var hUIWebPort string
var jwtSecret string
var trafficStatsSecret string
for _, item := range config {
if *item.Key == constant.HUIWebPort {
hUIWebPort = *item.Value
} else if *item.Key == constant.JwtSecret {
jwtSecret = *item.Value
} else if *item.Key == constant.Hysteria2TrafficStatsSecret {
trafficStatsSecret = *item.Value
}
}
if hUIWebPort == "" || jwtSecret == "" {
logrus.Errorf("hUIWebPort or jwtSecret is nil")
if hUIWebPort == "" || trafficStatsSecret == "" {
logrus.Errorf("hUIWebPort or trafficStatsSecret is nil")
vo.Fail(constant.SysError, c)
return
}
@@ -362,7 +320,7 @@ func ImportHysteria2Config(c *gin.Context) {
http.Insecure = &authHttpInsecure
auth.HTTP = &http
hysteria2ServerConfig.Auth = &auth
hysteria2ServerConfig.TrafficStats.Secret = &jwtSecret
hysteria2ServerConfig.TrafficStats.Secret = &trafficStatsSecret
if err = service.SetHysteria2Config(hysteria2ServerConfig); err != nil {
vo.Fail(err.Error(), c)
@@ -501,4 +459,3 @@ func UploadCertFile(c *gin.Context) {
}
vo.Success(certPath, c)
}
-1
View File
@@ -150,4 +150,3 @@ func Hysteria2Subscribe(c *gin.Context) {
c.String(200, configStr)
}
-1
View File
@@ -114,4 +114,3 @@ func ExportLog(c *gin.Context) {
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fileName))
c.File(filePath)
}
-1
View File
@@ -23,4 +23,3 @@ func MonitorHysteria2(c *gin.Context) {
}
vo.Success(hysteria2MonitorVo, c)
}
-1
View File
@@ -39,4 +39,3 @@ func validateField[T interface{}](c *gin.Context, field T) (T, error) {
}
return field, nil
}
-1
View File
@@ -117,4 +117,3 @@ func ListAccount(query interface{}, args ...interface{}) ([]entity.Account, erro
}
return accounts, nil
}
-1
View File
@@ -64,4 +64,3 @@ func UpsertConfig(configs []entity.Config) error {
}
return nil
}
+62 -1
View File
@@ -9,6 +9,8 @@ import (
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"hy2xs-admin/model/constant"
"hy2xs-admin/model/entity"
"hy2xs-admin/util"
"log"
"os"
"strings"
@@ -69,6 +71,66 @@ func InitSql(port string) error {
}
}
}
if err := ensureSecureBootstrapAdmin(); err != nil {
return err
}
if err := ensureTrafficStatsSecret(); err != nil {
return err
}
if tx := sqliteDB.Exec("ALTER TABLE account ADD COLUMN force_password_change INTEGER NOT NULL DEFAULT 0"); tx.Error != nil && !strings.Contains(tx.Error.Error(), "duplicate column name") {
logrus.Errorf("sqlite exec err: %v", tx.Error)
return errors.New("sqlite exec err")
}
return nil
}
func ensureSecureBootstrapAdmin() error {
admin, err := GetAccount("role = 'admin' and deleted = 0")
if err != nil {
return nil
}
if admin.Pass == nil {
return nil
}
if !util.IsBcryptHash(*admin.Pass) {
password, pwdErr := util.RandomString(18)
if pwdErr != nil {
return pwdErr
}
hash, hashErr := util.HashPassword(password)
if hashErr != nil {
return hashErr
}
conPass, conErr := util.RandomString(28)
if conErr != nil {
return conErr
}
if updateErr := UpdateAccount([]int64{*admin.Id}, map[string]interface{}{
"pass": hash,
"force_password_change": 1,
"con_pass": fmt.Sprintf("%s.%s", *admin.Username, conPass),
}); updateErr != nil {
return updateErr
}
}
return nil
}
func ensureTrafficStatsSecret() error {
if _, err := GetConfig("key = ?", constant.Hysteria2TrafficStatsSecret); err == nil {
return nil
}
secret, secErr := util.RandomString(32)
if secErr != nil {
return secErr
}
key := constant.Hysteria2TrafficStatsSecret
remark := "Hysteria2 trafficStats API secret"
if _, saveErr := SaveConfig(entity.Config{Key: &key, Value: &secret, Remark: &remark}); saveErr != nil {
return saveErr
}
return nil
}
@@ -117,4 +179,3 @@ func Paginate(pageNum *int64, pageSize *int64) func(db *gorm.DB) *gorm.DB {
return db.Offset(int((num - 1) * size)).Limit(int(size))
}
}
-1
View File
@@ -73,4 +73,3 @@ func replaceRelativePaths(htmlContent string, basePath string) string {
</script>`, basePath)
return strings.Replace(htmlContent, "</head>", injection+"</head>", 1)
}
-3
View File
@@ -16,13 +16,10 @@
"@vueuse/core": "^9.1.1",
"axios": "^1.3.4",
"copy-to-clipboard": "^3.3.3",
"echarts": "^5.2.2",
"element-plus": "^2.3.1",
"nprogress": "^0.2.0",
"path-browserify": "^1.0.1",
"path-to-regexp": "^6.2.0",
"pinia": "^2.0.33",
"screenfull": "^6.0.0",
"vue": "^3.2.45",
"vue-i18n": "9",
"vue-router": "^4.1.6",
+2 -58
View File
@@ -20,9 +20,6 @@ importers:
copy-to-clipboard:
specifier: ^3.3.3
version: 3.3.3
echarts:
specifier: ^5.2.2
version: 5.2.2
element-plus:
specifier: ^2.3.1
version: 2.3.1(vue@3.2.45)
@@ -32,15 +29,9 @@ importers:
path-browserify:
specifier: ^1.0.1
version: 1.0.1
path-to-regexp:
specifier: ^6.2.0
version: 6.2.0
pinia:
specifier: ^2.0.33
version: 2.0.33(typescript@4.9.3)(vue@3.2.45)
screenfull:
specifier: ^6.0.0
version: 6.0.0
vue:
specifier: ^3.2.45
version: 3.2.45
@@ -89,7 +80,7 @@ importers:
version: 9.9.0(eslint@8.34.0)
fast-glob:
specifier: ^3.2.11
version: 3.2.11
version: 3.3.1
postcss:
specifier: ^8.4.21
version: 8.4.21
@@ -559,28 +550,24 @@ packages:
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@swc/core-linux-arm64-musl@1.11.24':
resolution: {integrity: sha512-ypXLIdszRo0re7PNNaXN0+2lD454G8l9LPK/rbfRXnhLWDBPURxzKlLlU/YGd2zP98wPcVooMmegRSNOKfvErw==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@swc/core-linux-x64-gnu@1.11.24':
resolution: {integrity: sha512-IM7d+STVZD48zxcgo69L0yYptfhaaE9cMZ+9OoMxirNafhKKXwoZuufol1+alEFKc+Wbwp+aUPe/DeWC/Lh3dg==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@swc/core-linux-x64-musl@1.11.24':
resolution: {integrity: sha512-DZByJaMVzSfjQKKQn3cqSeqwy6lpMaQDQQ4HPlch9FWtDx/dLcpdIhxssqZXcR2rhaQVIaRQsCqwV6orSDGAGw==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@swc/core-win32-arm64-msvc@1.11.24':
resolution: {integrity: sha512-Q64Ytn23y9aVDKN5iryFi8mRgyHw3/kyjTjT4qFCa8AEb5sGUuSj//AUZ6c0J7hQKMHlg9do5Etvoe61V98/JQ==}
@@ -1273,9 +1260,6 @@ packages:
duplexer@0.1.2:
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
echarts@5.2.2:
resolution: {integrity: sha512-yxuBfeIH5c+0FsoRP60w4De6omXhA06c7eUYBsC1ykB6Ys2yK5fSteIYWvkJ4xJVLQgCvAdO8C4mN6MLeJpBaw==}
electron-to-chromium@1.4.529:
resolution: {integrity: sha512-6uyPyXTo8lkv8SWAmjKFbG42U073TXlzD4R8rW3EzuznhFS2olCIAfjjQtV2dV2ar/vRF55KUd3zQYnCB0dd3A==}
@@ -1446,10 +1430,6 @@ packages:
fast-diff@1.3.0:
resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
fast-glob@3.2.11:
resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==}
engines: {node: '>=8.6.0'}
fast-glob@3.3.1:
resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
engines: {node: '>=8.6.0'}
@@ -2221,9 +2201,6 @@ packages:
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
path-to-regexp@6.2.0:
resolution: {integrity: sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg==}
path-type@4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
@@ -2466,10 +2443,6 @@ packages:
engines: {node: '>=12.0.0'}
hasBin: true
screenfull@6.0.0:
resolution: {integrity: sha512-LGY0nhNQkC4FX4DT4pZdJ5cZH5EOz9Gfh9KcVMl779pS677k4IV1Wv7sY/CwC9VKFT21fYgCh7zkTVVefi5XKA==}
engines: {node: ^14.13.1 || >=16.0.0}
scule@1.0.0:
resolution: {integrity: sha512-4AsO/FrViE/iDNEPaAQlb77tf0csuq27EsVpy6ett584EcRTp6pTDLoGWVxCD77y5iU5FauOvhsI4o1APwPoSQ==}
@@ -2796,9 +2769,6 @@ packages:
tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
tslib@2.3.0:
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
tsutils@3.21.0:
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
engines: {node: '>= 6'}
@@ -3055,9 +3025,6 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
zrender@5.2.1:
resolution: {integrity: sha512-M3bPGZuyLTNBC6LiNKXJwSCtglMp8XUEqEBG+2MdICDI3d1s500Y4P0CzldQGsqpRVB7fkvf3BKQQRxsEaTlsw==}
snapshots:
'@aashutoshrathi/word-wrap@1.2.6': {}
@@ -4313,11 +4280,6 @@ snapshots:
duplexer@0.1.2: {}
echarts@5.2.2:
dependencies:
tslib: 2.3.0
zrender: 5.2.1
electron-to-chromium@1.4.529: {}
element-plus@2.3.1(vue@3.2.45):
@@ -4566,14 +4528,6 @@ snapshots:
fast-diff@1.3.0: {}
fast-glob@3.2.11:
dependencies:
'@nodelib/fs.stat': 2.0.5
'@nodelib/fs.walk': 1.2.8
glob-parent: 5.1.2
merge2: 1.4.1
micromatch: 4.0.5
fast-glob@3.3.1:
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -4715,7 +4669,7 @@ snapshots:
dependencies:
array-union: 2.1.0
dir-glob: 3.0.1
fast-glob: 3.2.11
fast-glob: 3.3.1
ignore: 5.2.4
merge2: 1.4.1
slash: 3.0.0
@@ -5312,8 +5266,6 @@ snapshots:
path-parse@1.0.7: {}
path-to-regexp@6.2.0: {}
path-type@4.0.0: {}
pathe@0.2.0: {}
@@ -5542,8 +5494,6 @@ snapshots:
immutable: 4.3.4
source-map-js: 1.0.2
screenfull@6.0.0: {}
scule@1.0.0: {}
semver@5.7.2: {}
@@ -5922,8 +5872,6 @@ snapshots:
tslib@1.14.1: {}
tslib@2.3.0: {}
tsutils@3.21.0(typescript@4.9.3):
dependencies:
tslib: 1.14.1
@@ -6226,7 +6174,3 @@ snapshots:
yargs-parser@20.2.9: {}
yocto-queue@0.1.0: {}
zrender@5.2.1:
dependencies:
tslib: 2.3.0
+1 -3
View File
@@ -8,12 +8,12 @@ require (
github.com/glebarez/sqlite v1.11.0
github.com/go-playground/validator/v10 v10.14.0
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/go-github/v39 v39.2.0
github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/sirupsen/logrus v1.9.3
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/spf13/cobra v1.8.1
golang.org/x/crypto v0.18.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
gorm.io/gorm v1.25.9
@@ -32,7 +32,6 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
@@ -55,7 +54,6 @@ require (
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
-22
View File
@@ -38,18 +38,10 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github/v39 v39.2.0 h1:rNNM311XtPOz5rDdsJXAp2o8F67X9FnROXTvto3aSnQ=
github.com/google/go-github/v39 v39.2.0/go.mod h1:C1s8C5aCC9L+JXIYpJM5GYytdX52vC1bLvHEF1IhBrE=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
@@ -129,35 +121,21 @@ github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-1
View File
@@ -5,4 +5,3 @@ import "hy2xs-admin/cmd"
func main() {
cmd.Execute()
}
-1
View File
@@ -24,4 +24,3 @@ func AdminHandler() gin.HandlerFunc {
c.Next()
}
}
-1
View File
@@ -31,4 +31,3 @@ func InitCron() error {
c.Start()
return nil
}
-1
View File
@@ -23,4 +23,3 @@ func FilterHandler() gin.HandlerFunc {
c.Next()
}
}
-1
View File
@@ -36,4 +36,3 @@ func JWTHandler() gin.HandlerFunc {
c.Next()
}
}
-1
View File
@@ -41,4 +41,3 @@ func LogHandler() gin.HandlerFunc {
c.Next()
}
}
-1
View File
@@ -24,4 +24,3 @@ func RateLimiterHandler() gin.HandlerFunc {
c.Next()
}
}
-1
View File
@@ -28,4 +28,3 @@ type AccountExport struct {
ConAt int64 `json:"conAt"`
Remark string `json:"remark"`
}
-1
View File
@@ -203,4 +203,3 @@ type serverConfigMasquerade struct {
ListenHTTPS *string `yaml:"listenHTTPS,omitempty" json:"listenHTTPS" validate:"omitempty"`
ForceHTTPS *bool `yaml:"forceHTTPS,omitempty" json:"forceHTTPS" validate:"omitempty"`
}
-1
View File
@@ -4,4 +4,3 @@ type Hysteria2UserTraffic struct {
Tx int64 `json:"tx"` // upload
Rx int64 `json:"rx"` // download
}
-1
View File
@@ -25,4 +25,3 @@ type ClashConfig struct {
Proxies []interface{} `yaml:"proxies"`
ProxyGroups []ProxyGroup `yaml:"proxy-groups"`
}
-1
View File
@@ -6,4 +6,3 @@ const (
V2rayN = "v2rayn"
NekoBox = "nekobox"
)
-1
View File
@@ -7,4 +7,3 @@ const (
CodeForbiddenError int = 50403
CodeInvalidError int = 50001
)
+13 -13
View File
@@ -1,17 +1,17 @@
package constant
const (
HUIWebPort = "H_UI_WEB_PORT"
HUIWebContext = "H_UI_WEB_CONTEXT"
HUICrtPath = "H_UI_CRT_PATH"
HUIKeyPath = "H_UI_KEY_PATH"
JwtSecret = "JWT_SECRET"
Hysteria2Enable = "HYSTERIA2_ENABLE"
Hysteria2Config = "HYSTERIA2_CONFIG"
Hysteria2TrafficTime = "HYSTERIA2_TRAFFIC_TIME"
Hysteria2ConfigRemark = "HYSTERIA2_CONFIG_REMARK"
Hysteria2ConfigPortHopping = "HYSTERIA2_CONFIG_PORT_HOPPING"
ResetTrafficCron = "RESET_TRAFFIC_CRON"
ClashExtension = "CLASH_EXTENSION"
HUIWebPort = "H_UI_WEB_PORT"
HUIWebContext = "H_UI_WEB_CONTEXT"
HUICrtPath = "H_UI_CRT_PATH"
HUIKeyPath = "H_UI_KEY_PATH"
JwtSecret = "JWT_SECRET"
Hysteria2TrafficStatsSecret = "HYSTERIA2_TRAFFIC_STATS_SECRET"
Hysteria2Enable = "HYSTERIA2_ENABLE"
Hysteria2Config = "HYSTERIA2_CONFIG"
Hysteria2TrafficTime = "HYSTERIA2_TRAFFIC_TIME"
Hysteria2ConfigRemark = "HYSTERIA2_CONFIG_REMARK"
Hysteria2ConfigPortHopping = "HYSTERIA2_CONFIG_PORT_HOPPING"
ResetTrafficCron = "RESET_TRAFFIC_CRON"
ClashExtension = "CLASH_EXTENSION"
)
-1
View File
@@ -12,4 +12,3 @@ const (
WrongPassword string = "wrong password"
ConfigNotExist string = "config not exist"
)
-1
View File
@@ -18,4 +18,3 @@ const (
Version = "v0.0.22"
)
-1
View File
@@ -34,4 +34,3 @@ type AccountUpdateDto struct {
Deleted *int64 `json:"deleted" form:"deleted" validate:"omitempty,oneof=0 1"`
Remark *string `json:"remark" form:"remark" validate:"omitempty,min=0,max=32"`
}
-1
View File
@@ -16,4 +16,3 @@ type ConfigUpdateDto struct {
type ConfigsUpdateDto struct {
ConfigUpdateDtos []ConfigUpdateDto `json:"configUpdateDtos" form:"configUpdateDtos" validate:"required"`
}
-1
View File
@@ -10,4 +10,3 @@ type BaseDto struct {
type IdDto struct {
Id *int64 `json:"id" form:"id" validate:"required,gt=0"` // Первичный ключ
}
-1
View File
@@ -25,4 +25,3 @@ type Hysteria2UrlDto struct {
AccountId *int64 `json:"accountId" form:"accountId" validate:"required,gt=0"`
Hostname *string `json:"hostname" form:"hostname" validate:"required,min=1,max=255"`
}
-1
View File
@@ -7,4 +7,3 @@ type LogDto struct {
type LogExportDto struct {
Option *int `json:"option" form:"option" validate:"required,oneof=0 1"`
}
-1
View File
@@ -3,4 +3,3 @@ package dto
type ServerDto struct {
Port *int64 `json:"port" form:"port" validate:"required,min=1,max=65535"`
}
+4 -4
View File
@@ -14,8 +14,8 @@ type Account struct {
Deleted *int64 `gorm:"column:deleted;default:0" json:"deleted"`
BaseEntity `gorm:"embedded"`
LoginAt *int64 `gorm:"column:login_at;default:0" json:"loginAt"`
ConAt *int64 `gorm:"column:con_at;default:0" json:"conAt"`
Remark *string `gorm:"column:remark;default:''" json:"remark"`
LoginAt *int64 `gorm:"column:login_at;default:0" json:"loginAt"`
ConAt *int64 `gorm:"column:con_at;default:0" json:"conAt"`
Remark *string `gorm:"column:remark;default:''" json:"remark"`
ForcePasswordChange *int64 `gorm:"column:force_password_change;default:0" json:"forcePasswordChange"`
}
-1
View File
@@ -6,4 +6,3 @@ type Config struct {
Remark *string `gorm:"column:remark;default:''" json:"remark"`
BaseEntity `gorm:"embedded"`
}
-1
View File
@@ -7,4 +7,3 @@ type BaseEntity struct {
CreateTime *time.Time `gorm:"column:create_time;default:null" json:"createTime"`
UpdateTime *time.Time `gorm:"column:update_time;default:null" json:"updateTime"`
}
-1
View File
@@ -29,4 +29,3 @@ type AccountInfoVo struct {
Username string `json:"username"`
Roles []string `json:"roles"`
}
-1
View File
@@ -5,4 +5,3 @@ type ConfigVo struct {
Value string `json:"value"`
Remark string `json:"remark"`
}
-1
View File
@@ -38,4 +38,3 @@ type Hysteria2AcmePathVo struct {
CrtPath string `json:"crtPath"`
KeyPath string `json:"keyPath"`
}
+3 -3
View File
@@ -1,7 +1,7 @@
package vo
type JwtVo struct {
TokenType string `json:"tokenType"`
AccessToken string `json:"accessToken"`
TokenType string `json:"tokenType"`
AccessToken string `json:"accessToken"`
ForcePasswordChange bool `json:"forcePasswordChange"`
}
-1
View File
@@ -21,4 +21,3 @@ type LogHysteria2Vo struct {
Msg string `json:"msg"`
Time string `json:"time"`
}
-1
View File
@@ -13,4 +13,3 @@ type Hysteria2MonitorVo struct {
Version string `json:"version"` // Версия
Running bool `json:"running"` // Статус выполнения
}
-1
View File
@@ -44,4 +44,3 @@ func Fail(message string, c *gin.Context) {
Data: nil,
})
}
-1
View File
@@ -6,4 +6,3 @@ type BaseVo struct {
Id int64 `json:"id"`
CreateTime time.Time `json:"createTime"`
}
-1
View File
@@ -56,4 +56,3 @@ func (h *Hysteria2Process) Release() error {
}
return nil
}
-1
View File
@@ -134,4 +134,3 @@ func (h *Hysteria2Api) OnlineUsers(secret string) (map[string]int64, error) {
}
return onlineUsers, nil
}
-1
View File
@@ -227,4 +227,3 @@ func (p *process) handleLogs(stdout, stderr io.ReadCloser) {
}
}
}
-1
View File
@@ -21,4 +21,3 @@ func initAccountAdminRouter(accountApi *gin.RouterGroup) {
account.GET("/verifyDefaultPass", controller.VerifyDefaultPass)
}
}
-1
View File
@@ -11,4 +11,3 @@ func initAuthRouter(authApi *gin.RouterGroup) {
auth.POST("/login", controller.Login)
}
}
-1
View File
@@ -22,4 +22,3 @@ func initConfigRouter(configApi *gin.RouterGroup) {
config.POST("/uploadCertFile", controller.UploadCertFile)
}
}
-1
View File
@@ -24,4 +24,3 @@ func initHysteria2Router(hysteria2Api *gin.RouterGroup) {
hysteria2.GET("/hysteria2Url", controller.Hysteria2Url)
}
}
-1
View File
@@ -13,4 +13,3 @@ func initLogRouter(accountApi *gin.RouterGroup) {
account.POST("/exportLog", controller.ExportLog)
}
}
-1
View File
@@ -12,4 +12,3 @@ func initMonitorRouter(accountApi *gin.RouterGroup) {
account.GET("/monitorHysteria2", controller.MonitorHysteria2)
}
}
-1
View File
@@ -39,4 +39,3 @@ func Router(router *gin.Engine, huiWebContext *string) {
}
}
}
+24 -5
View File
@@ -10,20 +10,40 @@ import (
"hy2xs-admin/model/dto"
"hy2xs-admin/model/entity"
"hy2xs-admin/model/vo"
"hy2xs-admin/util"
)
func Login(username string, pass string) (string, error) {
account, err := dao.GetAccount("username = ? and pass = ? and role = 'admin' and deleted = 0", username, pass)
func Login(username string, plainPassword string) (string, bool, error) {
account, err := dao.GetAccount("username = ? and role = 'admin' and deleted = 0", username)
if err != nil {
return "", err
return "", false, err
}
verified, legacy := util.VerifyPassword(plainPassword, *account.Pass)
if !verified {
return "", false, errors.New(constant.WrongPassword)
}
if legacy {
hash, hashErr := util.HashPassword(plainPassword)
if hashErr == nil {
_ = dao.UpdateAccount([]int64{*account.Id}, map[string]interface{}{"pass": hash})
}
}
accountBo := bo.AccountBo{
Id: *account.Id,
Username: *account.Username,
Roles: []string{*account.Role},
Deleted: *account.Deleted,
}
return GenToken(accountBo)
token, tokenErr := GenToken(accountBo)
if tokenErr != nil {
return "", false, tokenErr
}
requirePasswordChange := legacy
return token, requirePasswordChange, nil
}
func PageAccount(accountPageDto dto.AccountPageDto) ([]entity.Account, int64, error) {
@@ -156,4 +176,3 @@ func GetAccountInfo(c *gin.Context) (vo.AccountInfoVo, error) {
Roles: myClaims.AccountBo.Roles,
}, nil
}
+7 -8
View File
@@ -74,23 +74,23 @@ func GetHysteria2Config() (bo.Hysteria2ServerConfig, error) {
func UpdateHysteria2Config(hysteria2ServerConfig bo.Hysteria2ServerConfig) error {
// Значения по умолчанию
config, err := dao.ListConfig("key in ?", []string{constant.HUIWebPort, constant.JwtSecret})
config, err := dao.ListConfig("key in ?", []string{constant.HUIWebPort, constant.Hysteria2TrafficStatsSecret})
if err != nil {
return err
}
var hUIWebPort string
var jwtSecret string
var trafficStatsSecret string
for _, item := range config {
if *item.Key == constant.HUIWebPort {
hUIWebPort = *item.Value
} else if *item.Key == constant.JwtSecret {
jwtSecret = *item.Value
} else if *item.Key == constant.Hysteria2TrafficStatsSecret {
trafficStatsSecret = *item.Value
}
}
if hUIWebPort == "" || jwtSecret == "" {
logrus.Errorf("hUIWebPort or jwtSecret is nil")
if hUIWebPort == "" || trafficStatsSecret == "" {
logrus.Errorf("hUIWebPort or trafficStatsSecret is nil")
return errors.New(constant.SysError)
}
@@ -108,7 +108,7 @@ func UpdateHysteria2Config(hysteria2ServerConfig bo.Hysteria2ServerConfig) error
http.Insecure = &authHttpInsecure
auth.HTTP = &http
hysteria2ServerConfig.Auth = &auth
hysteria2ServerConfig.TrafficStats.Secret = &jwtSecret
hysteria2ServerConfig.TrafficStats.Secret = &trafficStatsSecret
yamlConfig, err := yaml.Marshal(&hysteria2ServerConfig)
if err != nil {
@@ -195,4 +195,3 @@ func GetAuthHttpUrl() (string, error) {
}
return fmt.Sprintf("%s://127.0.0.1:%d%s/hui/hysteria2/auth", protocol, port, webContext), nil
}
+8 -9
View File
@@ -27,16 +27,16 @@ func CronHandleAccount() {
return
}
jwtSecretConfig, err := dao.GetConfig("key = ?", constant.JwtSecret)
trafficSecretConfig, err := dao.GetConfig("key = ?", constant.Hysteria2TrafficStatsSecret)
if err != nil {
return
}
// Сохранение данных трафика
go saveAccountTraffic(apiPort, *jwtSecretConfig.Value)
go saveAccountTraffic(apiPort, *trafficSecretConfig.Value)
// Принудительное отключение
go kickAccount(apiPort, *jwtSecretConfig.Value)
go kickAccount(apiPort, *trafficSecretConfig.Value)
}
}()
}
@@ -58,7 +58,7 @@ func CronResetTraffic() {
}
}
func saveAccountTraffic(apiPort int64, jwtSecret string) {
func saveAccountTraffic(apiPort int64, trafficStatsSecret string) {
if !trafficMutex.TryLock() {
return
}
@@ -74,7 +74,7 @@ func saveAccountTraffic(apiPort int64, jwtSecret string) {
return
}
users, err := proxy.NewHysteria2Api(apiPort).ListUsers(true, jwtSecret)
users, err := proxy.NewHysteria2Api(apiPort).ListUsers(true, trafficStatsSecret)
if err != nil {
return
}
@@ -96,13 +96,13 @@ func saveAccountTraffic(apiPort int64, jwtSecret string) {
}
}
func kickAccount(apiPort int64, jwtSecret string) {
func kickAccount(apiPort int64, trafficStatsSecret string) {
if !kickMutex.TryLock() {
return
}
defer kickMutex.Unlock()
users, err := proxy.NewHysteria2Api(apiPort).OnlineUsers(jwtSecret)
users, err := proxy.NewHysteria2Api(apiPort).OnlineUsers(trafficStatsSecret)
if err != nil {
return
}
@@ -130,7 +130,7 @@ func kickAccount(apiPort int64, jwtSecret string) {
kickUsernames[j] = *item.Username
j++
}
if err = proxy.NewHysteria2Api(apiPort).KickUsers(kickUsernames, jwtSecret); err != nil {
if err = proxy.NewHysteria2Api(apiPort).KickUsers(kickUsernames, trafficStatsSecret); err != nil {
return
}
}(usernameList)
@@ -138,4 +138,3 @@ func kickAccount(apiPort int64, jwtSecret string) {
wg.Wait()
}
}
-1
View File
@@ -232,4 +232,3 @@ func iptablesRules(protocol string) ([]string, error) {
rules := strings.Split(output, "\n")
return rules, nil
}
+18 -2
View File
@@ -65,16 +65,33 @@ func setHysteria2ConfigYAML() error {
logrus.Errorf("marshal hysteria2 config err: %v", err)
return errors.New("marshal hysteria2 config err")
}
file, err := os.OpenFile(constant.Hysteria2ConfigPath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
tmpPath := fmt.Sprintf("%s.tmp", constant.Hysteria2ConfigPath)
file, err := os.OpenFile(tmpPath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
if err != nil {
logrus.Errorf("create hysteria2 server config file err: %v", err)
return errors.New("create hysteria2 server config file err")
}
defer file.Close()
_, err = file.WriteString(string(hysteria2Config))
if err != nil {
logrus.Errorf("write hysteria2 config.json file err: %v", err)
return errors.New("hysteria2 config.json file write err")
}
if syncErr := file.Sync(); syncErr != nil {
return syncErr
}
if closeErr := file.Close(); closeErr != nil {
return closeErr
}
if renameErr := os.Rename(tmpPath, constant.Hysteria2ConfigPath); renameErr != nil {
return renameErr
}
if chmodErr := os.Chmod(constant.Hysteria2ConfigPath, 0600); chmodErr != nil {
return chmodErr
}
if chownErr := os.Chown(constant.Hysteria2ConfigPath, 0, 0); chownErr != nil {
return chownErr
}
return nil
}
@@ -147,4 +164,3 @@ func Hysteria2AcmePath() (vo.Hysteria2AcmePathVo, error) {
}
return vo.Hysteria2AcmePathVo{}, errors.New("cert not found")
}
+35 -22
View File
@@ -8,11 +8,28 @@ import (
"hy2xs-admin/model/bo"
"hy2xs-admin/model/constant"
"hy2xs-admin/proxy"
"net"
"net/url"
"strconv"
"strings"
"time"
)
func parseListenPort(listen string) (int, error) {
host, port, err := net.SplitHostPort(listen)
if err != nil {
return 0, err
}
if host == "" || port == "" {
return 0, errors.New("invalid listen address")
}
value, convErr := strconv.Atoi(port)
if convErr != nil || value < 1 || value > 65535 {
return 0, errors.New("invalid listen port")
}
return value, nil
}
func Hysteria2Auth(conPass string) (int64, string, error) {
if !Hysteria2IsRunning() {
return 0, "", errors.New("hysteria2 is not running")
@@ -45,11 +62,11 @@ func Hysteria2Online() (map[string]int64, error) {
if err != nil {
return nil, errors.New("get hysteria2 apiPort err")
}
jwtSecretConfig, err := dao.GetConfig("key = ?", constant.JwtSecret)
trafficSecretConfig, err := dao.GetConfig("key = ?", constant.Hysteria2TrafficStatsSecret)
if err != nil {
return nil, err
}
onlineUsers, err := proxy.NewHysteria2Api(apiPort).OnlineUsers(*jwtSecretConfig.Value)
onlineUsers, err := proxy.NewHysteria2Api(apiPort).OnlineUsers(*trafficSecretConfig.Value)
if err != nil {
return nil, err
}
@@ -76,11 +93,11 @@ func Hysteria2Kick(ids []int64, kickUtilTime int64) error {
if err != nil {
return errors.New("get hysteria2 apiPort err")
}
jwtSecretConfig, err := dao.GetConfig("key = ?", constant.JwtSecret)
trafficSecretConfig, err := dao.GetConfig("key = ?", constant.Hysteria2TrafficStatsSecret)
if err != nil {
return err
}
if err = proxy.NewHysteria2Api(apiPort).KickUsers(keys, *jwtSecretConfig.Value); err != nil {
if err = proxy.NewHysteria2Api(apiPort).KickUsers(keys, *trafficSecretConfig.Value); err != nil {
return err
}
return nil
@@ -125,13 +142,13 @@ func Hysteria2Subscribe(conPass string, clientType string, host string) (string,
hysteria2Name = *hysteria2ConfigRemark.Value
}
hysteria2ConfigPortHopping, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigPortHopping)
userInfo := ""
configStr := ""
listenPort, err := parseListenPort(*hysteria2Config.Listen)
if err != nil {
return "", "", err
}
userInfo := ""
configStr := ""
publicHost := strings.Split(host, ":")[0]
if clientType == constant.Shadowrocket || clientType == constant.Clash {
userInfo = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d",
*account.Upload,
@@ -142,9 +159,8 @@ func Hysteria2Subscribe(conPass string, clientType string, host string) (string,
hysteria2 := bo.Hysteria2{
Name: hysteria2Name,
Type: "hysteria2",
Server: strings.Split(host, ":")[0],
Port: strings.Split(*hysteria2Config.Listen, ":")[1],
Ports: *hysteria2ConfigPortHopping.Value,
Server: publicHost,
Port: strconv.Itoa(listenPort),
Password: conPass,
}
@@ -226,6 +242,13 @@ func Hysteria2Url(accountId int64, hostname string) (string, error) {
if hysteria2Config.Listen == nil || *hysteria2Config.Listen == "" {
return "", errors.New("hysteria2 config is empty")
}
port, err := parseListenPort(*hysteria2Config.Listen)
if err != nil {
return "", err
}
if hostname == "" || hostname == "0.0.0.0" {
return "", errors.New("invalid public host")
}
account, err := dao.GetAccount("id = ?", accountId)
if err != nil {
@@ -259,15 +282,6 @@ func Hysteria2Url(accountId int64, hostname string) (string, error) {
urlConfig += fmt.Sprintf("&downmbps=%s", url.PathEscape(*hysteria2Config.Bandwidth.Down))
}
hysteria2ConfigPortHopping, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigPortHopping)
if err != nil {
return "", err
}
if *hysteria2ConfigPortHopping.Value != "" {
// shadowrocket
urlConfig += fmt.Sprintf("&mport=%s", *hysteria2ConfigPortHopping.Value)
}
hysteria2ConfigRemark, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigRemark)
if err != nil {
return "", err
@@ -278,6 +292,5 @@ func Hysteria2Url(accountId int64, hostname string) (string, error) {
if urlConfig != "" {
urlConfig = "/?" + strings.TrimPrefix(urlConfig, "&")
}
return fmt.Sprintf("hysteria2://%s@%s%s", *account.ConPass, hostname, *hysteria2Config.Listen) + urlConfig, nil
return fmt.Sprintf("hysteria2://%s@%s:%d", *account.ConPass, hostname, port) + urlConfig, nil
}
-1
View File
@@ -58,4 +58,3 @@ func GetToken(c *gin.Context) string {
}
return strings.SplitN(tokenStr, " ", 2)[1]
}
-1
View File
@@ -62,4 +62,3 @@ func MonitorHysteria2() (vo.Hysteria2MonitorVo, error) {
hysteria2MonitorVo.Running = running
return hysteria2MonitorVo, nil
}
-1
View File
@@ -67,4 +67,3 @@ func GetServerPortAndCert() (int64, string, string, error) {
return port, crtPath, keyPath, nil
}
-1
View File
@@ -30,4 +30,3 @@ func SplitArr[T any](arr []T, num int) [][]T {
return segments
}
+26
View File
@@ -2,7 +2,11 @@ package util
import (
"crypto/sha256"
"errors"
"fmt"
"strings"
"golang.org/x/crypto/bcrypt"
)
func SHA224String(password string) string {
@@ -16,3 +20,25 @@ func SHA224String(password string) string {
return str
}
func HashPassword(password string) (string, error) {
if len(strings.TrimSpace(password)) < 6 {
return "", errors.New("password too short")
}
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hash), nil
}
func IsBcryptHash(hash string) bool {
return strings.HasPrefix(hash, "$2a$") || strings.HasPrefix(hash, "$2b$") || strings.HasPrefix(hash, "$2y$")
}
func VerifyPassword(password string, storedHash string) (ok bool, legacy bool) {
if IsBcryptHash(storedHash) {
err := bcrypt.CompareHashAndPassword([]byte(storedHash), []byte(password))
return err == nil, false
}
return SHA224String(password) == storedHash, true
}
-1
View File
@@ -5,4 +5,3 @@ import "testing"
func TestSHA224String(t *testing.T) {
println(SHA224String("sysadmin"))
}
-1
View File
@@ -38,4 +38,3 @@ func ExportFile(filePath string, data any, t int) error {
}
return nil
}
-1
View File
@@ -86,4 +86,3 @@ func FindFile(dir, filename string) (string, error) {
}
return result, nil
}
-58
View File
@@ -1,58 +0,0 @@
package util
import (
"context"
"fmt"
"github.com/google/go-github/v39/github"
)
var githubClient *github.Client
func init() {
githubClient = github.NewClient(nil)
}
func GetReleaseAssetURL(owner, repo, version, fileName string) (string, error) {
ctx := context.Background()
var release *github.RepositoryRelease
var err error
if version != "" {
release, _, err = githubClient.Repositories.GetReleaseByTag(ctx, owner, repo, version)
if err != nil {
return "", fmt.Errorf("failed to get release for version %s: %v", version, err)
}
} else {
releases, _, err := githubClient.Repositories.ListReleases(ctx, owner, repo, nil)
if err != nil {
return "", fmt.Errorf("failed to list releases: %v", err)
}
if len(releases) == 0 {
return "", fmt.Errorf("no releases found")
}
release = releases[0]
}
assets, _, err := githubClient.Repositories.ListReleaseAssets(ctx, owner, repo, release.GetID(), nil)
if err != nil {
return "", fmt.Errorf("failed to list release assets: %v", err)
}
for _, asset := range assets {
if asset.GetName() == fileName {
return asset.GetBrowserDownloadURL(), nil
}
}
return "", fmt.Errorf("file '%s' not found in release '%s'", fileName, release.GetTagName())
}
func ListRelease(owner, repo string) ([]*github.RepositoryRelease, error) {
ctx := context.Background()
releases, _, err := githubClient.Repositories.ListReleases(ctx, owner, repo, nil)
if err != nil {
return nil, fmt.Errorf("failed to list releases: %v", err)
}
return releases, nil
}
-57
View File
@@ -1,66 +1,9 @@
package util
import (
"fmt"
"hy2xs-admin/model/constant"
"io"
"net/http"
"os"
"runtime"
)
func GetHysteria2BinPath() string {
return constant.Hysteria2BinPath
}
func GetHysteria2BinName() string {
hysteria2FileName := fmt.Sprintf("hysteria-%s-%s", runtime.GOOS, runtime.GOARCH)
if runtime.GOOS == "windows" {
hysteria2FileName += ".exe"
}
return hysteria2FileName
}
func DownloadHysteria2(version string) error {
hysteria2BinName := GetHysteria2BinName()
hysteria2BinPath := GetHysteria2BinPath()
// Download the latest version of Hysteria2
url, err := GetReleaseAssetURL("apernet", "hysteria", version, hysteria2BinName)
if err != nil {
return err
}
resp, err := http.Get(url)
defer resp.Body.Close()
if err != nil {
return fmt.Errorf("failed to download file: %v", err)
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to download file, status code: %d", resp.StatusCode)
}
if Exists(hysteria2BinPath) {
if err = os.Remove(hysteria2BinPath); err != nil {
return fmt.Errorf("failed to remove existing file: %v", err)
}
}
file, err := os.Create(hysteria2BinPath)
defer file.Close()
if err != nil {
return fmt.Errorf("failed to create file %s: %v", hysteria2BinPath, err)
}
_, err = io.Copy(file, resp.Body)
if err != nil {
return fmt.Errorf("failed to write to file: %v", err)
}
if err = os.Chmod(hysteria2BinPath, 0755); err != nil {
return fmt.Errorf("failed to change file permissions: %v", err)
}
return nil
}
-1
View File
@@ -98,4 +98,3 @@ func VerifyPort(port string) error {
}
return nil
}
-1
View File
@@ -21,4 +21,3 @@ func SplitMap[T any](inputMap map[string]T, chunkSize int) []map[string]T {
return segments
}
-1
View File
@@ -17,4 +17,3 @@ func RandomString(length int) (string, error) {
return string(bytes), nil
}
-1
View File
@@ -34,4 +34,3 @@ func CompareVersion(version1, version2 string) int {
// The version number is exactly the same
return 0
}