修复跨域请求问题
Some checks failed
build / build (api, amd64, linux) (push) Failing after -49s
build / build (api, arm64, linux) (push) Failing after -51s
build / build (api.exe, amd64, windows) (push) Failing after -51s

This commit is contained in:
CN-JS-HuiBai
2026-04-17 22:50:46 +08:00
parent 9f6a4515c0
commit e7b123ea59
11 changed files with 91 additions and 1069 deletions

View File

@@ -20,7 +20,7 @@ func main() {
database.InitCache()
router := gin.New()
router.Use(gin.Logger(), gin.Recovery())
router.Use(gin.Logger(), gin.Recovery(), middleware.CORS())
api := router.Group("/api")
registerV1(api.Group("/v1"))

View File

@@ -22,6 +22,7 @@ type Config struct {
AppPort string
AppURL string
PluginRoot string
AppDebug bool
}
var AppConfig *Config
@@ -46,9 +47,22 @@ func LoadConfig() {
AppPort: getEnv("APP_PORT", "8080"),
AppURL: getEnv("APP_URL", ""),
PluginRoot: getEnv("PLUGIN_ROOT", "reference\\LDNET-GA-Theme\\plugin"),
AppDebug: getEnvBool("APP_DEBUG", false),
}
}
func getEnvBool(key string, defaultValue bool) bool {
raw := getEnv(key, "")
if raw == "" {
return defaultValue
}
value, err := strconv.ParseBool(raw)
if err != nil {
return defaultValue
}
return value
}
func getEnv(key, defaultValue string) string {
if value, exists := os.LookupEnv(key); exists {
return value

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@ import (
"net/http"
"strings"
"time"
"xboard-go/internal/config"
"xboard-go/internal/database"
"xboard-go/internal/model"
"xboard-go/internal/service"
@@ -185,11 +186,14 @@ func SendEmailVerify(c *gin.Context) {
}
service.StoreEmailVerifyCode(email, code, 10*time.Minute)
SuccessMessage(c, "email verify code generated", gin.H{
data := gin.H{
"email": req.Email,
"debug_code": code,
"expires_in": 600,
})
}
if config.AppConfig.AppDebug {
data["debug_code"] = code
}
SuccessMessage(c, "email verify code generated", data)
}
func ForgetPassword(c *gin.Context) {

View File

@@ -97,7 +97,7 @@ func UserGetSubscribe(c *gin.Context) {
"next_reset_at": user.NextResetAt,
"plan": user.Plan,
"subscribe_url": strings.TrimRight(baseURL, "/") + "/s/" + user.Token,
"reset_day": nil,
"reset_day": user.ResetDay,
})
}

View File

@@ -33,6 +33,11 @@ type adminAppViewData struct {
}
func UserThemePage(c *gin.Context) {
baseURL := service.GetAppURL()
if origin := requestOrigin(c.Request); origin != "" {
baseURL = origin
}
config := map[string]any{
"accent": service.MustGetString("nebula_theme_color", "aurora"),
"slogan": service.MustGetString("nebula_hero_slogan", "One control center for login, subscriptions, sessions, and device visibility."),
@@ -47,6 +52,7 @@ func UserThemePage(c *gin.Context) {
"psbNo": service.MustGetString("psb_no", ""),
"staticCdnUrl": service.MustGetString("nebula_static_cdn_url", ""),
"isRegisterEnabled": !service.MustGetBool("stop_register", false),
"baseUrl": baseURL,
}
payload := userThemeViewData{
@@ -119,23 +125,34 @@ func requestOrigin(r *http.Request) string {
return ""
}
// 1. Try X-Forwarded-Proto (High priority for proxies)
scheme := r.Header.Get("X-Forwarded-Proto")
if scheme == "" {
if r.TLS != nil {
// Fallback for some proxies
if r.Header.Get("X-Forwarded-Ssl") == "on" || r.Header.Get("X-Forwarded-Scheme") == "https" {
scheme = "https"
} else if r.TLS != nil {
scheme = "https"
} else {
scheme = "http"
}
}
// 2. Try X-Forwarded-Host or Host
host := r.Header.Get("X-Forwarded-Host")
if host == "" {
host = r.Host
}
// 3. Fallback to Origin header if still empty (rare but possible)
if host == "" {
if origin := r.Header.Get("Origin"); origin != "" {
return origin
}
return ""
}
// 4. Handle Port if present in Forwarded headers
if forwardedPort := r.Header.Get("X-Forwarded-Port"); forwardedPort != "" && !strings.Contains(host, ":") {
defaultPort := map[string]string{
"http": "80",

View File

@@ -1,52 +0,0 @@
//go:build ignore
package middleware
import (
"net/http"
"strings"
"xboard-go/pkg/utils"
"github.com/gin-gonic/gin"
)
func Auth() gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.JSON(http.StatusUnauthorized, gin.H{"message": "未登录"})
c.Abort()
return
}
parts := strings.SplitN(authHeader, " ", 2)
if !(len(parts) == 2 && parts[0] == "Bearer") {
c.JSON(http.StatusUnauthorized, gin.H{"message": "无效的认证格式"})
c.Abort()
return
}
claims, err := utils.VerifyToken(parts[1])
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"message": "登录已过期"})
c.Abort()
return
}
c.Set("user_id", claims.UserID)
c.Set("is_admin", claims.IsAdmin)
c.Next()
}
}
func AdminAuth() gin.HandlerFunc {
return func(c *gin.Context) {
isAdmin, exists := c.Get("is_admin")
if !exists || !isAdmin.(bool) {
c.JSON(http.StatusForbidden, gin.H{"message": "权限不足"})
c.Abort()
return
}
c.Next()
}
}

View File

@@ -57,12 +57,21 @@ func Auth() gin.HandlerFunc {
func AdminAuth() gin.HandlerFunc {
return func(c *gin.Context) {
isAdmin, exists := c.Get("is_admin")
if !exists || !isAdmin.(bool) {
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"message": "unauthorized"})
c.Abort()
return
}
var user model.User
if err := database.DB.Select("is_admin").First(&user, userID).Error; err != nil || !user.IsAdmin {
c.JSON(http.StatusForbidden, gin.H{"message": "admin access required"})
c.Abort()
return
}
c.Set("is_admin", user.IsAdmin)
c.Next()
}
}

View File

@@ -0,0 +1,28 @@
package middleware
import (
"net/http"
"github.com/gin-gonic/gin"
)
func CORS() gin.HandlerFunc {
return func(c *gin.Context) {
origin := c.Request.Header.Get("Origin")
if origin == "" {
origin = "*"
}
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE, PATCH")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}

View File

@@ -45,6 +45,7 @@ type User struct {
NextResetAt *int64 `gorm:"column:next_reset_at" json:"next_reset_at"`
LastResetAt *int64 `gorm:"column:last_reset_at" json:"last_reset_at"`
ResetCount int `gorm:"column:reset_count;default:0" json:"reset_count"`
ResetDay *int `gorm:"column:reset_day" json:"reset_day"`
}
func (User) TableName() string {

View File

@@ -298,6 +298,16 @@ func BuildNodeConfig(node *model.Server) NodeServerConfig {
}
func ApplyTrafficDelta(userID int, node *model.Server, upload, download int64) {
groupIDs := parseIntSlice(node.GroupIDs)
if len(groupIDs) > 0 {
var count int64
if err := database.DB.Model(&model.User{}).
Where("id = ? AND group_id IN ?", userID, groupIDs).
Count(&count).Error; err != nil || count == 0 {
return
}
}
rate := CurrentRate(node)
scaledUpload := int64(math.Round(float64(upload) * rate))
scaledDownload := int64(math.Round(float64(download) * rate))