Fix25.1: стабилизирован auth_id flow и unlimited-семантика, усилен bootstrap secrets
This commit is contained in:
+52
-3
@@ -63,6 +63,9 @@ func InitSql(port string) error {
|
|||||||
if err := ensureSecureBootstrapAdmin(); err != nil {
|
if err := ensureSecureBootstrapAdmin(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if _, err := getOrCreateJwtSecret(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := ensureTrafficStatsSecret(); err != nil {
|
if err := ensureTrafficStatsSecret(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -337,7 +340,11 @@ func migrateLegacyAccounts() error {
|
|||||||
if authErr != nil {
|
if authErr != nil {
|
||||||
return authErr
|
return authErr
|
||||||
}
|
}
|
||||||
secretDigest := util.PeerSecretDigest(*acc.ConPass)
|
peerSecretKey, keyErr := getOrCreatePeerSecretDigestKey()
|
||||||
|
if keyErr != nil {
|
||||||
|
return keyErr
|
||||||
|
}
|
||||||
|
secretDigest := util.HmacSHA256Hex(*acc.ConPass, peerSecretKey)
|
||||||
secretEncrypted := *acc.ConPass
|
secretEncrypted := *acc.ConPass
|
||||||
quota := int64(0)
|
quota := int64(0)
|
||||||
if acc.Quota != nil {
|
if acc.Quota != nil {
|
||||||
@@ -397,6 +404,46 @@ func migrateLegacyAccounts() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getOrCreateJwtSecret() (string, error) {
|
||||||
|
if existing, err := GetConfig("key = ?", constant.JwtSecret); err == nil {
|
||||||
|
if existing.Value != nil && strings.TrimSpace(*existing.Value) != "" {
|
||||||
|
return strings.TrimSpace(*existing.Value), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
secret, secErr := util.RandomString(64)
|
||||||
|
if secErr != nil {
|
||||||
|
return "", secErr
|
||||||
|
}
|
||||||
|
if err := UpdateConfig([]string{constant.JwtSecret}, map[string]interface{}{"value": secret}); err != nil {
|
||||||
|
key := constant.JwtSecret
|
||||||
|
remark := "JWT signing secret"
|
||||||
|
if _, saveErr := SaveConfig(entity.Config{Key: &key, Value: &secret, Remark: &remark}); saveErr != nil {
|
||||||
|
return "", saveErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return secret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOrCreatePeerSecretDigestKey() (string, error) {
|
||||||
|
if existing, err := GetConfig("key = ?", constant.PeerSecretKey); err == nil {
|
||||||
|
if existing.Value != nil && strings.TrimSpace(*existing.Value) != "" {
|
||||||
|
return strings.TrimSpace(*existing.Value), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keyValue, keyErr := util.RandomString(48)
|
||||||
|
if keyErr != nil {
|
||||||
|
return "", keyErr
|
||||||
|
}
|
||||||
|
if err := UpdateConfig([]string{constant.PeerSecretKey}, map[string]interface{}{"value": keyValue}); err != nil {
|
||||||
|
key := constant.PeerSecretKey
|
||||||
|
remark := "Peer secret digest key"
|
||||||
|
if _, saveErr := SaveConfig(entity.Config{Key: &key, Value: &keyValue, Remark: &remark}); saveErr != nil {
|
||||||
|
return "", saveErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keyValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
func archiveLegacyAccount() error {
|
func archiveLegacyAccount() error {
|
||||||
if !tableExists("account") {
|
if !tableExists("account") {
|
||||||
return nil
|
return nil
|
||||||
@@ -517,7 +564,7 @@ func ensureTrafficStatsSecret() error {
|
|||||||
envSecret := strings.TrimSpace(os.Getenv("HY2XS_HYSTERIA_TRAFFIC_STATS_SECRET"))
|
envSecret := strings.TrimSpace(os.Getenv("HY2XS_HYSTERIA_TRAFFIC_STATS_SECRET"))
|
||||||
if envSecret != "" {
|
if envSecret != "" {
|
||||||
if existing, err := GetConfig("key = ?", constant.Hysteria2TrafficStatsSecret); err == nil {
|
if existing, err := GetConfig("key = ?", constant.Hysteria2TrafficStatsSecret); err == nil {
|
||||||
if existing.Value != nil && *existing.Value != envSecret {
|
if existing.Value != nil && strings.TrimSpace(*existing.Value) != envSecret {
|
||||||
return UpdateConfig([]string{constant.Hysteria2TrafficStatsSecret}, map[string]interface{}{"value": envSecret})
|
return UpdateConfig([]string{constant.Hysteria2TrafficStatsSecret}, map[string]interface{}{"value": envSecret})
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -530,9 +577,11 @@ func ensureTrafficStatsSecret() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := GetConfig("key = ?", constant.Hysteria2TrafficStatsSecret); err == nil {
|
if existing, err := GetConfig("key = ?", constant.Hysteria2TrafficStatsSecret); err == nil {
|
||||||
|
if existing.Value != nil && strings.TrimSpace(*existing.Value) != "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
secret, secErr := util.RandomString(32)
|
secret, secErr := util.RandomString(32)
|
||||||
if secErr != nil {
|
if secErr != nil {
|
||||||
return secErr
|
return secErr
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ const dialog = reactive({ visible: false, title: "", editId: 0 });
|
|||||||
const dataForm = reactive<PeerSaveDto & { id?: number }>({
|
const dataForm = reactive<PeerSaveDto & { id?: number }>({
|
||||||
name: "",
|
name: "",
|
||||||
secret: "",
|
secret: "",
|
||||||
quotaBytes: 0,
|
quotaBytes: -1,
|
||||||
expiresAt: getMonthLater(),
|
expiresAt: getMonthLater(),
|
||||||
maxDevices: 3,
|
maxDevices: 3,
|
||||||
disabled: 0,
|
disabled: 0,
|
||||||
@@ -123,7 +123,7 @@ async function handleQuery() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleAdd() {
|
function handleAdd() {
|
||||||
Object.assign(dataForm, { id: undefined, name: "", secret: "", quotaBytes: 0, expiresAt: getMonthLater(), maxDevices: 3, disabled: 0, remark: "" });
|
Object.assign(dataForm, { id: undefined, name: "", secret: "", quotaBytes: -1, expiresAt: getMonthLater(), maxDevices: 3, disabled: 0, remark: "" });
|
||||||
dialog.title = t("common.add");
|
dialog.title = t("common.add");
|
||||||
dialog.editId = 0;
|
dialog.editId = 0;
|
||||||
dialog.visible = true;
|
dialog.visible = true;
|
||||||
|
|||||||
+15
-13
@@ -82,12 +82,9 @@ func saveAccountTraffic(apiPort int64, trafficStatsSecret string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
peer, peerErr := dao.GetPeer("auth_id = ?", key)
|
peer, peerErr := dao.GetPeer("auth_id = ?", key)
|
||||||
if peerErr != nil {
|
|
||||||
peer, peerErr = dao.GetPeer("name = ?", key)
|
|
||||||
if peerErr != nil {
|
if peerErr != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if peer.Id == nil {
|
if peer.Id == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -133,32 +130,37 @@ func kickAccount(apiPort int64, trafficStatsSecret string) {
|
|||||||
}
|
}
|
||||||
if len(users) > 0 {
|
if len(users) > 0 {
|
||||||
i := 0
|
i := 0
|
||||||
usernames := make([]string, len(users))
|
authIDs := make([]string, len(users))
|
||||||
for k := range users {
|
for k := range users {
|
||||||
usernames[i] = k
|
authIDs[i] = k
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
usernameLists := util.SplitArr(usernames, 10)
|
authIDLists := util.SplitArr(authIDs, 10)
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for _, usernameList := range usernameLists {
|
for _, authIDList := range authIDLists {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(usernameList []string) {
|
go func(authIDList []string) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
now := time.Now().UnixMilli()
|
now := time.Now().UnixMilli()
|
||||||
peers, err := dao.ListPeer("name in ? and (disabled = 1 or (quota_bytes > 0 and quota_bytes < download_bytes + upload_bytes) or ? > expires_at or ? < banned_until)", usernameList, now, now)
|
peers, err := dao.ListPeer(`auth_id in ? and (
|
||||||
|
disabled = 1
|
||||||
|
or (quota_bytes > 0 and quota_bytes < download_bytes + upload_bytes)
|
||||||
|
or (expires_at > 0 and ? > expires_at)
|
||||||
|
or ? < banned_until
|
||||||
|
)`, authIDList, now, now)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
kickUsernames := make([]string, len(peers))
|
kickAuthIDs := make([]string, len(peers))
|
||||||
j := 0
|
j := 0
|
||||||
for _, item := range peers {
|
for _, item := range peers {
|
||||||
kickUsernames[j] = *item.Name
|
kickAuthIDs[j] = *item.AuthId
|
||||||
j++
|
j++
|
||||||
}
|
}
|
||||||
if err = proxy.NewHysteria2Api(apiPort).KickUsers(kickUsernames, trafficStatsSecret); err != nil {
|
if err = proxy.NewHysteria2Api(apiPort).KickUsers(kickAuthIDs, trafficStatsSecret); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}(usernameList)
|
}(authIDList)
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"hy2xs-admin/dao"
|
"hy2xs-admin/dao"
|
||||||
"hy2xs-admin/model/constant"
|
"hy2xs-admin/model/constant"
|
||||||
"hy2xs-admin/proxy"
|
"hy2xs-admin/proxy"
|
||||||
"hy2xs-admin/util"
|
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@@ -36,8 +35,15 @@ func Hysteria2Auth(conPass string) (int64, string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now().UnixMilli()
|
now := time.Now().UnixMilli()
|
||||||
secretDigest := util.PeerSecretDigest(conPass)
|
secretDigest, digestErr := PeerSecretDigest(conPass)
|
||||||
peer, err := dao.GetPeer("secret_digest = ? and disabled = 0 and (quota_bytes < 0 or quota_bytes > download_bytes + upload_bytes) and ? < expires_at and ? > banned_until", secretDigest, now, now)
|
if digestErr != nil {
|
||||||
|
return 0, "", digestErr
|
||||||
|
}
|
||||||
|
peer, err := dao.GetPeer(`secret_digest = ?
|
||||||
|
and disabled = 0
|
||||||
|
and (quota_bytes < 0 or quota_bytes > download_bytes + upload_bytes)
|
||||||
|
and (expires_at = 0 or ? < expires_at)
|
||||||
|
and ? > banned_until`, secretDigest, now, now)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, "", err
|
return 0, "", err
|
||||||
}
|
}
|
||||||
@@ -47,12 +53,12 @@ func Hysteria2Auth(conPass string) (int64, string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, "", err
|
return 0, "", err
|
||||||
}
|
}
|
||||||
device, exist := onlineUsers[*peer.Name]
|
device, exist := onlineUsers[*peer.AuthId]
|
||||||
if exist && *peer.MaxDevices <= device {
|
if exist && *peer.MaxDevices <= device {
|
||||||
return 0, "", errors.New("device limited")
|
return 0, "", errors.New("device limited")
|
||||||
}
|
}
|
||||||
|
|
||||||
return *peer.Id, *peer.Name, nil
|
return *peer.Id, *peer.AuthId, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Hysteria2Online() (map[string]int64, error) {
|
func Hysteria2Online() (map[string]int64, error) {
|
||||||
@@ -88,7 +94,7 @@ func Hysteria2Kick(ids []int64, kickUtilTime int64) error {
|
|||||||
}
|
}
|
||||||
var keys []string
|
var keys []string
|
||||||
for _, item := range peers {
|
for _, item := range peers {
|
||||||
keys = append(keys, *item.Name)
|
keys = append(keys, *item.AuthId)
|
||||||
}
|
}
|
||||||
apiPort, err := GetHysteria2ApiPort()
|
apiPort, err := GetHysteria2ApiPort()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user